Spawning units on our map and moving them (Post #15 / Strategy game #6)

Now that we finished our basic drawing calls and our map creation, let's add some units on the map and program some basic game moving concepts. (It will be a long post since we'll cover multiple steps together)

Redesigning our units

Since I redesigned the UI couple of posts back, and got one step closer to actually continue with the game programming I thought maybe I should re-draw my units too. So I got to it and came up with the following result.

Old static unit sheet

New animated unit sheet

As you can see I decided to draw moving animations when our units move through the map, so now we will adjust our unit drawing call to get frames too. (I added here the updated unit sheet in case you want to use it.)

Creating our basic module (This will change heavily probably..)

 I created a new module in which I made a structure to hold our factions data. (which on it's own is pretty much another structure, it's done like that so it's easier to work on than just having variables thrown here and there).

      Public Structure FactionStruct
        Dim Name As String
        Dim Nation As String
        Dim Color As Color
        Dim Unit() As UnitStruct

'StrategyLibrary is a reference to something we'll implement on another post
        'Dim Resources As StrategyLibrary
        'Dim References As StrategyLibrary
        'Dim Modifiers_A As StrategyLibrary
        'Dim Modifiers_B As StrategyLibrary
    End Structure

    Public Structure UnitStruct
        Dim X As Integer
        Dim Y As Integer
        Dim TargetX As Integer
        Dim TargetY As Integer
        Dim TileX As Integer
        Dim TileY As Integer
        Dim ID As Integer 'To specify unit ID from a unit library
        Dim TileToM() As Point 'Here we'll store all the available unit target tiles at any time
        Dim TileToMC As Integer 'Tile To Move Clicked
        Dim Frame As Integer 'For animation reasons
        Dim Health As Integer
        Dim Attack As Integer
        Dim Defense As Integer
        Dim OnTheMove As Int16 'To specify if the units is moving at any time
        Dim TurnPoints As Int16 'To specify how many times the unit can move
    End Structure

    Public MaxUnitsPF As Integer = 50 'Unit cap (For each faction)
    Public UnitSelected As Integer = 0 'Selected Unit
    Public UnitSelectF As Integer = 0 'Selected Unit Tile <<Frame>>
    Public UnitSelectedMouseHover As Integer 'Direction Mouse Hovers over

    Public MaxFactions As Int16 = 2 'Number of factions
    Public Faction(MaxFactions) As FactionStruct 'Faction structure

 Spawning our first unit

New page on our strategy game! So now that we have our basic faction structure ready, let's spawn our main unit! Our settlers!

In order to do so, we'll have to find a suitable block (non-ocean block), and a Do Until loop will fit perfectly. After we find that tile we'll need to assign to our unit values for Tile X,Y and Screen X,Y. Lastly we'll center (kinda) our camera on our settler.

    Public Sub Faction_F(ByVal func As String)
        Dim I, Q As Int16
        Dim X, Y As Integer
        If func = "CREATE_FACTIONS" Then
            For I = 0 To MaxFactions
                ReDim Faction(I).Unit(MaxUnitsPF)
                For Q = 0 To MaxUnitsPF
                    ReDim Faction(I).Unit(Q).TileToM(7)
                Next
            Next
            For I = 0 To MaxFactions 'Going through all the factions
                Faction(I).Unit(0).ID = 1 'Assigning settler unit

                Faction(I).Unit(0).TileX = Int(Rnd() * MaxGridX) 'Random tile X
                Faction(I).Unit(0).TileY = Int(Rnd() * MaxGridY) 'Random tile Y
                Do Until GridWorld(Faction(I).Unit(0).TileX, Faction(I).Unit(0).TileY).ID >= 4 'Make sure no unit is spawned on an ocean tile
                    Faction(I).Unit(0).TileX = Int(Rnd() * MaxGridX)
                    Faction(I).Unit(0).TileY = Int(Rnd() * MaxGridY)
                Loop
                X = Faction(I).Unit(0).TileX
                Y = Faction(I).Unit(0).TileY
                Faction(I).Unit(0).X = ((X - Y) * GridWidth / 2) * ZoomVar 'Making sure it gets the proper X,Y based on resizing
                Faction(I).Unit(0).Y = ((X + Y) * GridHeight / 2) * ZoomVar 'Making sure it gets the proper X,Y based on resizing
                Faction(I).Unit(0).TargetX = Faction(I).Unit(0).X
                Faction(I).Unit(0).TargetY = Faction(I).Unit(0).Y
                Faction(I).Unit(0).TurnPoints = 20 'For testing reasons we'll give 20 moving points to our settler
            Next
            UnitSelected = 0 'There is only our settler, so we select it
            Unit_F("MOVE_CALCULATE", UnitSelected)
            'Center the camera on our settler
            OffSetX = -Faction(0).Unit(0).X + ScreenWidth / 2 - 140
            OffSetY = -Faction(0).Unit(0).Y + ScreenHeight / 2 - 60
        End If
    End Sub

Testing our above algorithm

The first step was done (if you include this Faction_F("CREATE_FACTIONS")) at the end of our map creation algorithm and call a draw_textured for our unit (I will include the code as a whole at the next steps) and run the application you should get the result as shown below.

Our first unit is on the map!

To explain it a bit better. (Our  TileToM() array will hold the data of the 8 tiles around our unit whenever our unit moves through our map grid. Now on our move move event we'll check if our mouse is over one of those 8 tiles and if clicked it will move to that tile)

More unit algorithms

I'll include large chunks of code since it will be a long post and explain them as it goes. You will understand how they are all connected at the end. If you have questions just post a response at the article!

'On our Unit_F sub we control our mouse events (hover over the available tiles our unit can move to, clicking on one and of course lastly Calculating which are the available tiles our unit can move to!)

 Public Sub Unit_F(ByVal Func As String, ByVal ID As Integer)
        Dim Q As Int16

        If Func = "MOUSE_HOVER_UNIT" Then
            Dim HoveringOver As Int16 = 0
            For Q = 0 To 7
                If GridPX = Faction(0).Unit(ID).TileToM(Q).X And GridPY = Faction(0).Unit(ID).TileToM(Q).Y Then
                    UnitSelectedMouseHover = Q
                    HoveringOver = 1
                    Exit For
                End If
            Next
            If HoveringOver = 0 Then
                UnitSelectedMouseHover = 10
            End If

        ElseIf Func = "MOUSE_MOVE_UNIT" Then

            'We check if the mouse clicked on an (can move to) tile, then if the unit is not already on the move and lastly if it has remaining points to move
            If UnitSelectedMouseHover <> 10 And Faction(0).Unit(UnitSelected).OnTheMove = 0 And Faction(0).Unit(UnitSelected).TurnPoints > 0 Then

                Faction(0).Unit(UnitSelected).TargetX = GridWorld(Faction(0).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).X, Faction(0).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).Y).SX
                Faction(0).Unit(UnitSelected).TargetY = GridWorld(Faction(0).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).X, Faction(0).Unit(UnitSelected).TileToM(UnitSelectedMouseHover).Y).SY
                Faction(0).Unit(UnitSelected).OnTheMove = 1
                Faction(0).Unit(UnitSelected).TileToMC = UnitSelectedMouseHover
                'As you can see we assigned the target X,Y variables the point we want our unit to move to and we declared it as "On the move"
            End If

        ElseIf Func = "MOVE_CALCULATE" Then 'Which tiles can we move to?
            Q = 0
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX + 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY
            Q = 1
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX + 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY + 1
            Q = 2
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY + 1
            Q = 3
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX - 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY + 1
            Q = 4
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX - 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY
            Q = 5
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX - 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY - 1
            Q = 6
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY - 1
            Q = 7
            Faction(0).Unit(ID).TileToM(Q).X = Faction(0).Unit(ID).TileX + 1
            Faction(0).Unit(ID).TileToM(Q).Y = Faction(0).Unit(ID).TileY - 1
        End If

    End Sub

