![]() | |
PC Games
• Orb Tutorials
• 2D shoot 'em up Latest Updates
SDL2 Quest game tutorial
SDL2 Versus game tutorial
Download keys for SDL2 tutorials on itch.io
The Legend of Edgar 1.37
SDL2 Santa game tutorial 🎅
Tags • android (3) • battle-for-the-solar-system (10) • blob-wars (10) • brexit (1) • code (6) • edgar (9) • games (44) • lasagne-monsters (1) • making-of (5) • match3 (1) • numberblocksonline (1) • orb (2) • site (1) • tanx (4) • three-guys (3) • three-guys-apocalypse (3) • tutorials (18) • water-closet (4) Books ![]() The Battle for the Solar System (Complete) The Pandoran war machine ravaged the galaxy, driving the human race to the brink of destruction. Seven men and women stood in its way. This is their story. |
— Simple 2D quest game — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction In the last part, we added buildings that could be entered via doors. What we're missing now is the town residents, whom the robot can interact with. In this part, we're going to set about populating the town. Each resident will have a name, can be spoken to, and will also have a profession, if they were assigned to a building when they were added to the town. The building itself will also be given a type, to aid this. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./quest09 to run the code. As before, you can move around with the WASD control scheme. To speak to a resident, simply walk into them. A dialog box will be displayed as they speak. Press Space or Enter to dismiss the box. Notice how each resident is displayed in grey before they are interacted with, to help us know whom we have yet to meet. When you're finished, close the window to exit. Inspecting the code There is actually quite a bit we need to do to add our residents. We could, of course, have simply added the entities to the town and called it a day, but we want them to talk to them, have different dialogs, have names, and also have professions (although these won't actually impact our game). Let's start with the changes we've made to structs.h. First, we've updated Building:
We've added the `type` field, that will determine the type of building. This will be a BT_ enum value, found in defs.h. Next, we've added the actual Resident struct:
This struct will hold the extended data for a Resident entity (as part of its `data` field). `town` is the town entity to which the Resident belongs; `entity` is a reference to their own entity object (forming a two-way relationship), and `building` is the Building to which the Resident belongs (if any). That should all be quite easy to follow, so let's now look at the changes to townGen.c. There are quite a number of updates here, so we'll start with generateTown:
We're calling a few new functions here, generateTownName, assignBuildingTypes, and addResidents. We'll look at generateTownName a bit later on. For now, we'll move on to assignBuildingTypes:
What this function does is randomly assign a building type to each building in the town. In our game, we'll be limited to one inn, bank, petshop, etc., but as many houses as we wish. We also want to make it so that not all towns will have a blacksmith, a takeaway, and such. To achieve this, what we do is fill an array with all the possible building types, plus a number of houses (NUM_BUILDING_TYPES is BT_MAX * 2). We then shuffle the array, to randomly order the elements. Finally, we loop through all our buildings, setting their `type` at the incrementing index of types as we go. This means that we will have a unique set of buildings, with normal houses being a lot more common. With our building types randomly determined, we can move on to adding in the Residents themselves.
There's not as much going on here as it first appears, and this function can be thought of as having to main parts. To start with, we're attempting to add residents to a building. We do so by looping through all the buildings in the town. If the building isn't a house, we definitely want to try to add in a resident, to act as the owner of the establishment. If the building is a house, there will be a 50/50 chance that the homeowner will not be there. When it comes to adding in the resident to a building, we will make a limited number of attempts to do so (up to 100). We select a random point within the bounds of the building, and then make a call to canAddResidentAt (we'll come to this in a moment). Should the placement be successful, we'll create the Resident themselves, place them at the location (`x` and `y`), and then set their `town` and `building` data. Next, we'll randomly scatter a number of other Residents throughout the town. As we're picking random locations, some will be out on the streets, while others will be inside buildings. In this part, those who end up inside buildings won't be assigned the building itself, as they are simply visiting. As in the first part, we'll set their `town` field to the current town. So, in summary, we're simply adding in some Residents to our buildings and town streets. Now to look at canAddResidentAt:
As expected, this function attempts to add a Resident at the given position (`rx`, `ry`). There are two checks performed - the first is that we want to ensure that there is space between the Resident and a wall; we don't want our residents to be jammed right up next to walls, which could result in areas becoming impassable (such as entrances to buildings). The next check ensures that there is also a distance of 3 units between the input position and any other entities (including doors) on the current map. Once again, we don't want everything to be clustered together, so performing this check ensures that the Residents don't block routes that the player wants to explore. That's all the changes we've made to townGen.c, so next we can look at resident.c, where we're creating our Resident entity. As well as setting them up, we're also dealing with their various interactions. This compilation unit will eventually do some quest management for us. For now, the interactions are basic. First off, we have initResident:
Nothing taxing here, as we're just creating our Resident data. Note how we're initially setting the entity's texture to the greyscale image (unknownResident). This is flipped during the interaction. Also note we're calling generateResidentName, to give the Resident a random name. We'll come to name generation in a short while. For now, let's look at the `touch` function:
When the player interacts with a Resident, by walking into them, we'll flip their texture to a colourful one, and then invoke the `chat` function. When flipping the texture, we're making use of the entity's `id`, to ensure consistency; due to the way we set our random seed, the entity will always have the same `id` when created for this world. All simple enough. So, let's look at the `chat` function:
The chat function basically displays a message box, with the name of the Resident, and some dialog text. The `text` of the dialog will depend on the Resident. Those who have a building assigned to them will deliver a fixed line, about their establishment. For example, a Resident assigned to an Inn will ask if the robot wants a room for the night. Note also that the `speaker` string includes the profession of the Resident, based on their `building`. So, the owner of the Inn will have the suffix "(Landlord)" added. If the Resident isn't assigned to a building, they will either make a comment about the adventurer being a robot, or will introduce themselves, and welcome the robot to the town (using the name of the `town` we originally linked them against). With the `speaker` and `text` strings determined, we call addMessageBox to display the message. The message box (including addMessageBox) uses the same logic as found in SDL2 Adventure, so we won't delve into how that works. Know only that the colour of the dialog box is determined by the value of `speaker`, to once again be consistent; the background will always be the same when speaking to this Resident. Those interested can investigate messageBox.c, to see how it all works.
That's mainly it for this part - we can now add in our Residents, assign them to buildings, and interact with them. Before finishing up, we'll take a look at the other things we've added, for some context. First, let's look at how the name generation works, in nameGen.c. There are a number of functions here, that mostly do the same job. We'll take generateTownName as an example:
To create the name for our town, we'll pass over a char buffer (`name`) to fill (expected to be MAX_NAME_LENGTH in size). We'll then randomly choose a prefix and suffix from our list of townPrefixes and townSuffixes. With the two chosen, we use sprintf to join them together, into the `name` buffer. There's scope for some optimisation here, since we're counting the length of the arrays each time. Thankfully, this isn't a time critical operation, and also happens during our world generation, so we can get away with it. Note that this function will loop until it creates a name that is not already in use, via isUnique:
isUnique basically caches the generated names of our islands, towns, and residents, so that they are only assigned once. We don't want to have multiple towns and residents with the same name in our game. There is an upper limit of MAX_GENERATED_NAMES in our game, meaning we'll never be able to have more than a total of 256 (as defined) islands, towns, and residents. This is plenty of room for us, however. If we need more space, we can simply increase this value (or turn it into a linked list). As for the name parts themselves, these live in a JSON file called names.json. It can be found in the data directory. Below is a short example of how this appears:
The actual file is much bigger than this, have many prefixes and suffixes for each type. As you can see, it is simply a list of names and words, forming the left and right parts of a pair. These are joined together as desired, to create a unique name. Finally, we've made tweaks to various other files, to incorporate our updates. First up, we've updated the `logic` function in town.c:
When a message box is active, we want to take control away from the player until it is cleared. Next, we've updated generateWorld (overworldGen.c):
A call to initNameGen prepares our name generation. Lastly, we've updated generating.c, to setup our message box:
Once everything has finished generating, initMessageBox is invoked to ensure messages can be processed. There we have it, our town is complete. We have buildings, doors, and people to talk to. The only thing missing is the ability to enter and exit Towns. Right now, our game is forcing us into a town, with no means to leave. Our next part will fix this, so we will finally be able to fully explore the world. There are many island to explore, towns to discover, and people to meet, and it's about time we were able to do so. Purchase The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle: From itch.io |