• 2D shoot 'em up
Water Closet ported to PlayStation Vita
The Legend of Edgar 1.35
SDL2 Rogue tutorial
SDL2 Gunner tutorial
— Creating an in-game achievement system —
Now that we've integrated Medals into our gameplay, we should consider that a player might not be able to earn all the Medals in one session; the game could have multiple levels, etc., and also require the player to reach certains goals that might not be obtainable right away. In this part, we'll look at saving our progress.
Extract the archive, run make, and then use ./medals03 to run the code. You will see a window open like the one above, showing a little blue alien and a handful of floating batteries. The goal is to collect all the batteries. Guide the little alien around using the WASD control scheme. Press Tab to show the stats and also the Medals that are on offer (press Tab again to dismiss the display). Collect the batteries that you are able to, and then close the window to save your game. Re-run the game to start collecting batteries again. Notice when you press Tab that your Medal progress and stats are carried over. Continue playing until you have earned all the Medals.
Inspecting the code
We've started by adding in some new stats. Our Medals call for awards given for collecting different battery types, so we've updated defs.h to handle this:
We've now added in three new enums: STAT_FULL_BATTERIES_COLLECTED, STAT_MEDIUM_BATTERIES_COLLECTED, and STAT_LOW_BATTERIES_COLLECTED, to record the stats for the number of full batteries, medium batteries, and low batteries that can be collected.
Next, we've updated structs.h, making changes to the Battery struct:
We've added in a `type` field, that will be used to track the type of battery that this is.
Heading over to batteries.c, we can now see how we've put this to use:
In the respective initBatteryXXX functions, we've set the type of battery that this is. BT_FULL, BT_MEDIUM, and BT_LOW are defined in battery.h. The names correspond to the battery type.
Next, we've updated the `touch` function, to incorporate `type`:
Now, as well as adding to our STAT_BATTERIES_COLLECTED stat when we collect a battery, we're testing the type of battery that we've collected. We're then incrementing game's stats STAT_FULL_BATTERIES_COLLECTED, STAT_MEDIUM_BATTERIES_COLLECTED, or STAT_LOW_BATTERIES_COLLECTED depending on the type of battery this is, by performing a switch against the Battery `type`.
We're continuing to flag the battery as dead and playing a sound, but we've now added in many more stat checks. If STAT_FULL_BATTERIES_COLLECTED is 10, we're calling awardMedal and passing over "batteriesFull10". If STAT_MEDIUM_BATTERIES_COLLECTED is 10, we're calling awardMedal and passing over "batteriesMedium10", and so on. (remember: all these Medals are defined in data/medals.json).
In short, we've added some new stats, that can be incremented and tested in the `touch` function in batteries.c, by checking the battery's `type`.
Now, we should look at how we're persisting our game. If we move over to game.c, we can see we've added in many more functions, to handle loading and saving.
Starting with loadGame:
This is a standard game loading function that we've used in some other tutorials. We're first checking to see if our save game file exists (SAVE_GAME_FILENAME is defined as "save.json" in game.h). If so, we're reading in the data and converting it to JSON, and then calling loadStats and loadMedals, passing over the "stats" and "medals" objects from the JSON to them.
Our loadStats function is quite simple:
For loading our stats, we're looping through each of the child nodes (as `node`) in our JSON (our stats are saved as a JSON array, passed into this function as a variable named `root`), grabbing the name from the node and passing it to our `lookup` function to get its int value (assigning it to a variable called `i`), and then setting the value at the appropriate stat index to the value of "value" in the JSON node. Nothing complicated.
Again, we're looping through all the nodes in the JSON array passed into the function (`root`), again assigning them to a variable called `node`. We're then extracting the "id" field from node and searching for a matching Medal in our Medals linked list. When we find a Medal with an `id` that matches the id from the JSON node, we're updating the Medal's awardDate with the awardDate from the JSON. Basically, when we start up our game, all our Medals will be locked and unearned. This function is updating those unlock dates with those from the save file (note that the value might still be 0!).
Moving on saveGame, this function is quite simple:
Saving is much the same as that found in other tutorials: we're creating our root JSON object, and then adding the stats and medals fields to it, using the results of calls to saveStats and saveMedals. The JSON data is written to saved to SAVE_GAME_FILENAME (save.json).
Moving over to saveStats now:
We're looping through all our stats, creating a JSON object for each, and setting the "name" and "value" fields. Notice how we're making a call to getLookUpName, and passing over "STAT_" and the loop index (`i`) to the function, to translate the stat name into a human-readable format. All the stats are saved into a JSON array called `items` that we're returning at the end of the function.
saveMedals works in a similar way:
For each of our Medals, we're creating a JSON object (`item`), and storing the Medal `id` and awardDate. We're adding `item` to a JSON array (`items`) and then returning it at the end of the function.
In summary, our save game data is storing both our stats and our medal data, so it can be restored when we start the game back up again. This allows us to unlock our Medals at a pace that suits us.
Moving over to stage.c now, where we've made a small update to display the Medals and stats. Starting with initStage:
We've added a static variable called showStats, that we're setting to 0. This variable will be used to control whether we display the stats and medals on screen.
We've then updated `logic`, to handle this variable:
We're testing first if Tab has been pressed. If so, we'll clear the key, and then set showStats to the inverse of showStats (in effect, making it toggle between 0 and 1). We're then testing to see if showStats is 0. If so, we'll call doEntities. The reason for this is because we don't want to process our entities when we're displaying the stats. In effect, the game is paused.
Updates to `draw` follow:
We're now testing to showStats is set and calling drawStats if so.
drawStats itself is quite straightforward:
We're first calling drawRect to darken the screen a bit, to make the text we're about to draw more readable. Next, we're rendering the text for each of our medals, in much the same way as we did in the first part of the tutorial, with the text being displayed in yellow if we've unlocked the Medal.
With the Medals drawn, we're rendering our stats. We're using a for-loop to iterate through them, and sprintf to create the stats text. statText is a static char array in stage.c, with text entries that align with the type of stat (e.g., STAT_BATTERIES_COLLECTED aligns to "Batteries Collected: %d"), etc.
addEntities has been modified somewhat to aid with our demonstration:
We're now creating just 8 batteries in our for-loop, rather than 25. We're also giving low batteries a 50% chance of appearing, medium batteries a 40% chance, and full batteries a 10% chance.
Lastly, we've tweaked init.c, to update initGameSystem:
After everything has been setup, we're calling loadGame, to load our progress back up.
input.c has also seen a one line addition:
Now, when the SDL_QUIT event is found, we're calling saveGame before `exit`. So, when we close the window, our game will be saved.
And, there we go. We can now save and reload our Medal progress, so that it will carry across between game sessions. As you can no doubt see, this is quite similar to saving regular game data.
What would be exciting now is to put our Medals into a full game, so we can see how different aspects of the Medal process works. In our next part, we'll look at how Medals have been implemented in a game called UFO Rescue!
The source code for all parts of this tutorial (including assets) is available for purchase:
It is also available as part of the SDL2 tutorial bundle:
If you do not wish to create an itch.io account, you can also purchase the tutorial bundle using PayPal, and then download the tutorials directly from the main tutorials page.
Share your comments and thoughts below. All comments are anonymous and cannot be edited.