• 2D shoot 'em up
SDL2 turn-based strategy tutorial
Water Closet ported to PlayStation Vita
The Legend of Edgar 1.35
SDL2 Rogue tutorial
— A simple turn-based strategy game —
The final ghost that we're going to introduce is an exploding type. Upon spotting an enemy, it will walk right up to them, and stand there. Killing one of these exploding ghosts while they are next to you is a very bad idea, as they will detonate and cause damage to the surrounding as they go down.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./sdl2TBS20 to run the code. You will see a window open like the one above, showing three wizards in a maze-like map, as well as a number of ghosts. Play the game as normal. Take out the Green Ghosts from a distance, as much as possible. Note how they cause a huge amount of damage to those standing within one square when they die. Once you're finished, close the window to exit.
Inspecting the code
Our Green Ghosts actually require a little more logic compared to their peers, but they are still not too difficult to implement; we just need to update a number of files and take some extra considerations into account.
Starting with defs.h:
We've added in an enum value called AI_EXPLODER. This will be for the ghost AI to say it is an exploder type.
The exploders have no weapons, so we can go straight over to ai.c, where we've updated doAI:
We've added in AI_EXPLODER to our ai `type` switch statement. We'll be calling a new function named doExploder, and passing in the current ghost's Unit data.
doExploder itself is quite a simple function:
The idea behind this function is that the Exploder will search for nearby, visible mages, and then move to the closest one.
We start by clearing Stage's targetEntity and setting a variable called `closest` to 0. Next, we loop through all the entities in the stage, looking for mages (ET_MAGE) that are the ghost's line of sight (hasLos). When we find one, we'll calculate the distance (as `distance`) and then test whether Stage's targetEntity is NULL or the distance of this mage is nearer than `closest`. We'll then set `closest` to the value of `distance` and set Stage's targetEntity as this mage.
With that done, we'll test if we found a target, and then check the value of `distance`. If it's greater than 1, we'll call a function named moveToTarget, otherwise we'll set the ghost's `ap` to 0. This means that if the ghost is standing more than 1 square away from its target, it will move closer to them. Otherwise, it will stand where it is..! Finally, if we didn't find a target after all these checks, we'll call moveRandom().
Easy enough, eh? moveToTarget is just as easy:
We've seen this same logic used a couple of times before, so we'll just briefly go over it. The idea of this function is to have the ghost move to within one square of its target (Stage's targetEntity). We're taking the target's `x` and `y` and randomly adding -1 and 1, checking if the square we want to move to is free. We'll then calculate a route to the destination. If we're unable to move to the goal after several attempts, we'll simply set the Unit's `ap` to 0, so it stays where it is.
That's all we need to do for the actual AI of the ghost. As usual, we'll update addAIUnits, to make use of them:
We're calling initEntity with "Green Ghost", to create our three Green Ghosts for this part.
Onto ghosts.c now, and here's where things get a bit more interesting than the previous ghosts. Starting with initGreenGhost:
We're setting the ghost's attributes as normal (notice how it has lower stats than others, include a much shorter moveRange). It's AI `type` is of course AI_EXPLODER, and it has no weapon. However, it's `die` function is new. When the Green Ghosts is killed, it will call a function named `explode`.
Let's take a look at that function now:
This is the function that is responsible for making the exploder actually explode upon death. The first thing it does is check that its `dead` flag is not already set (to avoid two exploders endlessly killing each other). Next, it calls the regular `die` function. With that done, we'll entering into two for-loops, both from -1 to 1, to test the surroundings in a one square radius. For each iteration of the loop, we're calling getEntityAt for the position and testing the entity returned. If it's a mage or ghost, we're calling its takeDamage function, to inflict 15 points of damage! We're also calling addHitEffect, to generate some visual feedback. If there isn't a mage or ghost there, and the map square is a ground tile, we'll convert into a slime tile.
So, in short, an exploding ghost will inflict 15 points of damage to all those within one square of it, and also splatter the area with slime..! Nasty. We could, of course, have made it nastier, by inflicting damage based on distance, but that would make the game quite a bit tougher and perhaps make the exploders too difficult to deal with.
Before we press on, we should take a look at a small change we've made to the takeDamage function in units.c:
We're now testing that the unit is not already dead before running the rest of the logic. This is to prevent situations where an exploder dies and kills another exploder, that dies, and inflicits damage on the first exploder again, etc. While this doesn't cause other game to lock up, it does produce lots of damage text, and also fills the hud with repeat messages. This little tweak prevents that issue from occurring.
You may well have realised something - it's possible for the ghosts to kill the mages as they die. So, what happens if the last ghost kills the last mage? What's our game going to do? Well, turning to stage.c, we've made a small update to handle that:
For now, when endTurn is invoked, we'll attempt to swap turns just 2 times, before giving up and exiting the program. A bit heavy handed, yes, but as of right now, we don't have a proper win or lose state. So, this will do for the time being.
There we go! All our ghosts are implemented, as well as map generation, weapons, items, etc. Our game is very nearly finished! There are just a handful of things that need to be fixed up, and we also need to add in the aforementioned win / lose state. We'll be looking into that next.
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.