• 2D shoot 'em up
The Legend of Edgar 1.37
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
The Legend of Edgar 1.36
SDL2 map editor tutorial [UPDATED]
— 2D Santa game —
With the previous part, we finished up making our game a bit more pleasing to look at. In this final part, we'll be adding in sound, music, a particle system, and a quadtree. All stuff that isn't essential to our game, but helps to improve it in various ways.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa15 to run the code. Press Space to play. Play the game as normal, aiming to earn a highscore. When you're finished, close the window to exit.
Inspecting the code
Other than our sound and music, there are a few little tweaks we could make to our game, to improve it. One such addition is an indication to the player that they have successfully delivered a gift to a house on the Nice list. We'll be illuminating a series of lights on that house, so that the player can be sure they haven't missed it, or have negative points (remember, it's possible to drop a gift down a chimney, and then throw lots of coal down there as well - this won't affect our Xmas Spirit, but will affect our score).
Note: while we've added things such as a particle system, a quadtree, and our sound and music, we won't be going into extensive details on, as they've been covered many, many times in other tutorials, and at this point we don't want to be repeating ourselves.
We go first to structs.h, where we've made an update to House:
We've added in a new variable called successLights. This will track the animation of the successful delivery lights. We're using a variable here for each house, so that all the lights don't run in step with one another, which might look a bit boring. Next, we've added a reference to the chimney itself (as a pointer called `chimney`). This is so that we can interrogate the chimney attached to the House, to find out if it is complete.
Next, it's over to house.c, to make use of that update. First, to initHouse:
We're assigning the created chimney's data to the House (`h`) `chimney` pointer. We can now track the chimney from the house.
Over then to `tick`:
We're now testing if the house's `chimney` is complete, and if so we're increasing the value of the house's successLights. As we'll see in a moment, we're using this value to choose which texture to draw in our successLights texture array. Therefore, if the value of successLights equals or exceeds NUM_SUCCESS_LIGHTS_TEXTURES (defined in house.c as 3), we're resetting it to 0.
The updates to `draw` follows:
Now, when drawing houses on the Nice light (the house's `chimney` is not `naughty`), we're going to additionally render a set of dull lights if the house is incomplete (dullSuccessLightTexture), or one of our success lights if the chimney is complete (successLightTextures). As noted earlier, the house's successLights variable picks a texture from the successLightTextures texture array, and uses that. We also render dull success lights if the house has 0 points, just to cover the case where a player has completed a house before then pushing it into negative points, by dropping coal!
Lastly, we need to load our new textures, so it's over to loadTextures:
We're loading our dullSuccessLightTexture and our successLightTextures array (as gfx/successLights01.png, gfx/successLights02.png, etc).
That's it for the changes we've made to house.c. Of couse, we've added in music and sound effects. We won't talk about every single place we've add in sounds, but just point to a few instances where it has been done. Using player.c as an example, we've calling playSound in the `die` function:
Our sounds are played using a function called playSound, where we pass over the sound id we want to play (SND_GAME_OVER) and the channel we want to play the sound on (CH_SANTA). These are all defined in defs.h, and loaded via sound.c.
Another example can be found in snowball.c, in the `tick` function:
We've added a call to playSound when the snowball returns to its startY value, when the snowman "catches" the snowball. Here, we're caling playSound, passing over SND_SNOWMAN_CATCH on the CH_SNOWMAN channel.
While we're still on the subject of sound and music, an interesting example of sound control can be found in killPlayer (back in player.c):
As well as playing the sound effect that occurs when Santa's sleigh is struck (or we run out of Xmas Spirit), we're also calling pauseMusic (from sound.c). This is what causes our music to stop playing when we get a game over.
We start playing it again in initTitle (from title.c), which we always return to when the game is concluded:
We've added in a call to resumeMusic (again, in sound.c) which will make our music start playing again.
As previously noted, we've added in a particle system (in particles.c). It's once again done in a very standard way, so we won't go into details. Instead, we'll just show an example of where it is used. Over to carrot.c, we've updated `touch`:
Now, when the carrot hits the player, we're going to spawn 25 particles, via a call to addParticle. We'll give each particle a random orange hue (the final three parameters of addParticle at the RGB values). Other places where we've added in particles include chimney.c, gift.c, and snowball.c.
We've also added in a quadtree. While this isn't strictly required, it does help to reduce the number of collision checks that are going on. In our game, it is quite easy to rocket to 300 collision checks per frame, due to each entity checking for collisions against each other (such as when gifts and coal are deployed by the player). We're therefore using a quadtree to divide the game screen into various sections, and only checking for collisions that are relevant to the current entity.
We can see how this is being put to use in entities.c. Starting with doEntities:
We're removing the current entity (`e`) from our quadtree, by using a call to removeFromQuadtree, passing over the entity and the quadtree we want to work with (our one is set in Stage). While processing the entity in the usual loop, we test whether it is still alive, and placing it back into the quadtree if so. This means that dead entities are removed from our tree.
When it comes to testing our collisions, we just update doCollisions to tell our code to use the quadtree:
This works much like the collision checks in SDL2 Gunner. We call getAllEntsWithin (defined in quadtree.c), passing over the rectangular area occupied by our current entity (`e`), as well as an array of entity pointers (`candidates`), into which the discovered entities will be added. We then loop through all of the found entities and test just those ones for collisions. The result is that a call to this function that might have performed 300 checks now only performs 20.
We'll start wrapping this part up now, briefly looking at some setup and tear down for our new features. Over to stage.c, where we've updated initStage:
We're now calling initQuadtree and initParticles, to create our quadtree and setup our particle system, respectively.
We've also updated resetStage:
Here, we're calling clearParticles and destroyQuadtree, to remove all our particles and reset our quadtree.
Lastly, over in init.c, we've updated initGameSystem:
We're loading our sounds (initSound) and music (loadMusic), and playing our music on a loop (playMusic, passing over -1). We have no volume control, so we're setting the volume of our music here to a value that won't make it louder than our sound effects (setMusicVolume).
SDL2 Santa is finally complete! We have a full game loop, sound, music, a title screen, and a highscore table. Of course, there are a number of things that one might now wish to add to our game, to expand it. A small list of such things can be found below.
Otherwise, we have a great starting point for a game like this. Maybe one could change it around, to make it Halloween themed, and have a witch on a broomstick, dropping pumpkins onto zombies below? Perhaps even change the Santa game so that one can fly both left and right, like Defender. The opportunities are nearly endless. Like the scrolling in our game.
The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle: