• 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 —
Our third mission is going to do something special - we're going to be charged with chasing down 5 bombers, and destroying them. This mission will make use of destroying named targets, and not just enemies. Defeating enemies won't count towards our total, only destrying the bombers will. Once the quota of bombers is met, the player will need to defeat all the remaining enemies, and the mission will be complete.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./shooter3-26 to run the code. You will see a window open displaying the intermission's planets screen. Select Bluey, then enter the comms section to start the mission. Play the game as normal. You may repeat the mission as often as you like. Remember that you can change the fighter's damage, weapons, output, and health by editing game.c, if you wish to get ahead of things early. Once you're finished, close the window to exit.
Inspecting the code
Let's take a quick look at the mission file (data/missions/bluey.json):
Our enemy types is a comma separated list of enemies. Notice how we've got greebleLightFighter listed twice. This means they are twice as likely to appear compared to the others. Our objectives list contains, as expected, a requirement to defeat 5 greebleBombers (as the targetName). That's all we need to do to make the bombers the active target..!
Other than our new mission file and the update the planets.json, there isn't a great deal that we need to do here! This a short part indeed.
To support our new enemy, we've updated defs.h with some new enums:
AI_DEFENSIVE is an enum that will be used to specify that the enemy uses a defensive AI pattern, and will tend to stay away from enemies.
We've also added a new AI_WPN:
AI_WPN_ROCKET will give this AI access to rockets as a weapon, just what the bombers need.
Now for the bombers themselves. We've created a new compilation unit called greebleBomber.c, that contains all the functions required. Starting with initGreebleBomber:
Okay, a very standard factory function here. Of note is that the weaponType is AI_WPN_ROCKET, and the ai type itself is AI_DEFENSIVE. The bombers have a fair bit of health, but no shielding.
On to `tick`:
Very similar to the other fighters. The only difference here is where we're positioning the engine (self->y + 25). If we were going to make a generic bunch of functions for our fighters, we'd want to either pass the engine position over as a parameter, or build it into the Fighter struct.
We're going to skip over the `draw` and `destroy` functions, as they are the same as greebleLightFighter and greebleDualFighter, and only consider the `die` function:
Mostly the same as the others. However, our bombers don't drop any catnip when they are destroyed! Instead, there is a 100% chance of dropping ammo, with them releasing up to 3 of the powerups. Also, note that we're passing over "greebleBomber" to updateObjective. This is essential if we want our objective count (Destroy 5 Bombers) to be reachable.
That's all for the bomber, so let's head over to ai.c, where we've put in the new defensive AI code. Starting with doFighterAI:
We've added AI_DEFENSIVE to our switch statement, and calling doDefensiveAI when we meet it. The doDefensiveAI function is up next:
So, what's going on here? Well, we're first testing if we have a `target` and also if a random of 10 is 7 or less. If so, there's a 1 in 3 chance that our AI will head towards its `target`. Otherwise, it will fly in the opposite direction! Basically, it's more likely to retreat from the player than attack them. If the first if-statement was false, our bomber will choose a random point a screen's width and height away from it, and head for that. This is a more extreme form of the random darting movement the red and blue fighters use when battling the player. With that decided, we sort out the bomber's speed (`dx` and `dy`), `facing`, and update its thinkTime.
Yes, our defensive AI is very simple. Again, it's more likely to keep away from us than to engage us. But given how deadly the bombers are, that's no bad thing.
The last function we've updated is attackTarget:
As we now have a new weapon type for the AI, we've added AI_WPN_ROCKET to our switch statement, calling fireRocket when the condition is met. That's all we need to do..! When we first created the fireRocket function, we didn't tie it to the player, allowing it to be used by any fighter. And so, all we need to do is pass over the enemy fighter's details (self and its facing) and we're all good.
Before we wrap up, just a reminder that when adding in fighters and other entities, we need to register them with our entityFactory. The initEntityFactory function contains a mapping of the entities we want to spawn, against their init functions:
As we add more entities, we'll be updating this function with the relevant details.
And that's a wrap! Simple, wasn't it? Again, since we already spent time putting together our objectives, mission loading, AI, etc., all we need do now is slot the missing pieces in. We need to make adjustments here and there, but nothing major.
Right, so we've now seen offensive and defensive enemies. How about ones that flee us completely? Our next mission will see the player having to chase down enemies that will try to evade them, and don't possess any weapons. But not only that, we're about to introduce the possibility of failing a mission without being killed..!
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: