This is another tutorials on the Empire game. If you've found this article first, please see part one. Here's more information about the Empire Game.
- Link to Empire Design Spec.
The sources and compiled code plus all supporting dlls and graphic files can be downloaded here. To run the generator unzip everything in the binaries into one folder.
- Download Source Code File (Zip).
- Download binaries for Scroll (Exe + SDL dlls + graphic files) zipped up.
Note, if you are creating your own SDL project (for these sources in Visual Studio 2010 or Visual C++ Express 2010), then you should see How to setup Visual Studio 2010/Visual C++ 2010 Express with SDL.
As part of the Empire game development, we saw in in the Empire Map Generator, how to generate a game map. In this tutorial, we'll produce a scrolling hex map from that generated map. In the next tutorial we'll add armies to it.
Although slightly nerdy through the association with hex based wargames, hexagons provide a better method of handling unit movement. With squares, units move about 1.414 times faster diagonally (square root of 2) compared to horizontal or vertical movement. It's not as bad with hexes though they do come in two orientations- horozontal runs and vertical runs. The image at the top of the page shows them.
At first glance it can appear complicated to map a hex grid onto a 2D cartesian grid as each location in that cartesian grid is surrounded by 8 locations. In a hex grid, each hex location is surrounded by six hexes. The trick is to think of a hex grid as a brick wall. Start with the bricks all lined up on top of each other (not how you build brick walls!). So each brick is surrounded by eight bricks. Now slide each alternate row half a brick to the right. Voila you have a brick wall where each brick is now surrounded by six bricks. In the example below I've slid the 2nd row half a brick to the right.
That's for a horizontal run. Note how the top left and bottom left bricks now are no longer next to the center brick. For a vertical run, you slide the alternate columns vertically half a brick; not recommended for reral brick walls.
We'll use the horizontal run as it's easier and the graphics I have (provided by an artist friend Lee Cawley from Manchester England back in 1993) are oriented horizontally. I've got the original set where each hex is 32 pixels wide by 34 tall and a second set where I've just doubled them up. Not all of the graphics are used, the characters from the map.txt file are mapped to the graphics in the LoadMap() function.
Scrolling the Map
To scroll the map we have to redraw it quickly enough. There are two ways to do this.
- Pre-render the whole map into a surface then just blit the screen size portion of this rendered map surface onto the display surface.
- Render the apropriate size into the screen surface.
By rendering I mean take the map structure then use the map chars with masking to output the hexes at the right places. For an 80 x 50 map, this is approx 52MB for the whole surface which is a bit big amd would need re-rendering when a unit moved. Whereas for a map area of say 1024 x 768 this equates to 31 hexes across x 28 down and that's much more manageable.
Note the effective hex of each hex row is 25, despite the smallest hex size being 34. The difference is because the rows overlap.
The Vertical Scroll Problem
Without a simple fix, when the map is scrolled vertically (use the cursor keys to try it) the map characters seem to jump on each row. This is because of the 2D -> Hex transformation. Going from eight surrounding chars to six. The easiest answer is to scroll by 2. But to help you understand the problem, here's what happens. Consider this section of the 2D grid.
a b c d e f
g h i j k l
m n o p q r
s t u v w x
That's how it's held in memory. But when we display it as hexes, we're displaying this:
a b c d e f
g h i j k l
m n o p q r
s t u v w x
So let's look at the hex o which is next to h and i above it, n and p either side and t and u beneath it. Likewise p has i and j above it, o and q either side and u and v below.
Now let's scroll the map down 1 and look at i and j the two hexes that were directly above o and p in the 2D layout. Now i has c,d above, h and j either side and o,p below. The six hexes shown in our 2D grid went from
n o p
h i j
Notice that t and u were not immediately below op nor were h i blow c,d in the 2D layout. Like wise for location p which scrolls down to j, locations u,v below p scroll down to p, q. It's difficult to put into words but if you want to see it happen, just change the lines in function ProcessKey() that alter MoveMapy=2 and -2 to 1 and -1 or just press the t key which scrolls the map by 1 in both axes. To make it easier to see, limit the vertical scrolling to one row per keypress by adding MoveMapy=0; to the end of function ScrollMap().
Notes on How It Works
Smooth Scrolling. This is done by MoveMapx and MoveMapy variables which are added to the hx and hy in ScrollMap() and the map redrawn in RenderMap(). These are set to 1,-1 (for MoveMapx) and -2,2 for MoveMapy in the ProcessKey() function and both are cleared when the SDL_KEYUP event is received in the GameLoop() function. This line:
while ( SDL_GetTicks()- tickcount < 17); // delay it to ~60 fps
Frame limits the game to a maximum of 59 frames per second. Although this loop does nothing, in the future we'll make use of this "idling time" to do light weight AI computations.
This is pretty straightforward and uses the map.txt generated from the map generator; I added one city into it.. If you're wondering why land and mountain chars are selected using Random(4), it's because there are 4 hex images for each and they should be fixed. If the chars were picked randomly every time the map was scrolled it would not look good.
Using the Mouse
The game needs mouse interaction, so it makes sense to also have the map draggable. The flag dragmap is set when the right mouse button is held down ad the mouse moved. SDL already provides the mouse relative movement, which I hold in mousexmove and mouseymove so the SDL_MOUSEMOTION event is used to set MoveMapx and MoveMapy. Experimenting I found dividing the relative motions by -10 a good fit. If you prefer a different value be sure to &= the MouseMovey with 254 to avoid the vertical scroll problem.
If you prefer the map to scroll against the mouse move, change the divisor to 10.
That completes this tutorial. In the next one I'll add units and selecting them, the shroud layer, SDL mouse cursors (drag cursor, unit selection etc) plus a few other features.
- Want to know more about SDL?
I've published this tutorial with a slight bug in the code. After working for ages, something I've done has broken the mouse scrolling. The debug values for mousexmove and mouseymove show enormous values. As soon as I've fixed it, I'll update the source files.
- Want to see more games programming tutorials and articles? Visit games programming.