— Simple 2D adventure game — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction Our final room is an Escape Room. Upon entering, the player will be locked in, with no clear means of escape. The icon will also be nowhere to be seen. However, the way out is simple: fake walls. By circling the room and pushing against the walls, the player will reveal a way out (and also the hidden icon). This might sound hard to do, but with our entity and touch system, it was in fact very easy. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./adventure13 to run the code. The usual controls apply. The escape room is located in the south-east corner of the store room. A signpost offering Free Cake will mark the entrance. Following the route into the escape room, then hug the walls to find the icon and the way out. Close the window to exit. Inspecting the code You will notice how a wall comes up block our exit once we enter the escape room. This was achieved very easily. The wall is raised when the player walks on an invisible entity, known as a Trigger. This is defined in structs.h:
A simple structure, it contains just one variable called target. This will be used to hold the name of the entity we want to connect to. To facilitate this, we've added a new function pointer to our Entity struct, called activate:
We'll see more on how this works in a little bit. For now, let's return to our Trigger. Our Trigger functions are defined in trigger.c. It contains three functions. We'll start with initTrigger:
At this point, you can see that this is a regular init function for an Entity. Note, however, that we're not assigning a texture. We want it to be invisible, so not assigning one will cause it not to render (we've updated entities.c to accomodate this). Our load function isn't anything out of the ordinary, either:
We're just loading in the target data for the Trigger, in much the same way as we load in other entity extended data. Our touch function is more interesting:
After confirming the thing that has touched the Trigger is the player, we're calling a new function called activateEntities, and passing over the Trigger's target to it. After this, we're marking the trigger as dead, to remove it from the dungeon and stop it from being activated again. Our activateEntities function is defined in entities.c, and takes one parameter - the name of the entity we wish to interact with:
In this function, we'll loop through all the entities in our dungeon and look for any that have a name matching that one we passed into the function. We also want to make sure that they have an activate function set. If so, we call it. To see what this looks like in action, let's consider our Wall entity. Our Wall entity is what it suggests - an entity to represent a wall. It's just a solid (or not) entity with a wall tutorial. It is defined in wall.c, a file that contains 4 functions. We'll go from the top, starting with initWall:
Nothing special here, as we're just setting the function pointers. However, our activate function is what we're most interested in:
The activate function here will toggle the entity's solid state, making it either solid or non-solid. How this all ties into the activateEntities function is simple. Consider a trigger with a target of "CakeWall". We then name our Wall entity "CakeWall". Upon the player touching the Trigger entity, activateEntities will be called, passing over "CakeWall" as its argument. Our Wall named "CakeWall" will be found in the dungeon's entity list, and the Wall's activate function (above) will be called. In effect, we've made it so that a stepping on our invisible Trigger will make a once non-solid wall become solid and visible (and visa-versa). With our solid state changed, we call a function called updateSolid, to change the entity's texture:
If it's solid, we want to set its texture as a wall. Otherwise, we set it to NULL, to make it invisible. We've separated this out as its own function, so that the logic can be shared by the load function:
Walls can either be solid or non-solid to begin with, represented in the JSON as a 1 or 0. We have also created a fake wall. This is simply an entity that looks like a wall, but disappears once the player touches it. It's defined in fakeWall.c. There are just two functions, initFakeWall and touch. Starting with initFakeWall:
Our fake wall is just a basic entity, without any extra entity data. Notice how the fake wall is solid, even though it will die upon the player touching it. This is because we want the wall to block the player's line of sight, and not give away its existence. The touch function is next, and is likely just as you might expect:
Once touched, the fake wall's alive is set to ALIVE_DEAD to remove it from the dungeon, and an info message is set (with a reference to DOOM..!). With the wall gone, the player will be able to proceed into the area once disguised from them. Once again, we need to add the new entities to our initEntityFactory function in order to use them:
And that's it for the escape room! We only needed to create some entities to disguise things from the player and make them blend in with their surroundings. Before we wrap up, let's look at some other tweaks, starting with drawEntities in entities.c:
As well as testing if the entity isn't dead before drawing it, we're also testing if a texture has been set. We don't want to pass over NULL data to our blitAtlasImage, as it could cause us grief. We've also updated the Dungeon Mistress's touch logic, since we're now able to return her all 4 of the Icons she wants:
With all four Icons returns, the Dungeon Mistress is less than pleased that we've passed her test, and will grant the Prisoner his freedom. A locked door in the started area will be unlocked via a call to activateEntities, passing in "ExitDoor" as an argument. Our Door entity has also received an update for this (in door.c):
When called, the Door's activate will toggle the state of the door between locked and unlocked. Our game is very nearly finished! Hurrah! All we need to do now is add some finishing touches: a title screen, an ending screen, and some music and sound effects. We'll tackle these in the final part of the tutorial and then stick a fork in it. It's been quite a journey, eh? Purchase The source code for all parts of this tutorial (including assets) is available for purchase: From itch.io It is also available as part of the SDL2 tutorial bundle: | |
Desktop site |