Strategy, custom resolution, creating buttons, building a city (Post #17 / Strategy game #8)

On the last post before the sprite sheet overhaul we spawned our settlers on the map, so it's only logical for this post to be about creating a city, addressing some custom resolution issues and creating buttons for our game.

Moving from fixed resolution to custom resolution

  While I'm writing this post, I realize that I created my UI sheets having in mind my monitor's resolution which is 1920 x 1080, so I didn't actually calculate different monitor sizes. What does that mean? Well let's say I run the application on my laptop which has a resolution of 1280 x 720 then our UI will look all weird, or a part of it will probably be drawn out the screen. We need to fix that if we want other things to work proper (like our buttons. Our tiles and mouse clicking will run proper though since we calculate everything based on camera zoom in/out - or a scaling float that doesn't care about screen resolution).

The problem can be seen on the image above (UI is off screen). So let's create a public float which will hold our screen to drawing ratio or just add the equation straight to the code, and we go from there to changes and implementing buttons.

    Public ScreenWidth As Integer = F_New.GLHandler.Width 'Where F_New is the form my GLControl is on
    Public ScreenHeight As Integer = F_New.GLHandler.Height 'If you are running a console application these are Screen.PrimaryScreen.Bounds .Width and .Height

Let’s take for example the sidebar to create our custom ration. 

It was created for a screen of 960 x 540 (real size) and we double it’s projection size on our screen since we are running our game on a 1920 x 1080 size screen. Till now we use a ratio of W = 1920/960 and H = 1080/540 which is x2 and x2 for width and height.
Now, what if our target screen is let’s say 1220 x 700? I know there is no such screen resolution but maybe someone runs it on a custom GLControl size.
We need a custom ration of W = 1220/960 and H = 700/540 so instead of multiplying by 2 we'll multiply by roughly 1.27 for width and 1.29 for height.

     Public UISizeW As Integer = 960
    Public UISizeH As Integer = 540

So wherever we have *2 on our drawing UI code we change it to the following for width and height

            'Top Border
            sRect = New Rectangle(5, 8, 140, 5)
            dRect = New Rectangle(0, 0, ScreenWidth - 140 * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)

            'Bottom Border
            sRect = New Rectangle(5, 8, 140, 5)
            dRect = New Rectangle(0, (ScreenHeight - sRect.Height * (ScreenHeight / UISizeH)), ScreenWidth - 140 * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)

            'Main UI Screen
            sRect = New Rectangle(140, 8, 140, 540)
            dRect = New Rectangle(ScreenWidth - sRect.Width * (ScreenWidth / UISizeW), 0, sRect.Width * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)

            'Left border
            sRect = New Rectangle(0, 8, 5, 540)
            dRect = New Rectangle(0, 0, sRect.Width * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)

'Here is the minimap drawing code

            'Main UI Overlap
            sRect = New Rectangle(0, 8, 140, 540)
            dRect = New Rectangle(ScreenWidth - sRect.Width * (ScreenWidth / UISizeW), 0, sRect.Width * (ScreenWidth / UISizeW), sRect.Height * (ScreenHeight / UISizeH))
            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)

Here you'll notice that the sidebar and the borders are drawn proper but the minimap is not.

 Public GridMiniSizeW As Single = 1
    Public GridMiniSizeH As Single = 1

We split the GridMiniSize variable into W and H ones and on our Public Sub GridCalls(ByVal Func As String) we change these to the following

GridMiniSizeW = (132 * (ScreenWidth / UISizeW)) / MaxGridX
            GridMiniSizeW = (132 * (ScreenHeight / UISizeH)) / MaxGridX

We also change the code for drawing the mini-map to the following.

            'DRAWING MINI-MAP
            For X = 0 To MaxGridX
                For Y = 0 To MaxGridY
                    If GridWorld(X, Y).FOV = 1 Then
                        sRect = New Rectangle(GridWorld(X, Y).ID * 8, 0, 8, 8)
                        'dRect = New Rectangle(ScreenWidth - 140 * 2 + 4 * 2 + X * GridMiniSize, 5 * 2 + Y * GridMiniSize, GridMiniSize, GridMiniSize)
                        dRect = New Rectangle(ScreenWidth - 140 * (ScreenWidth / UISizeW) + 4 * (ScreenWidth / UISizeW) + X * GridMiniSizeW, 5 * (ScreenHeight / UISizeH) + Y * GridMiniSizeH, GridMiniSizeW, GridMiniSizeH)
                        If (GridWorld(X, Y).SX + OffSetX) > -GridWidth And (GridWorld(X, Y).SX + OffSetX) < (ScreenWidth - 120) And (GridWorld(X, Y).SY + OffSetY) >= -GridHeight And (GridWorld(X, Y).SY + OffSetY) < ScreenHeight + 40 Then
                            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 1, 1, 1, 1)
                        Else
                            DrawTexturedQuad(sRect, dRect, 800, 548, 3, 1.0F, 0.4, 0.4, 0.4, 1)
                        End If
                    End If
                    If X = GridPX And Y = GridPY Then
                        DrawQuad(ScreenWidth - 140 * 2 + 4 * 2 + X * GridMiniSizeW, 5 * 2 + Y * GridMiniSizeH, GridMiniSizeW, GridMiniSizeH, 1.0F, 1.0F, 1, 0.4, 1)
                    End If
                Next
            Next

 Creating a button and making it build a city from our settler