'On our Unit_F sub we control the movement of our unit when a target tile is clicked. It's a simple if X<>TargetX and Y<>TargetY then move till reach & change frame while moving.

Public Sub Unit_M(ByVal X As Integer, ByVal Y As Integer)
        If Faction(Y).Unit(X).X <> Faction(Y).Unit(X).TargetX Then
            If Faction(Y).Unit(X).X > Faction(Y).Unit(X).TargetX Then
                Faction(Y).Unit(X).X -= 1
            ElseIf Faction(Y).Unit(X).X < Faction(Y).Unit(X).TargetX Then
                Faction(Y).Unit(X).X += 1
            End If

        End If
        If Faction(Y).Unit(X).Y <> Faction(Y).Unit(X).TargetY Then

            If Faction(Y).Unit(X).Y > Faction(Y).Unit(X).TargetY Then
                Faction(Y).Unit(X).Y -= 1
            ElseIf Faction(Y).Unit(X).Y < Faction(Y).Unit(X).TargetY Then
                Faction(Y).Unit(X).Y += 1
            End If
        End If
        If Faction(Y).Unit(X).X = Faction(Y).Unit(X).TargetX And Faction(Y).Unit(X).Y = Faction(Y).Unit(X).TargetY Then
            If Faction(Y).Unit(X).OnTheMove = 1 Then
                Faction(Y).Unit(X).TileX = Faction(Y).Unit(X).TileToM(Faction(Y).Unit(X).TileToMC).X
                Faction(Y).Unit(X).TileY = Faction(Y).Unit(X).TileToM(Faction(Y).Unit(X).TileToMC).Y
                Faction(Y).Unit(X).OnTheMove = 0
                Unit_F("MOVE_CALCULATE", X)
            End If
        End If
        If Faction(Y).Unit(X).OnTheMove = 1 Then
            If Faction(Y).Unit(X).Frame < 27 Then
                Faction(Y).Unit(X).Frame += 1
            Else
                Faction(Y).Unit(X).Frame = 4
            End If
        Else
            Faction(Y).Unit(X).Frame = 0
        End If
    End Sub

