![]() | |
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 Third Side (Battle for the Solar System, #2) The White Knights have had their wings clipped. Shot down and stranded on a planet in independent space, the five pilots find themselves sitting directly in the path of the Pandoran war machine as it prepares to advance The Mission. But if they can somehow survive and find a way home, they might just discover something far more worrisome than that which destroyed an empire. |
— Simple 2D quest game — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction In our last part, we put together the framework for generating quests, with our demo quest simply being to wish someone a Happy Birthday. In this part, we're going to make our first "real" quest, which will involve delivering an item to someone. This is, in fact, much like the Happy Birthday quest, except that we will now have an item in our inventory, that will be removed once the quest is complete. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./quest14 to run the code. As before, you can move around with the WASD control scheme. Play the game as normal. Every now again, a Resident will request that you deliver an item to another person. The Quest Log can help you to locate the town where the Resident resides (so long as you've discovered it), in the Inventory section. In the town itself, the Resident will be swaying left and right on the screen, as a small indication that they are the one you need to talk to to complete the quest. Once done, the item will be removed from your inventory. When you're finished, close the window to exit. Inspecting the code This part represents the final major piece of our game, as it involves adding in the inventory, and also completing the section on the Quest Log. A new entity type, Item, will be created in this part. Let's start by looking at the updates to structs.h:
We've added a new struct called Item, which will represent a quest item. This item can be linked to a `quest`, which will aid us when viewing its data in the Quest Log. Next, we've updated the Game struct:
We've added in a linked list to hold our inventory items (inventoryHead, inventoryTail). We also have a field to hold the number of inventory item - numInventoryItems. Moving on now to quests.c, where we've made some updates to support our new quest type (note that we've removed the demo quest, with the delivery quest replacing it). First up, we've updated generateQuest:
Now, instead of START_DEMO_QUEST, our second quest step is START_DELIVERY_QUEST. We've next made some changes to executeQuestSteps, to support the new quest steps we'll be introducing:
We're first handling the new START_DELIVERY_QUEST command, and another new one command called REMOVE_INVENTORY_ITEM. This, as we'll see in a bit, removes an item from the player's inventory. Before that, we'll first look at doStartDeliveryQuest. This won't look too dissimilar from the demo quest setup:
Much like the demo quest, we're selecting a random resident as the `recipient`, setting the quest's `title` and `description`, and setting up a number of quest steps. One thing of note is that we're also creating an Item (as `item`). This item will form the core of the quest. With the item created, we link it to the quest (`q`), and then immediately invoke its `touch` function, passing over the player at the entity that touched it. This will basically move the item into the player's inventory (from the world where it currently lives). Doing this ensures that the behaviour of picking up an item remains the same, whether we added it manually or found it in the world. Another thing to notice is the REMOVE_INVENTORY_ITEM command in the quest steps. Notice that we're passing over the `id` of the Item we created. This ensures that we remove the correct item from the player's inventory when this command is executed. An example of a fully complete delivery script from this part might look like this: INTERACT 88 START_DELIVERY_QUEST PULSE_ENTITY 7 1 INTERACT 7 PULSE_ENTITY 7 0 REMOVE_INVENTORY_ITEM 157 MSG_BOX Johnny Lawrence;At last, my Parcel! Thank you, I've been waiting for this for ages. COMPLETE_QUEST Translated, line by line, this script would mean: - Wait for the player to interact with entity #88. - Setup the delivery quest. - Make the entity with id 7 start to pulse (they will sway back and forth, to draw our attention to them). - Wait for the player to interact with entity #7. - Make the entity with id 7 stop pulsing. - Remove the item from our inventory with id 157 - Show a message box, with the speaker as "Johnny Lawrence" and the text "At last, my Parcel! Thank you, I've been waiting for this for ages." - Mark the quest as complete. If we look at the new doRemoveInventoryItem function (invoked by executeQuestSteps), we can see this in action:
We simply extract the `id` of the Item from the quest step, and then pass that value to a function called removeInventoryItem. We'll see this in a moment. That's all the changes we need to make to quests.c. As you can see, since we've put together a strong, fluent framework, adding in new commands and steps is quite simple. We'll be expanding this further with more interesting features in the next two parts. For now, let's turn our attention to inventory.c. This is a new file that holds a number of functions related to inventory management. Our game's inventory is quite simple, being just a linked list, so all this will be easy to understand. First, we have initInventory:
Nothing overcomplicated here. We're setting up Game's inventory linked list, and also setting up a "dead" list (deadHead, deadTail). This is the list into which we'll move items that are removed from our inventory (when items are removed from the inventory, they are destroyed, never transferred). Next, we have addToInventory:
The entity (`e`) is added to our inventory's linked list, and Game's inventory item counter is incremented. We then add a message to the HUD, to say that the item was added to our inventory. Finally, we have removeInventoryItem:
This is the function that is called by our quest step script, when the REMOVE_INVENTORY_ITEM command is encountered. Simply put, this function loops through all the items in the inventory, searching for one that matches the `id` passed into the function. When the item is found, we'll display a HUD message to say the item was removed, and then remove the entity from the inventory list, before then adding it to the dead list instead. We then decrement Game's numInventoryItems. Nothing unexpected (unless you weren't expecting the item to be killed off - again, we have no use for items outside of quests, so removing them from the game is no big deal).
With our quest creation updated and inventory prepared, we should now look at how items themselves work. As expected, this all resides in a file called item.c. Before we jump into the functions themselves, we should look at one of the static variables in the file:
This variable hold an array of strings, that represent both the name of the item, and the texture filename to use. We'll be manipulating these during our item creation. Let's look at initItem now:
For the most part, this is a standard entity setup function, with an Item object (`i`) being set to the entity's `data` field. Of note here is that we're randomly selecting a string from the `filenames` array, and passing that to a function named setName. We're also using the randomly chosen filename in the texture lookup. We'll come to setName in a moment. First, let's deal with `touch` (you'll remember that we're manually invoking this in our delivery quest setup):
When the player interacts with an Item, we'll first remove it from the current map (detachEntity), then add it to our inventory (addToInventory), and finally call doQuestInteraction. This call won't do anything in our delivery quest, since no INTERACT commands will be triggered by it, but will come in handy in the future. Now for setName:
Simply put, what this function does is copies `filename` into `name`, with some changes. It does this one character at a time, capitalising the first letter, and stopping if it hits a number. So, a `filename` with the text "coin" will become "Coin", while a `filename` with the text "parcel1" will become "Parcel". This not only lets our names look nice in text displays, but also allows us to have multiple parcel graphics, without them being called "Parcel1", "Parcel2", etc. in our inventory and message boxes. That's all there is to item.c! The last major thing we have to do is update the Quest Log, so that we can actually use the Inventory section. This is going to look very similar to the other sections we recently updated, so we won't spent too long on it. Over to questLog.c, where we've updated `logic`:
For the name case SHOW_INVENTORY, we're calling doInventory:
Much like like processing the other sections, we're looping through our items. One thing to note here is that we're testing the Item's linked `quest`. Should the quest have a `town` or `island` linked to it, we'll highlight these on the map (again, only if they've been `discovered`). Next, we have `draw`:
For the SHOW_INVENTORY case, we're calling drawInventory:
As expected, we're looping through all the inventory items, and rendering the ones in view. For each item, we're rendering its `name`, its linked quest's `description`, and also drawing the item's texture on to the right of the text. Yes, very much like our other lists. This part is more or less done. Before we finish up, let's take a quick look at the detachEntity function, in entities.c:
This function merely chops an entity (`target`) out of the current Map's entity list. We loop through all the entities in the list until we find the matching one, then set its parent's (`prev`) `next` pointer to the target's `next` (thereby bypassing it). We then set the target's `next` to NULL, to ensure it's been fully isolated, and exit the function. Our first real quest is done! We can talk to Residents and be given items to delivery, with the Resident in receipt being very grateful for it. Not only that, but we also have our inventory system working, and our Quest Log is fully complete! We've got two further quest types that we want to add in - Fetch and Dispose. These will be extremely easy, as we're now able to simply build things up in the quest generation system. Once that's all done, we'll finish up the game with randomised quest types, music, sound effects, etc. 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 |