Before we jump into creating a city we'll need to have some buttons in order to tell our unit what to do (that's why I didn't go into changing UI again - this post will have lots of additions to it). So I'll implement code that will handle buttons on the screen, and I'll create buttons for disposing our unit, passing it's turn, special unit function (if it has one) and get into a state of defence.

I'm starting with creating buttons cause we'll have a lot of them from now on. Now what's the logic behind a button in an openGL window?

  • We need to create a sub to handle a call to it from a mouse_down event
  • That sub will check where the mouse clicked and accordingly pass it to a proper result
  • Since we'll have cities and settings windows or whatever else we want we'll have to create a variable which will hold which window state our game is in (example : diplomacy menu is open so the rest are closed)

 How will our button structure work?

Now in order for our buttons to work proper we'll use the 0-1 float system of openGL. What does that mean? Since I created my UI in a 1:1 resolution of 960x540 as I explained on the resolution above, I'll calculate button positions based on that scale and automatically transform them into my actual screen resolution based on that float.

Let's create our special function button (It will spawn a city from a settler)


So on a 960x540 resolution, that button is at X = (857/960) = 0.859 and Y = (467/540) = 0.942 with Width (15/960) and Height (15/540)
Forward onto the code, I will build the city while creating the first button of the game. Let's go step by step in this process.
 
Our button structure and our button sub
  • You'll see on our button sub which we'll call at application start up we create our buttons based on our 960x540 and it translates them to floats right after.
  • On our hover we find which of the buttons we created we hover over which we'll call on our mouse_hover event
  • And on our click what happens when each button is clicked, for now it checks if we are over our build city button and we have a settler as current unit on the map
 
     Public Structure NullButtons
        Dim Text As String
        Dim X As Single
        Dim Y As Single
        Dim Width As Single
        Dim Height As Single
        Dim Enabled As Int16 'Specifies if it's enabled
        Dim UIState As Integer 'Specifies in which UI windows state it's enabled
    End Structure

    Public SBtnNumber As Integer = 20 'Maximum number of buttons
    Public SButton(SBtnNumber) As NullButtons 'Our buttons array
    Public SUIWindow As Integer 'Which UI window is open
    Public SButtonHover As Integer 'Which button is our mouse hovering over

    Public Sub GameButtons(ByVal Func As String)
        Dim I, Q As Integer

        If Func = "CREATE" Then
            SButton(1).Text = "Special unit function" 'This will be for a button description which we'll create later on
            SButton(1).Enabled = 1
            SButton(1).X = 857 : SButton(1).Y = 467
            SButton(1).Width = 15 : SButton(1).Height = 15
            For I = 0 To SBtnNumber
                SButton(I).X /= 960 : SButton(I).Width /= 960
                SButton(I).Y /= 540 : SButton(I).Height /= 540
            Next
        ElseIf Func = "HOVER" Then
            Q = 0
            For I = 0 To SBtnNumber 'Looping through our button array
                'Now checking if our Mouse X,Y (translated also unto floats) are inside any button's borders (X,Width - Y,Height)
                If (MouseX / ScreenWidth) >= SButton(I).X And (MouseX / ScreenWidth) <= (SButton(I).X + SButton(I).Width) And (MouseY / ScreenHeight) >= SButton(I).Y And (MouseY / ScreenHeight) <= (SButton(I).Y + SButton(I).Height) Then
                    If SButton(I).Enabled = 1 Then
                        Q = I 'Found that X,Y is over a button
                    End If
                End If
                If Q <> 0 Then 'A button was found - 0 will assign to no button and our array (0) will remain empty
                    SButtonHover = Q
                    Exit For
                Else
                    SButtonHover = 0
                End If
            Next
        ElseIf Func = "CLICK" Then
            If SButtonHover = 1 Then 'If the mouse is over Special unit function
                If Faction(0).Unit(UnitSelected).ID = 1 Then 'If that unit is a settler -> create a city
                    For I = 0 To MaxCitiesPF
                        If Faction(0).City(I).Enabled = 0 Then
                            City_M("CREATE", I, 0, Faction(0).Unit(UnitSelected).TileX, Faction(0).Unit(UnitSelected).TileY)
                            Exit For
                        End If
                    Next
                End If
            End If
        End If
    End Sub


And a city general sub which gets called above on our click (City_M. I add this now just because the reference for it is above.)
 
    Public Sub City_M(ByVal func As String, ByVal ID As Integer, ByVal FactionN As Integer, ByVal X As Integer, ByVal Y As Integer)
        If func = "CREATE" Then 'Create a simple city
            Faction(FactionN).City(ID).Enabled = 1
            Faction(FactionN).City(ID).Name = "Test City"
            Faction(FactionN).City(ID).TileX = X
            Faction(FactionN).City(ID).TileY = Y
            Faction(FactionN).City(ID).Population = 50
            Faction(FactionN).City(ID).Prosperity = 1
            Faction(FactionN).City(ID).Luxuries = 0
            Faction(FactionN).City(ID).Unemployment = 0
            Faction(FactionN).City(ID).Civility = 100
            Faction(FactionN).Unit(UnitSelected).ID = 0
        End If
    End Sub


Don't forget to add this GameButtons("CREATE") when we start our application
And references to these on our Mouse_Hover and Mouse_Click events
GameButtons("HOVER") and GameButtons("CLICK")

 Buttons are almost done, now I added the following rendering code to our UI drawing
If SButtonHover <> 0 Then
                If SButton(SButtonHover).UIState = SUIWindow Then 'If the button we hover over is at the same UI window
                    sRect = New Rectangle(SButton(SButtonHover).X * ScreenWidth, SButton(SButtonHover).Y * ScreenHeight, SButton(SButtonHover).Width * ScreenWidth, SButton(SButtonHover).Height * ScreenHeight)
                    DrawQuad(sRect.X, sRect.Y, sRect.Width, sRect.Height, 1.0F, 0.5F, 0.35F, 0.4F, 0.65F)
                End If
            End If

 Which gives us the following result (draws a rectangle over the button we hover over)

Moving forward to the cities. We create a new structure as shown below
 
    Public Structure CitiesStruct
        Dim Enabled As Int16
        Dim TileX As Integer
        Dim TileY As Integer
        Dim Name As String
'The next are going to be our city's extra information
        Dim Population As Integer
        Dim Prosperity As Integer
        Dim Luxuries As Integer
        Dim Unemployment As Integer
        Dim Civility As Integer
    End Structure
 
Inside our faction structure I declared this Dim City() As CitiesStruct

Somewhere on our module we declare Public MaxCitiesPF As Integer = 50 'Cities cap (For each faction)

All the above are done the exact same way we created our units in our faction, so we'll redim our cities inside our Faction_F sub just after we redim our unit we continue to ReDim Faction(I).City(MaxCitiesPF)

The following code goes into our Fog of war calculation right after calculating it for our units

  For Q = 0 To MaxCitiesPF
            If Faction(0).City(Q).Enabled = 1 Then
                For Y = (Faction(0).City(Q).TileY - 3) To (Faction(0).City(Q).TileY + 3)
                    For X = (Faction(0).Unit(Q).TileX - 3) To (Faction(0).Unit(Q).TileX + 3)
                        If X >= 0 And X <= MaxGridX And Y >= 0 And Y <= MaxGridY Then
                            If GridWorld(X, Y).FOV = 0 Then GridWorld(X, Y).FOV = 1
                            GridWorld(X, Y).FOW = 1
                        End If
                    Next
                Next
            End If
        Next 

And lastly our city rendering code (Faction colors is an array I created for testing some variations on the RGB float you can just remove it for now)

'Drawing Cities
            For Y = 0 To MaxFactions
                For X = 0 To MaxCitiesPF
                    If Faction(Y).City(X).Enabled = 1 Then
                        DrawText(Faction(Y).City(X).Name, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX - 16 * ZoomVar, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 20 * ZoomVar, 1.1 * ZoomVar, 1, 0.21 - FactionColors(Y).R, 1 - FactionColors(Y).G, 0 + FactionColors(Y).B, 1)
                        Dim CityPopulation As Integer = (51 + (Faction(Y).City(X).Population \ 20000)) '51 refers to our first city tile on our tilesheet, and every 20000 population it will become bigger
                        TexY = CityPopulation \ 17
                        TexX = CityPopulation - TexY * 17
                        sRect = New Rectangle(TexX * 64, TexY * 40, 64, 40)
                        dRect = New Rectangle(GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SX + OffSetX, GridWorld(Faction(Y).City(X).TileX, Faction(Y).City(X).TileY).SY + OffSetY - 8 * ZoomVar, 64 * ZoomVar, 40 * ZoomVar)
                        DrawTexturedQuad(sRect, dRect, 1088, 160, 0, 1.0F, 1, 1, 1, 1)
                    End If
                Next
            Next

That was it! Now we have buttons and our first city.



Comments