• 2D shoot 'em up
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
The Legend of Edgar 1.36
SDL2 map editor tutorial [UPDATED]
TBFTSS: The Pandoran War - Amiga OS4 Port
— Mission-based 2D shoot 'em up —
A fundamental aspect of our game will be the ability to earn cash rewards from defeating enemies. While we'll be awarding the player catnip (the currency) for each enemy defeated, we'll also be releasing collectables - catnip, ammo, and health powerups. These will float around for a few seconds before vanishing.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./shooter3-05 to run the code. You will see a window open like the one above. Use the WASD control scheme to move the fighter around. Hold J to fire your main guns. Defeat the enemies to have them drop various collectables. To pick them up, simply fly into them. Notice how they flicker and vanish after a few seconds if they are not collected. Once you're finished, close the window to exit.
Inspecting the code
Like a lot of things now, adding in collectables is a fairly simple task. We've got various files to add and modify, but overall it's nice and easy.
So, let's start with defs.h:
We've add in a bunch of new enums. These will represent the type of collectable (CT being short of Collectable Type). CT_CATNIP will represent catnip, CT_HEALTH a health powerup, CT_AMMO an ammo powerup.
As one of our powerups gives us more ammo, we've added a #define for that purpose:
We're not using ammo just yet, but we're going to make sure now that our collectable doesn't give us more ammo than MAX_KITE_AMMO.
Now let's look at structs.h, where we've made a new additions and updates:
We've created a new struct to represent our collectable. By now, the meaning of most fields will be clear, so we'll skip ahead to the update to Stage:
We've added in a two fields, collectableHead and collectableTail, to act as the linked list for our collectables. Finally, we've updated Game:
We've added a `catnip` field, as well an `ammo` field within the `kite` struct.
With that prepared, we do dig into the new collectables.c compilation unit. This will be very straightforward, as it adheres with the design and coding patterns we've been adopting the whole way through this series.
Starting with initCollectables:
A very standard init function. We're setting up our linked list for the collectables, and loading in some textures.
On to the next function, doCollectables:
Again, a very standard function that is processing a linked list. For each one of our collectables, we're calling doCollectable. A collectable is removed from the game when it's `health` is 0 or less (such as when it is touched by the player or naturally expires).
All standard stuff so far. Okay, let's see what doCollectable does:
Firstly, we're moving the collectable, by updating its `x` and `y` by it's `dx` and `dy`. We're also decreasing its `health`. Next, we're testing to see if it has collided with the player. If so, we'll work out what `type` of collectable this is, and respond accordingly. If it's CT_CATNIP, we'll increase game's `catnip` by the `value` of the collectable. If it's CT_HEALTH, we'll award the player 5 extra health points. If it's CT_AMMO, we'll increase Game's `kite`'s `ammo` by 1, but not allow it to go higher than MAX_KITE_AMMO. With that done, we'll set the collectable's `health` to 0, so that it is removed from the game.
A straightforward function - we're just moving the collectable around, preparing to time it out, and checking to see if the player has collected it.
Next, let's look at drawCollectables:
No real surprises here. We're drawing the collectable if it's on screen. One thing we're doing extra is checking if the collectable's `health` is greater than 2 seconds, or if the modulo 10 of the `health` is less than 5. What this means is that if the collectable has at least 2 seconds of health left, it will always draw. However, if not we'll only draw it based on the value of its `health`. This will cause it to flicker when it's close to expiry, giving us a hint that we need to grab it soon.
That's our processing and drawing done. Now, let's look at the other functions we have. Starting with initCollectable:
This function is basically a factory for our Collectables, settings up all the common fields, attributes, and behaviour. We're passing in the `type`, the originating position (`x` and `y`), and the `texture` to use. Next, we're mallocing a Collectable, adding it to our list, and setting its values. Every collectable created will move in a random direction, at a random velocity (determined by its `dx` and `dy` values), and will live for between 6 and 7 seconds.
If we look at the dropHealth function, we can see how it is used:
As expected, we're passing over CT_HEALTH, as well as the starting position, and the health texture. The position values are passed into the dropHealth function itself.
dropCatnip follows, and takes a somewhat different approach:
The idea behind this function is to create a number of catnip collectables, based on the `amount` of catnip passed into the function. The function will enter a while-loop, creating catnip collectables with a minimum value of 5 (unless `amount` is 5 or less, in which case it will be the remaining value). The amount assigned to the collectable will be deducted from the amount passed into the function, until it reaches 0. This means that our catnip collectables will all have random values, meaning that the player will have to collect them all to gain the full amount (and when being fired upon, this might not be possible!). This is done just to add some variety to the proceedings, rather than create a single catnip collectable of the value we request.
Finally, we have dropAmmo:
It's largely the same as the dropHealth function, except for the different arguements.
That's it for collectables.c. Now we can move on and see how it's all put together. If we head over to greebleLightFighter.c, we've updated the `die` function:
Whenever the enemy fighter is destroyed, it will drop between 0 and 34 catnip (via a called to dropCatnip). There's also a 1 in 4 chance that we're going to call dropHealth, and a 1 in 5 chance that we'll call dropAmmo. So, our fighter has a chance to drop all our collectables whenever it it destroyed.
The last thing we need to do is integrate our collectables code into stage.c. So, starting with initStage:
We're making the call to initCollectables. Next, we've updated doStage:
We've added the call to doCollectables. And finally, in `draw`:
We've added drawCollectables.
That was easy! And the nice thing about this system, as we'll see later, is that we can expand it to include other items that the player can pick up, with ease.
But what about the health, catnip, and ammo values that these collectables are affecting? It would be good to know what we've picked up, and how much of it. So, in the next part we're going to introduce our HUD, that will also incorporate elements such as messages, health, and ammo.
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: