• 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 —
Santa's night is not going as well as he'd planned - his reindeer are all laid up in bed, and he's forgotten to bring all the gifts and coal with him on his delivery run. His elves are helping out with their magic, but it's not exactly working as expected, and now the nearby snowmen are hurling snowballs at him. The snowballs will instantly destroy the sleigh if they make contact (because they're magic), so they are best avoided. The snowman and snowball here will represent the first of our two planned hazards.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./santa11 to run the code. Press Space to play. Use the same controls as before. As you play, enchanted snowmen will appear, tossing snowballs into the air. Avoid the snowballs and continue to deliver gifts and collect sacks. The game will continue for as long as you are able to maintain your Xmas Spirit (and don't crash into a house). When you're finished, close the window to exit.
Inspecting the code
Adding in our snowmen is a simple task, since both the snowman and the snowball are nothing more than entities.
Starting with defs.h:
We've added in ET_SNOWMAN and ET_SNOWBALL, to represent the types for a snowman and a snowball, respectively.
Next up is an update to structs.h:
We've added in a struct to define our snowball. `thinkTime` is used to control the delay between the ball hopping up into the air, while `startY` stores the starting point on the y axis. We don't have a struct for the snowman, since he is simply an entity.
Speaking of the snowman, let's look at him next. All our snowman's logic and rendering live, predictably, in snowman.c. It contains the standard set of functions, so let's start with initSnowman:
We start by loading the texture we want to use, before then setting up the snowman's position. We choose an `x` position between 200 and 300 pixels beyond the right-hand side of the screen, and a `y` position with the bottom of the snowman (according to its texture) on the ground (GROUND_Y). We then use canAddEntity, the function we saw when originally adding in houses, to see if we can place the snowman in the desired spot, before doing so. We don't want our snowman to be behind houses or other things; he should be quite visible, so the player can spot him, and know to avoid him.
With our position decided upon, we next call initSnowball. Just like our chimneys that are created by our houses, our snowballs are created by our snowmen. Our snowballs are not a part of our snowmen, however, being funtionally independent entities. We call initSnowball, passing over our `x` and `y` positions to the function, with some adjustments so that the snowball is in the hand of the snowman. We'll see initSnowball in a little bit. For now, we continue to create our snowman, setting up all the regular fields: `x`, `y`, `type`, `texture`, `tick`, `draw`, and `touch`.
You'll find that the rest of the snowman is very standard. Starting with `tick`:
We move the snowman according to the speed of our Stage, and remove it once it has left the right-hand side of the screen.
The `draw` function follows:
We're just rendering the snowman using its `texture`. Finally, we have `touch`:
Nothing more than a simple check to see if the player hits the snowman. If they do, we'll call killPlayer.
That's all for snowman.c. As you can see, our snowman really is just a static entity that creates a snowball at the same time as itself.
Now over to snowball.c, where we setup and handle our snowball. Compared to the snowman, things are a little more interesting. We'll start with initSnowball:
We're creating Snowball data here (`s`), to go with our snowball entity (`e`). We're setting the snowball's thinkTime to one second, and it's `startY` to the value of `y` that we're passing into the function. We then set the usual `x`, `y`, `type`, `texture`, `tick`, `draw`, and `touch`.
The `tick` function comes next, and is where we drive the snowball's behaviour:
The snowman, being a static entity, is not responsible for tossing the snowball in the air. Instead, the snowball itself jumps up. We start by checking if the snowball's thinkTime is greater than 0, and decreasing it if so. If it falls to 0 or less, we're setting its `dy` to a value between -16 and -12. In effect, once the snowball's thinkTime expires, the snowball will hop up into the air, at a random velocity. Essentially, when our snowball's thinkTime is greater than 0, we consider it to be at rest, in the snowman's hand.
We see this next, in the else clause - the snowball is now considered to be airborne, and so we apply gravity to it. We increase the value of its `dy`, and add that to its `y`. At some point, the snowball will start to return to the ground. We test to see if the snowball has therefore gone past its starting point (`y` > `startY`), and if so clamp its `y` to `startY`, and set its thinkTime to a value between 1 and 2 seconds.
All in all, this means that the snowball will hop up into the air, come down, pause a moment, and then hop back up again. The effect will be as though the snowman is tossing the ball into the air, since it will always land back in his hand for a moment. Of course, the ball is just bouncing up and down by itself. The snowman has nothing to do with it.
Finally, we move the snowball left at the same speed as Stage, and set its `dead` flag if it goes off screen.
That's the main logic done, so we'll look at the remaining functions. Starting with `draw`:
We're just rendering the snowball, using its `texture`. Next up is `touch`:
As one might expect, we're checking if the snowball has touched the player, and calling killPlayer if so. Notice that we're passing over the snowball's x and y values, so that the hit texture that appears on the player is located where the snowball made contact. This looks a little nicer than centering the explosion on the player. Finally, we're setting the snowball's `dead` flag to 1, so it, too, is removed from the game.
That's it for snowman.c and snowball.c. We've only got one more thing to do, and our first snowman is ready for action. Heading over to stage.c, where we've updated addObject:
We've added in an extra check to our random spawning. If `n` is less than 50, we're calling initSnowman. This means there's now a chance that we'll add a gift sack, a coal sack, or a snowman.
Excellent! That was nice and simple, eh? Adding in this simple hazard has made our game a little more challenging, so we don't just need to collect sacks and aim at chimneys.
We did say that there were two types of enchanted snowman, however. Therefore, in the next part we'll add our second snowman, one that will make things a bit trickier than before.
The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle: