• 2D shoot 'em up
SDL2 Rogue tutorial
SDL2 Gunner tutorial
SDL2 Shooter 2 tutorial
SDL2 Widget tutorial
SDL2 Adventure tutorial
— Creating a simple roguelike —
To finish off our game, we're going to add in the final touches - a title screen, some sound and music, and the option to Save and Quit our current run. We'll only be supporting one saved game at a time, so there won't be a load option. If a save game exists, it will automatically be loaded from the title screen.
Extract the archive, run make, and then use ./rogue20 to run the code. You will see a window open, displaying the title screen as above. Press Space (or Escape or Return to continue). If you wait a few seconds, the highscore table will be displayed for a short period, before returning to the title screen again. Play the game as usual. You can press Escape during play to bring up the pause menu, allowing you to adjust the sound and music, and also quit the game. Once you're finished, close the window to exit, or select Save and Exit from the in-game menu.
Inspecting the code
Let's start with looking at the changes to defs.h:
We've added in a new define, to specify the name of our options save file, the file that will contain the volumes of our sound and music. We'll see this in use a bit later on.
structs.h has also been tweaked slightly:
We've added in two new fields to Game - soundVolume and musicVolume, to hold the values of our sound and music volumes, respectively.
Next, we've added in a new file called title.c, which will be used to handle our title screen. We've only got a handful of functions to cover, and none of them are all that complicated.
Starting with initTitle:
initTitle handles setting up the title screen. We're loading two textures - `background`, which will serve as the background image to our title screen and highscore, and `logo`, which is the game's title logo. Both of these are SDL_Textures, so neither are taken from our texture atlas, as they're bigger than our atlas's size (512 x 512). Next, we're setting a variable called logoAlpha to 0. This variable controls the alpha value of our logo, when it fades in. tickVal is used to control the blinking "Press Space" text colour. We're then setting a variable called `timeout` to 7 seconds. This is how long we'll display the title screen before switching over to the highscore table. displayTimer is set to half a second, and is used for the transition period when coming from the dungeon, highscore table, etc. We're then loading and playing our music, before finally setting our `logic` and `draw` delegates.
initTitleView is next:
This function is used when moving from the highscore table, etc. back to the title. We just need to set the displayTimer to half a second, the timeout to 7 seconds, and reset the `logic` and `draw` delegates. This basically a cutdown version of initTitle.
Our `logic` function follows. It's quite simple:
We're making use of the displayTimer variable here, to control how soon we can interact with the title. When displayTimer is 0, we're increasing the value of logoAlpha, to make the logo appear, limiting it to 255. We're also increasing the value of tickVal. Next, we're checking if Space, Return, or Escape have been pressed. If so, we'll be starting the game. We'll clear the user input and then call initDungeon, to either resume the existing game or start a new one.
Lastly, we're decreasing the value of `timeout`, limiting it to 0. When it hits 0, we're calling initHighscoreView, to display the highscores table. So, as you can see, every 7 seconds we'll be moving over to the highscore table display.
The last function to look at is `draw`:
Again, after testing if displayTimer is 0, we're rendering our graphics. We start by drawing the background, by calling `blit` and pressing over `background`. The `blit` function takes an SDL_Texture, rather than an AtlasImage. We're telling the function to draw `background` at 0,0 without being centered. That means it will cover the entire screen. Next, we're using SDL_SetTextureAlphaMod along with `logo` and logoAlpha, to change the alpha value of our logo. logoAlpha starts at 0, but as the value increases, our logo will slowly fade in. Again, we're using our `blit` function for this, since `logo` is an SDL_Texture.
We're next taking the modulo of tickVal and FPS, and then testing if the value is less than half a second. If so, we'll be setting a variable called `c` to 255. Otherwise, it will be 192. `c` is the colour of the "Press Space" text we're drawing. Effectively, we're using tickVal to make it pulse between white and light grey every half a second.
That's our title screen done. We can now look at all the other misc. changes we've made. Starting with initHighscoreView in highscores.c:
Like title.c, we've added in a `timeout` variable, which we're setting to 7 seconds.
If we look the `logic` function, we can see how it's being used:
Again, just like title.c, we're decreasing `timeout` and limiting it to 0. When it hits 0, we're calling initTitleView, to return to the title display.
Moving over to game.c next, we've updated initGame:
We've made an update to load in our options (really just our sound and music volumes). We're first setting game's soundVolume and musicVolume to the maximums (MIX_MAX_VOLUME is 127). We're then testing to see if the options.json file exists. If so, we're going to load it, convert it into JSON, and then extract the values of soundVolume and musicVolume from it, assigning these to game's soundVolume and musicVolume, respectively. We're also using the MIN macro to ensure that our values don't exceed MIX_MAX_VOLUME.
To accompany our options loading, we've also created a saveOptions function:
A simple function - we're storing the values of game's soundVolume and musicVolume into a JSON object and then saving that out to our options.json file.
Moving over to dungeon.c, we've made a few tweaks to support our in-game menu. Starting with initDungeon:
We're setting a variable called `paused` (declared as static within dungeon.c) to 0. This variable controls whether we're display our in-game menu. Also of note is a call to pauseMusic, passing over 0. This function controls whether our music is playing. Passing 1 pauses the music, while 0 resumes it (this function can be found in sound.c). We're also calling a new function named setupWidgets, to create our pause widgets.
The logic function has been tweaked to support the new `pause` variable:
We've wrapped our main game processing code in an if-statement, to test if the game is not paused (`pause` is 0). If it isn't, the game plays out as normal. Otherwise, we're calling doWidgets, passing over "pause" to process our in-game widgets. We're also testing to see if Escape has been pressed, to exit the menu and return to the game (basically, unpause). If so, we're clearing the Escape key, then calling saveOptions to save our settings, and updating `paused` to 0 to continue the game.
`draw` has also been tweaked, to make use of the `paused` variable:
When paused, we're no longer calling drawHud. This is to reduce the text noise on screen when the widgets are present, so that things don't get confusing. At the end of the functon, we're checking if `paused` is set and then drawing a transparent black rectangle across the screen, to dim it, then calling drawWidgets, passing over "pause" to draw the pause widgets.
Our widget setup is handled by setupWidgets. This is a standard widget setup function that we've seen in the past, so we'll instead focus on the two important functions that the widgets use. Starting with `resume`:
This function is called when we select "Resume" from the menu. It simply calls saveOptions and then sets `paused` to 0, so that the game continues.
The other function is `quit`:
This function saves our game and then quits, by calling exit. We don't return to the title screen. Having this in place now allows us to save the game at any time and not just when we change floors.
The very last change we need to make to our game is to tell it to start at the title screen, rather than jumping straight into the dungeon. So, we head over to main.c and update main:
Instead of calling initDungeon, we're now calling initTitle.
And there we have it. 20 simple steps for creating a Rougelike using SDL2. It's been quite a journey, but hopefully you will have found this helpful. The nice thing about this game is that it's fairly easy to throw in some new monsters, weapons, etc. In fact, this tutorial was originally meant to be a lot longer, featuring ranged weapons, more equipment slots, and several different monsters. It could well surface one day as a Director's Cut. You'll know where it find it if so!
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.