Strategy game, Turn based system and unit movement restrictions (Post #21 / Strategy game #12)

Now that we can build units, buildings and research technologies, we'll focus a bit more on the actual map of the game and how it's refreshed through turns.

➤ Creating a game system based on turns

 I changed a bit the UI sprite sheet to include some animations for when "End Turn" button is pressed. I'll include the updated sheet at another post since the difference is some sprites that resemble status bars and a sequence for when clicking the button. Now I added some variables that will hold end turn steps, which when updated will show the process update on the screen.

     Public TurnChange As Boolean = False 'A switch between states
    Public TurnSteps As Int16 = 0 'A counter for turn sequence
    Public TurnStepsL As Int16 = 0 'A Long counter for turn sequence

 Let's first disallow player from clicking on the screen while enemy turn isn't finished. (I will just add a clause on GLMouseDown Sub)

    Public Sub GLMouseDown(ByVal Btn As MouseEventArgs) 'Just call this from you clicking on screen event
            If TurnChange = False Then 'Addition
            If Btn.Button = MouseButtons.Left Then
                Unit_F("MOUSE_MOVE_UNIT", UnitSelected)
                GameButtons("CLICK")
            End If
        End If
    End Sub

 And at the following rendering code part (which draws the arrows and the select box under our selected unit, I added the same clause).

If TurnChange = False Then 'Addition
                                '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
                                    If Faction(Y).Unit(X).TileToM(Z).X >= 0 And Faction(Y).Unit(X).TileToM(Z).X <= MaxGridX And Faction(Y).Unit(X).TileToM(Z).Y >= 0 And Faction(Y).Unit(X).TileToM(Z).Y <= MaxGridY Then
                                        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
                                    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

As you can see, now if TurnSteps is 0 then you can do stuff, If not then you have to wait until enemy turn is finished. 

Reminder : When we click on our "End Turn" button, it calls this Faction_F("END_TURN")

ElseIf func = "END_TURN" Then 'I removed all previous code from this and just added this
            TurnChange = True

ElseIf func = "END_TURN_STEPS" Then 'This is our new code part which we'll add our previous code with some more steps.

'For now add the code we had on END_TURN here.

 And at the end of Faction_F("REFRESH_FACTIONS") add the following code

If TurnChange = True Then
                If TurnStepsL < 89 Then
                    TurnStepsL += 1
                Else
                    TurnChange = False
                    TurnStepsL = 0
                    Faction_F("END_TURN_STEPS")
                End If
                TurnSteps = TurnStepsL \ 8
            End If

What we are trying to do here is to create a sequence that will get called till it's finished, and it will call another code while it's enabled. (added it at refresh_factions which is a call that's called all the time.

(In order to check how this works I added the visuals in our sprite sheet that I said at the start of the post which we'll add now)

Add the following inside our UI draw after drawing the side bar

'Turn button
            sRect = New Rectangle(857, 0 + 26 * TurnSteps, 130, 26)
            dRect = New Rectangle(825 * (ScreenWidth / UISizeW), 509 * (ScreenHeight / UISizeH), sRect.Width * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 1020, 548, 3, 1.0F, 1, 1, 1, 1)

The above code will give us the following visual result.


So our turn is now finished after the animation effect has played.

Another thing we'll jump into now is adding visuals for Unit health and available movement.

➤ Unit visuals (Health and Movement bars)

For these property bars I created specific bars in the sprite sheet for 100%, 66%, 33% and 0% of each. Now there will correspond to a system of min = 0 and max = 3. I did a max of 3 tiles to move and a max of 3 to health to keep the battles and movement to a small amount each turn. Now when later on we create the library from which our units will get these values assigned, they will get picked from a pool where let's say a primitive warrior will have 1 tile to move each turn and a warrior on a horse will have 3 tiles to move each turn. And they will show over our unit like the image below.

For these I just added (After 'Drawing units code inside the faction & unit loop part of our rendering code) the following

'Health & Movement bars
                        sRect = New Rectangle(806, 297 + (3 - Faction(Y).Unit(X).Health) * 3, 51, 4)
                        dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX, Faction(Y).Unit(X).Y + OffSetY - 34 * ZoomVar, 51 * ZoomVar, 4 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1020, 548, 3, 1.0F, 1, 1, 1, 1)

                        sRect = New Rectangle(806, 310 + (3 - Faction(Y).Unit(X).TurnPoints) * 3, 51, 4)
                        dRect = New Rectangle(Faction(Y).Unit(X).X + OffSetX, Faction(Y).Unit(X).Y + OffSetY - 30 * ZoomVar, 51 * ZoomVar, 4 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1020, 548, 3, 1.0F, 1, 1, 1, 1)

Which are just calls to our sprite sheet based on unit's health and turn points variables.

Now let's create our library and assign our units four properties (Health, Turn Points, Attack, Defence). Since we have already a library in our structure Library_Constructions_S in which we hold the creation of our units let's go in and add these properties in there. I declared them as single, so go ahead and change these references on our faction.units as single too (except turn points).

    Public Structure Library_Constructions_S
        Dim Name As String
        Dim UnlockID As Integer 'An index which when called will unlock the unit
        Dim Unlocked As Integer 'Is the unit or structure unlocked
        Dim Turns As Integer
        'Additional variables
        Dim Health As Single
        Dim TurnPoints As Int16
        Dim Attack As Single
        Dim Defence As Single
    End Structure

And add this pre-calculated values code inside our library initialize sub (Game_Library_Construct)

I = 1 'Settler
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 0.1F : Lib_Construct_S(I).Defence = 0.5F
            I = 2 'Worker
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 0.2F : Lib_Construct_S(I).Defence = 0.4F
            I = 3 'Warriors
            Lib_Construct_S(I).Health = 1 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 0.6F : Lib_Construct_S(I).Defence = 0.7F
            I = 4 'Archers
            Lib_Construct_S(I).Health = 1 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 0.7F : Lib_Construct_S(I).Defence = 0.3F
            I = 5 'Pikemen
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 0.5F : Lib_Construct_S(I).Defence = 0.5F
            I = 6 'Cavalry
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 0.4F : Lib_Construct_S(I).Defence = 0.9F
            I = 7 'Catapult
            Lib_Construct_S(I).Health = 1 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 1.2F : Lib_Construct_S(I).Defence = 0.3F
            I = 8 'Longbowmen
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 1 : Lib_Construct_S(I).Attack = 1.0F : Lib_Construct_S(I).Defence = 0.5F
            I = 9 'Knights
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 0.9F : Lib_Construct_S(I).Defence = 0.9F
            I = 10 'Riflemen
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.3F : Lib_Construct_S(I).Defence = 0.6F
            I = 11 'Cannon
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.5F : Lib_Construct_S(I).Defence = 0.5F
            I = 12 'Infantry
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.4F : Lib_Construct_S(I).Defence = 0.6F
            I = 13 'Parachuters
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.5F : Lib_Construct_S(I).Defence = 0.3F
            I = 14 'Light Vehicle
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.4F : Lib_Construct_S(I).Defence = 1.0F
            I = 15 'Marines
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.5F : Lib_Construct_S(I).Defence = 0.6F
            I = 16 'AA Vehicle
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.4F : Lib_Construct_S(I).Defence = 1.2F
            I = 17 'Tank
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.7F : Lib_Construct_S(I).Defence = 1.5F
            I = 18 'Helicopter
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.6F : Lib_Construct_S(I).Defence = 0.8F
            I = 19 'Airplane
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.0F : Lib_Construct_S(I).Defence = 1.0F
            I = 20 'Fighter Jet
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 2.0F : Lib_Construct_S(I).Defence = 1.3F
            I = 21 'Ship
            Lib_Construct_S(I).Health = 2 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 0.8F : Lib_Construct_S(I).Defence = 0.8F
            I = 22 'Warship
            Lib_Construct_S(I).Health = 3 : Lib_Construct_S(I).TurnPoints = 2 : Lib_Construct_S(I).Attack = 1.7F : Lib_Construct_S(I).Defence = 1.5F

Now whenever we spawn a unit (so we'll jump onto our CREATE_UNIT sub), we'll also assign these values like shown below.

Where we had only this line Faction(FactionID).Unit(Q).TurnPoints = Y, we'll now have these lines

Faction(FactionID).Unit(Q).TurnPoints = Lib_Construct_S(UnitID).TurnPoints
        Faction(FactionID).Unit(Q).Health = Lib_Construct_S(UnitID).Health
        Faction(FactionID).Unit(Q).Attack = Lib_Construct_S(UnitID).Attack
        Faction(FactionID).Unit(Q).Defence = Lib_Construct_S(UnitID).Defence

Do the same for this code when we spawn our first settler (Faction_F(Create_Factions))

Faction(I).Unit(0).TurnPoints = Lib_Construct_S(1).TurnPoints
                Faction(I).Unit(0).Health = Lib_Construct_S(1).Health
                Faction(I).Unit(0).Attack = Lib_Construct_S(1).Attack
                Faction(I).Unit(0).Defence = Lib_Construct_S(1).Defence

 Let's test our new code.

When I created a new unit, it spawned with the values that it got assigned from our library. You could create another two bars or other visuals to represent it's attack and defense values.

➤ Unit movement - restrictions

Now we'll restrict our unit from moving more than it's turn points, and every turn we'll refresh it's turn points back to full from the library values.

Inside our Faction_F("END_TURN_STEPS")

'Turn Points re-assign at the beginning of turn
            For I = 0 To MaxFactions
                For Q = 0 To MaxUnitsPF
                    If Faction(I).Unit(Q).ID > 0 Then
                        Faction(I).Unit(Q).TurnPoints = Lib_Construct_S(Faction(I).Unit(Q).ID).TurnPoints
                    End If
                Next
            Next

 Inside our Unit_F("MOUSE_MOVE_UNIT")

Just add inside our clause this line at the end Faction(0).Unit(UnitSelected).TurnPoints -= 1. Now everytime you click on a tile to move (If the unit has enough turn points it will move and lose one)

We can now move our units (X times or turn points / turn)

Another restriction we'll introduce here is for ground units not passing through water tiles and for sea units not passing through ground tiles.

Add to UnitStruct the following

Dim TileToMA() As Boolean 'Here we'll check if unit can move to this TileToM(Index)

 And ReDim Faction(I).Unit(Q).TileToMA(7) inside Faction_F("CREATE_FACTIONS") under TileToM redim

And the rest code to the following parts

Unit_F(,) Inside "MOVE_CALCULATE" After the tile position calculations add the following loop, which will check what kind of tiles are the available to move on ones and flag them as Can move to or Can't move to depending on what unit is the move calculated for.

For Q = 0 To 7
                If GridWorld(Faction(0).Unit(ID).TileToM(Q).X, Faction(0).Unit(ID).TileToM(Q).Y).ID <= 3 Then 'Sea tiles
                    If Faction(0).Unit(ID).ID >= 20 Then 'If it's a sea or flying unit
                        Faction(0).Unit(ID).TileToMA(Q) = True
                    Else 'If it's a ground unit
                        Faction(0).Unit(ID).TileToMA(Q) = False
                    End If
                Else 'ground tiles
                    If Faction(0).Unit(ID).ID >= 22 Then 'If it's a sea unit
                        Faction(0).Unit(ID).TileToMA(Q) = False
                    Else 'If it's a ground or flying unit
                        Faction(0).Unit(ID).TileToMA(Q) = True
                    End If
                End If
            Next

Now let's put this newly assigned boolean to use.

On  the same sub under "MOUSE_HOVER_UNIT" change the clause to this

If GridPX = Faction(0).Unit(ID).TileToM(Q).X And GridPY = Faction(0).Unit(ID).TileToM(Q).Y And Faction(0).Unit(ID).TileToMA(Q) = True Then

So it takes into account our boolean when checking the mouse hovering over tile.

Let's also update our rendering code too in order to hide the available positions if they are incompatible with our unit type.

Inside Draw_Calls under our Draw Unit Loop change the clause to this

If Faction(Y).Unit(X).TileToM(Z).X >= 0 And Faction(Y).Unit(X).TileToM(Z).X <= MaxGridX And Faction(Y).Unit(X).TileToM(Z).Y >= 0 And Faction(Y).Unit(X).TileToM(Z).Y <= MaxGridY And Faction(Y).Unit(X).TileToMA(Z) = True Then 'The addition is Faction(Y).Unit(X).TileToMA(Z) = True


Now a ground unit can only move on ground tiles and a sea unit on water tiles, while a flying unit can move on both. (We also prevent our rendering code to show these restricted tiles as shown above in the image)

 ➤ Small fix

One thing that was off that I found in the code was when the hovering tile was drawn on the minimap which wasn't updated on different resolutions and was shown way off the minimap. The code now is like shown below. (Inside drawing minimap loop)

If X = GridPX And Y = GridPY Then
                        dRect = New Rectangle(ScreenWidth - 140 * (ScreenWidth / UISizeW) + 4 * (ScreenWidth / UISizeW) + X * GridMiniSizeW, 5 * (ScreenHeight / UISizeH) + Y * GridMiniSizeH, GridMiniSizeW, GridMiniSizeH)
                        DrawQuad(dRect.X, dRect.Y, dRect.Width, dRect.Height, 1.0F, 1.0F, 1, 0.4, 1)
                        'DrawQuad(ScreenWidth - 140 * (ScreenWidth / UISizeW) * 2 + 4 * (ScreenWidth / UISizeW) + X * GridMiniSizeW, 5 * (ScreenHeight / UISizeH) + Y * GridMiniSizeH, GridMiniSizeW, GridMiniSizeH, 1.0F, 1.0F, 1, 0.4, 1) That was the old code
                    End If


Now that most of our movement and value assigning is done, we can move forward to adding enemy movement and creating our battle system.








Comments