• 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
— An old-school isometric game —
We now have a small map that we can navigate, as well as other objects littering the ground. However, the walls and trees that we've added can cause us to lose sight of Purple Guy as he stands behind them. In this part, we're going to look into how we can make these objects transparent, so that we can still see him.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./isometric06 to run the code. You will see a window open like the one above. Click on ground squares to have Purple Guy move around. Notice how as Purple Guy moves behind larger objects that might otherwise obstruct our view of him that they become transparent, allowing us to still see him. As he moves away, they return to their solid colour. Once you're finished, close the window to exit.
Inspecting the code
Making our objects become transparent as Purple Guy moves behind them isn't nearly as tricky as one might think. It involves quite a few tweaks, but is otherwise simple.
Let's start with defs.h:
We've added in two new defines. IF stands for Iso Flags, and will be the flags that we'll pass over to our addISObject function. IF_TEST_OCCLUSION means that we want the ISOObject to test whether it is obstructing our view of Purple Guy, as we'll see later.
Now over to structs.h, where we've made a number of updates:
To ISOObject, we've added an anonymous struct called `colour`, holding `r`, `g`, `b`, and `a` values. These are our red, green, blue, and alpha values, and will allow us to control the colour and alpha value of the ISOObject as a whole.
We've also updated Entity:
We've added a field called isoFlags, that will contain the isometric flags that this entity will use when being turned into an ISOObject.
Last, we've updated World:
We have a new field called playerISORect. This is a rectangular area that will represent Purple Guy on screen. We'll be using this to determine where he is when we add other ISOObjects, and allow us to test if they are overlapping him.
Now over to tree.c:
We're setting the tree's isoFlags as IF_TEST_OCCLUSION.
That's all there is for tree.c, so we can now move onto entities.c, where we can see the major updates put to work. Turning now to drawEntities:
Our addISOObject function now takes one extra argument - the `flags` to use. When calling addISOObject for our entity (the first call), we're passing over the entity's isoFlags. In the case of the tree, this will be IF_TEST_OCCLUSION. All other entities will be passing 0 (IF_NONE) as their default. Note that the addISOObject call for the shadow also passes over IF_NONE, as the shadow won't be occluding our view.
We've also added in a new test, to check if the current entity (`e`) is the player. If so, we're setting the values of World's playerISORect `w` (width) and `h` (height) to the player's texture's `w` and `h`. Notice how we're subtracting 8 from the height. This is simply to allow for a small leeway in the coverage; we're not going to worry if a single pixel is overlapping Purple Guy, since he's still quite visible. Next, we're calling toISO, and passing over the player's `x` and `z` values, outputting them into playerISORect's `x` and `y`. Finally, we're adding the previously calculated `sx` and `sy` adjustments. In short, we're calculating where Purple Guy will be rendered on screen, and storing those dimensions into World's playerISORect. There is room for optimisation here, but for now this approach is working fine.
Onwards now to map.c, where we've updated drawMap:
We've updated our calls to addISOObject. For any tile that is a wall, we're now passing over IF_TEST_OCCLUSION. Otherwise, we're passing over IF_NONE. The ground tiles won't be obstructing our view of Purple Guy, so there's no need to test them. Note that we've removed the check for the selected tile (and associated graphic loading), as we're not using it any more.
Now onto iso.c, where the major work for handling our transparency has taken place. Starting with drawISOObjects:
Now, when drawing our ISOObjects, we're applying their alpha and colours, via calls to SDL_SetTextureAlphaMod and SDL_SetTextureColorMod. Notice how we're passing over the ISOObject's (`o`'s) color's `a` value to SDL_SetTextureAlphaMod, and the `r`, `g`, `b` values to SDL_SetTextureColorMod, as one would expect. At the end of the loop, we're resetting the texture's colours back to 255, since we're working with a texture atlas and don't want to permanently mess with the colors.
Easy. Now for something a bit more complicated. Onto addISOObject:
As we saw before, the function has been updated to accept a new parameter: `flags`, the IF values. To begin with, we're setting the ISOObject's (`o`'s) rgba values to 255, as a default. We're then testing the flags that were passed over, to see if they contain IF_TEST_OCCLUSION. If so, we'll be checking to see if this ISOObject we're adding is overlapping Purple Guy.
First up, we want to check if World's playerISORect is behind the current ISOObject. We can do this by simply testing the `x` and `z` values passed into the function against the player's `x` and `z` values. If the player's `x` value is greater or equals to the function's `x` and the player's `z` is less than or equal to the function's `z`, then it means the current ISOObject is being drawn after the player, and could therefore be obstructing them. We then perform a simple collision check, using the player's screen dimensions against the ISOObject's screen dimensions. If they overlap, it means the ISOObject is obstructing Purple Guy! Again, there is space for optimisation here.
Having discovered that Purple Guy is being occluded by the new object, we set the new object's alpha value (color's `a`) to 35%. If we wanted it to vanish entirely, we could set it to 0. 35% is good for us.
Great! Now we don't have to worry about losing track of the player when they move behind large objects. One other thing we need to take care of is the tile select. Before, we were repurposing the zone exit tile (the yellow one!). Now, we're displaying a wireframe sprite, that is easier to track and find when it moves behind walls.
If we turn now to world.c, we can see how this is being done. Starting with initWorld:
We're loading two new textures, into an array called tileSelect. These will form the front and back of our select box, and give it a 3D look.
Next, we've updated `draw`:
Here, we're calling a new function drawCursor:
This function is responsible for drawing our wireframe box. Before we begin drawing, we check that Purple Guy isn't currently walking, that the cursor is within map bounds, and that the cursor is over a ground tile or a wall tile.
To actually draw our wireframe select box, we're calling addISOObject twice (after performing a check that our cursor is over a valid tile). We're passing over the relevant `x`, `z`, `sx`, and `sy` values, and then each of the tileSelect textures, the first one being drawn on the middle layer (LAYER_MID), and the second being drawn in the foreground layer (LAYER_FOREGROUND). This gives the impression of the wireframe being 3D, and mostly works when hovering over entities.
I say mostly, because here is an example of where the sprite-based isometric sorting starts to break down a bit. If one positions the wireframe down from walls, it can be seen that it doesn't quite work the way we expect. This is but a minor error, and one that doesn't detract too much from our game. Most importantly, we don't lose sight of our select box when it goes behind walls.
Another part done! The foundations of our game are pretty much there. What we need to do next is load our map data, so that we can start to explore more. We've been standing by that river a bit too long.
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: