— Mission-based 2D shoot 'em up — Note: this tutorial assumes knowledge of C, as well as prior tutorials.
Introduction We're ready to begin our main game loop. Our game will start at the intermission screen, progress into a mission, and then back to the intermissions screen. The player will carry over an catnip they have earned from their mission, as well as damage received, and ammunition. Entering the shop, the player will be able to repair the KIT-E, and buy upgrades. Saving will be possible, as well as the option to automatically save. Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./shooter3-22 to run the code. You will see a window open like the one above. Use the mouse to control the cursor. Select the planet Radish, then click the comms icon, and finally Start Mission to play the game as before, using the same controls. The player is immortal. Once the mission is complete, the game will return to the intermission screen. The Radish mission may be repeated as much as the player wants. Use the catnip earned to buy upgrades for the KIT-E. Once you're finished, close the window to exit. Inspecting the code It sounds like our game is basically finished. Not true, as we still have a bunch of missions to add in, and further tweaks to the code to support them. We'll get to them in due course. First up, let's look at the changes we've made to support the game loop. Starting with structs.h:
We've added a new function pointer to Entity - `destroy`. This will be called when we clear all our mission data, once the stage is finished. We might have something special we want to do other than just free the entity's `data` field if it is set, so using a function pointer gives us the means to do so. Next, let's head over to comms.c, where we've filled in the startMission function:
We're simply calling initStage. That's it, that's all we need to do..! Our [still hard-coded] mission will begin. In future, we'll be adding a bit more to do, but for now it's all we need. Now to stage.c itself. Here, we've made various updates to make sure our game loop works as expected. To begin with, we've updated initStage:
As with initIntermission, we're starting with startTransition and finishing with endTransition. In between, we're stopping any music that might be playing, saving our game (if autosave is enabled), loading some other music to play (Venus.ogg), and finally playing the music, after the transition period has ended. Simple enough. Now for the changes to `logic`:
If we're currently playing the game (`show` is SHOW_STAGE), we're now going to check if our mission is complete and missionCompleteTimer is less than MISSION_COMPLETE_STATUS_TIME. This is when we will be displaying the mission complete screen. At this point, we'll be testing the Pause control has been pressed (the game prompts us for this now). If so, we'll clear the control, and call a new funtion named completeStage. So, basically we're testing if the mission has been completed and the continue prompt is showing, before we move on. completeStage is a simple function:
We're setting Game's kite's `health` to the value of the player's current `health`, to carry it over between missions, calling a new function named clearStage, and then calling initIntermission. clearStage is a function that does as its name suggests suggests - it wipes all the data associated with the mission we're playing, to prevent memory leaks, and resource and status bleeds. It's defined thusly:
As we can see, it mostly does nothing except call several other clearXXX functions, before memsetting the Stage object itself, to reset it. Our enemyTypes array was built when we loaded the mission, so we need to loop through the array and free all the associated data, before freeing enemyTypes itself. The clearXXX functions all do the same thing, but we'll look at a few briefly now, since that's all for stage.c. If we head over to entities.c, we've added in the clearEntities function:
As you can see, it simply loops through both the entity list and the dead entity list, and removes all the entities we created, calling their `destroy` function if it is set. As another example, let's look at clearCollectables in collectables.c:
Again, just a simple function that loops through all the objects in the list, and frees their data. You may be wondering what the `destroy` function is calling for our entities. The only implementation right now is in fighters.c, where we've created a function called destroyFighter:
For our fighters, this just clears the fighter data. More complex data structures would require a bit more work here, and using a function pointer makes that possible. We'll see something more complicated in a later part. As for how it is used, if we look at greebleLightFighter.c, we can see it set to the entity's `destroy` function in initGreebleLightFighter:
We're setting the destroyFighter function pointer here. We're doing likewise with the player, in initPlayer:
Here, we're setting destroyFighter to the player's entity. That's it for our game loop! We can play our test mission as many times as we desire, earning catnip to spend on upgrades for the KIT-E, making it more powerful. Some real missions would be nice, though ... ... but before we get there, there is just one more little feature to add, that will truly bring out game alive - a scripting system. Once this is in place, our game will be transformed, with missions able to offer much more than they ordinarily would. 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 |