'Now we'll update a bit our old Grid algorithms (Mouse move and click, resizing and changing Field of view and field of war. You'll see there are additions to resizing since Units are drawn directly with they pixel X,Y without passing through our grid X,Y reference.

 Public Sub GLMouseMove(ByVal X1 As Integer, ByVal Y1 As Integer)
        Dim X, Y As Integer
        X = X1 - OffSetX
        Y = Y1 - OffSetY
        GridPX = (Y + X / 2) / (GridHeight * ZoomVar)
        GridPY = GridPX - (X) / (GridHeight * ZoomVar)
        Unit_F("MOUSE_HOVER_UNIT", 0)
    End Sub
    Public Sub GLMouseDown(ByVal Btn As MouseEventArgs) 'Just call this from you clicking on screen event
        'If you run this from a console openGL application then you'll need to change it a bit since there is a different event call

        If Btn.Button = MouseButtons.Left Then
            Unit_F("MOUSE_MOVE_UNIT", 0)
        End If
    End Sub

 Public Sub FOV_FOW_Grid()
        Dim X, Y, Q As Integer
        Q = 1
        For Y = 0 To MaxGridY
            For X = 0 To MaxGridX
                GridWorld(X, Y).FOW = 0
            Next
        Next
        For Q = 0 To MaxUnitsPF
            If Faction(0).Unit(Q).ID <> 0 Then
                For Y = (Faction(0).Unit(Q).TileY - 2) To (Faction(0).Unit(Q).TileY + 2)
                    For X = (Faction(0).Unit(Q).TileX - 2) To (Faction(0).Unit(Q).TileX + 2)
                        If GridWorld(X, Y).FOV = 0 Then GridWorld(X, Y).FOV = 1
                        GridWorld(X, Y).FOW = 1
                    Next
                Next
            End If
        Next
    End Sub
    Public Sub Grid_Resize(ByVal Func As String)
        Dim X, Y As Integer
        Dim Q, I As Integer
        If Func = "Minus" Then
            If ZoomVar > 0.6F Then
                ZoomVar -= 0.2F
                'Tweaking Offset
                OffSetX += 50
                OffSetY += 220
            End If
        ElseIf Func = "Plus" Then
            If ZoomVar < 2.4F Then
                ZoomVar += 0.2F
                OffSetX -= 50
                OffSetY -= 220
            End If
        End If
        For X = 0 To MaxGridX
            For Y = 0 To MaxGridY
                GridWorld(X, Y).SX = ((X - Y) * GridWidth / 2) * ZoomVar
                GridWorld(X, Y).SY = ((X + Y) * GridHeight / 2) * ZoomVar
            Next
        Next
        For I = 0 To MaxFactions
            For Q = 0 To MaxUnitsPF
                X = Faction(I).Unit(Q).TileX
                Y = Faction(I).Unit(Q).TileY
                Faction(I).Unit(Q).X = ((X - Y) * GridWidth / 2) * ZoomVar
                Faction(I).Unit(Q).Y = ((X + Y) * GridHeight / 2) * ZoomVar
                Faction(I).Unit(Q).TargetX = Faction(I).Unit(Q).X
                Faction(I).Unit(Q).TargetY = Faction(I).Unit(Q).Y
            Next
        Next
    End Sub

Almost done! Our additions and changes to our rendering algorithms

            'Drawing Units
            For Y = 0 To MaxFactions
                For X = 0 To MaxUnitsPF
                    If Faction(Y).Unit(X).ID <> 0 Then
                        If X = UnitSelected And Y = 0 Then
                            'Here we draw the points around the unit where it can move (if mouse is over one of them it's drawn with a different color
                            For Z = 0 To 7
                                sRect = New Rectangle((11 + Z) * 64, 392, 64, 56) 'Y * 56
                                dRect = New Rectangle(GridWorld(Faction(Y).Unit(X).TileToM(Z).X, Faction(Y).Unit(X).TileToM(Z).Y).SX + OffSetX, GridWorld(Faction(Y).Unit(X).TileToM(Z).X, Faction(Y).Unit(X).TileToM(Z).Y).SY + OffSetY - 24 * ZoomVar, 64 * ZoomVar, 56 * ZoomVar)
                                If Z = UnitSelectedMouseHover Then
                                    DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 0, 0, 0.6)
                                Else
                                    DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 1, 1, 0.6)
                                End If
                            Next
                            'Here we draw tile animation underneath our selected unit
                            sRect = New Rectangle((UnitSelectF \ 4) * 64, 392, 64, 56) 'Y * 56
                            dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX, Faction(Y).Unit(X).Y + OffSetY - 24 * ZoomVar, 64 * ZoomVar, 56 * ZoomVar)
                            DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 1, 1, 0.6) 'GridWorld(Faction(Y).Unit(X).TileX, Faction(Y).Unit(X).TIleY).Z
                        End If
                        'Drawing units
                        sRect = New Rectangle(Faction(Y).Unit(X).ID * 64, (Faction(Y).Unit(X).Frame \ 4) * 56, 64, 56) 'Y * 56
                        dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX, Faction(Y).Unit(X).Y + OffSetY - 24 * ZoomVar, 64 * ZoomVar, 56 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1472, 448, 1, 1, 1, 1, 1, 1) 'GridWorld(Faction(Y).Unit(X).TileX, Faction(Y).Unit(X).TIleY).Z
                    End If
                    Unit_M(X, Y)
                Next
            Next
            If UnitSelectF < 40 Then 'Frames for tile underneath (These our divided by 4 to get our actual frame since we have 10 frames)
                UnitSelectF += 1
            Else
                UnitSelectF = 3
            End If

That was it, a long post! You should have the following result when you implement the above to our strategy game.

On our Strategy Struct we created the code for spawning units, checking their nearby available to move tiles, handlers for mouse moving and mouse clicking and a simple algorithm to move our unit to the target tile. (this one gets called in our render code at this line : Unit_M(X, Y))

We updated our Grid module for calculation for Field of view and fog of war, added handlers that connect mouse move and click between modules.

And lastly we updated our rendering code to draw our unit with it's frames and the tile overlays for selecting target.

I know it was a long post with lots of coding lines so if you have questions about something just comment below!

Comments