• 2D shoot 'em up
SDL2 Rogue tutorial
SDL2 Gunner tutorial
SDL2 Shooter 2 tutorial
SDL2 Widget tutorial
SDL2 Adventure tutorial
— Simple 2D adventure game —
Our dungeon will not be limited to a single screen (although it could be, since it could be designed as a flip screen affair where moving past the bounds of one screen takes you to another). We'll be going the traditional route of scrolling the map as the player moves around. For the most part, the player will always be in the centre of the screen, other than when at the limits of the map. We also want to prevent the player from walking into walls and holes in the ground.
Extract the archive, run make, and then use ./adventure02 to run the code. You will see a window open like the one above, showing the prisoner on a tiled background. Use WASD to move around. Compared with the first tutorial, the dungeon is now three times larger. You will find you can move around a larger area, but are prevented from moving into the hole (black zones) and walls (beige-ish squares). Close the window to exit.
Inspecting the code
We've not had to make too many changes to the code to introduce mapping and collision to the map. We'll start with defs.h:
We've increased the MAP_WIDTH and MAP_HEIGHT to 57 and 30, making it three times larger than the original map. We've also introduced two new defines: MAP_RENDER_WIDTH and MAP_RENDER_HEIGHT. These will be used to determined how many tiles along the x or y axis should be drawn. These values are both the same as the original map size, since these fit prefectly into our screen space. We've also added in a define for TILE_WALL, with a value of 40. This will be used to specify that any tile with a value of 40 or greater should be classed as a wall. We'll see more on this later on.
The single change made to structs.h is to introduce a camera variable into Dungeon. This is an SDL_Point, so just an x and y:
Let's take a look at dungeon.c next. Only the initDungeon funciton has changed here, and that's to use MAP_RENDER_WIDTH and MAP_RENDER_HEIGHT in our render offsets calculation, instead of the MAP_WIDTH and MAP_HEIGHT:
If we didn't change these, our offsets would be all wrong and map would render in the wrong position.
Moving onto map.c, we've again made just a handful of changes. Instead of loading the map data or pre-defining it, we're going to randomly place walls and holes on an otherwise flat map:
In order to keep the map layout the same each time, we're going to seed the random (srand) with a fixed number. After this, we're defaulting all the tiles to TILE_GROUND, but randomly changing the them into a TILE_HOLE or a TILE_WALL, when testing a random. It's crude, but again it's just to populate the map with fixed demo data.
With that done, we can move onto the map drawing. If we examine drawMapTiles we can see that our y and x loops use MAP_RENDER_HEIGHT and MAP_RENDER_WIDTH, instead of drawing the entire map. This is done just to draw a fixed portion of the dungeon map and not the entire thing:
Notice the important change here: we're taking the dungeon camera's x and y values and adding them to the x and y of our loop, and assigning them to two variables: mx and my. We're then using mx and my as the indexes within our map data. This is important to ensure we render the map correctly. The y and x loops will give us numbers from 0 to MAP_RENDER_HEIGHT and MAP_RENDER_WIDTH (10 and 19). Adding the camera's x and y will allow us to pick out the correct tile for our camera position. We then use mx and my to extract the tile and render it if it's not a TILE_HOLE.
We also need to employ the camera's x and y when drawing our entities, as we can see in entities.c:
However, in this case we want to subtract the camera's x and y from the entity's, to ensure everything is correctly centered around the player. We're assigning the result of this subtraction to two variables: x and y, and then using those to carry out the drawing operation as before.
Finally, we'll come to the most important section of this part of the tutorial - testing the player movement and controlling the camera. The player's movement checks and the camera controls are both handled in movePlayer:
movePlayer has had some changes made it to before the assignment of the result to the player's x and y. After clamping the x and y values to the bounds of the map, we then test to see if the tile at those coordinates has a value of equal to or greater than a TILE_GROUND and less than a TILE_WALL. In other words, is the tile walkable by the player. Remember that ground tiles can be anywhere between TILE_GROUND and TILE_WALL - 1 (so, 1 and 39). If so, we're good to assign the x and y to the player.
After that, we're ready to make the camera adjustments. We want to center the camera around the player. So, we assign the camera's x and y values to the player's x and y values. We then subtract half the MAP_RENDER_WIDTH from the camera's x and half the MAP_RENDER_HEIGHT from the y. We finally clamp these values to ensure they never go below 0 and also never greater than the MAP_WIDTH / MAP_HEIGHT less the appropriate render size.
That might sound a bit confusing, so just image that when assigning the camera to the player's x and y, it would means the player would always be drawn in the top left of the screen. We want the player displayed in the center, so we need to subtract half of each of the dungeon's render sizes. This would move the view left by half the dungeon render width and up by half the render height.
As you can see, moving the player around correctly, and controlling and using the camera is pretty easy. Next we'll look at how to load a map, instead of relying on generation.
The source code for all parts of this tutorial (including assets) is available here:
It is also available as part of the SDL2 tutorial bundle (with on-going updates):
If you do not wish to create an itch.io account, you can also purchase the tutorial bundle using PayPal. This method will be slower, however, as it will require manual verification of the transaction.
Share your comments and thoughts below. All comments are anonymous and cannot be edited.