• 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
— Mission-based 2D shoot 'em up —
The final part of our intermission is creating the options screen. Here, we'll be able to adjust our sound, music, controls, and other things. It's basically a standard options menu..!
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./shooter3-21 to run the code. You will see a window open like the one above. Use the mouse to control the cursor. Click and drag the mouse on the sound and music volume bars to change the values. Click on the checkboxes to toggle the autosave and show FPS options. The quit button does nothing right now. To adjust the controls (in the Configure Controls ... section), click on the control value to change, then press the new key or keypad button to wish to use (pressing Backspace will clear the value, and Escape will cancel the change). The configuration is saved to a file called config.json when navigating away from the options screen. Once you're finished, close the window to exit.
Inspecting the code
We've seen options configuration before, in SDL2 Gunner, so if you've been following this tutorial series this won't come as anything new. We'll therefore speed through this final bit. One thing you'll immediately be aware of is that we have sound and music! I thought I would leave this until we were able to control the volumes (although keep in mind that your configuration settings won't carry over to other parts, as they live in separate directories..!)
Turning to the code, we've updated structs.h:
We've added a new field here - `autosave`. This is to support the automatic saving of our games, and is tied to the options screen we're building.
With that added, we can turn to the options screen proper. options.c has a number of functions, but nothing too difficult to understand. Starting with initOptions:
Of course, we're setting up a number of widgets here, since this screen is all about the widgets. Notice we're setting a variable called `show` (static in options.c) to SHOW_GENERAL. This is to help us know whether we want to process our options widgets or our control widgets.
Next up is doOptions:
Here, we can see we're testing the value of `show`, and deciding to either call doWidgets with "options" or "control", depending on the value being SHOW_GENERAL or SHOW_CONTROLS.
The drawOptions function follows, and has near identical logic:
We either want to call drawWidgets with "options" or "controls", depending on the value of `show`.
Next up is loadConfig:
Okay, just some JSON loading going on here. We're checking if the config file exists (CONFIG_FILENAME is a define set to "config.json"), and then setting App's config's various values with those from our config file. With all that done, we're calling setSoundVolume and setMusicVolume (in sound.c) to set our volumes with the values of App's config's soundVolume and musicVolume.
saveConfig comes next:
We're just outputting the configuration data to config.json here. Nothing more that needs to be said..!
Now onto the widget action functions. Again, we'll speed through these, as it will be quite clear what they are doing. Starting with soundVolume:
We're setting the sound volume with the value of the soundVolumeWidget (note: our slider holds a value between 0.0 and 1.0, so we just multiply MIX_MAX_VOLUME, the maximum sound volume supported by SDL2, by this amount).
We're using similar logic to soundVolumne. The `autosave` function is next:
We're setting App's Config's `autosave` value to the autosaveWidget's `on` value, that will either be 0 or 1. The showFPS function comes next:
As expected, we're setting App's Config's showFPS to the showFPSWidget's `on` value, that will either be 0 or 1. Next up is the `controls` function:
When the "Configure Controls ..." button is pressed, we're setting the value of `show` to SHOW_CONTROLS, to display and process our control widgets. The final function is returnToOptions:
This function is merely setting show to SHOW_GENERAL. This function is tied to the "back" button in the "controls" widget group. We're setting the action we want to use in initOptions, so that we can return to the main options configuration after leaving the controls screen (our controls are handled in controls.c). As we've just mentioned controls.c, let's head over there now, and take a look.
We've added a number of functions to this compilation unit, all of which involve widget processing and handling..! So, we'll be speeding through this part, too. Starting with initControls:
This is just some widget setup. Nothing more to add! doControls follows:
We're just processing the "controls" widgets. drawControls is similar in nature:
We're just drawing our "controls" widgets. Next up is setupControlWidgets:
Again, this is related to our widget configuration. We're looping through all our control widgets, setting their positions, and applying the current keyboard and joypad button from App config.
The updateControls function comes next:
This function just updates all our control widgets with the values from App's config. This is called whenever we change a control, to keep everything in sync. Finally, we have the `deadzone` function:
Yet again, we're just setting Apps' Config's `deadzone` value to the value of the deadzoneWidget's value (as a percentage of DEADZONE_MAX, defined as 32000).
Okay, that's options.c and controls.c handled. As you can see, it's mostly composed of widget handling, so nothing worth lingering on.
Let's head over to init.c, where we've updated initGameOptions:
initGameOptions has been with us since the first part of this tutorial, but now we're calling loadConfig, to actually load our saved configuration (if it exists). initGameOptions sets all our defaults before doing so (such as the WASD control scheme).
Finally, let's move over to intermission.c, where we're incorporating our options screen. First up, we've updated initIntermission:
We're now testing if the `autosave` options is set, and calling saveGame in response. We're also calling initOptions, and setting section to IS_OPTIONS, to jump straight to it (for this demonstration). Also note how we're loading and playing music!
Next up, the adjustment to `logic`:
We've added the case statement for IS_OPTIONS, and are calling doOptions. Similarly, we've updated `draw`:
We've added the IS_OPTIONS case, to call drawOptions.
Finally, we've made a tweak to doSectionIcons:
We've added in some logic to see if the current section is IS_OPTIONS and the new section is a different one (in other words, we've navigated away from it). If so, we're calling saveConfig.
And there we have it, our intermission section is mostly complete. Now, what were we making again? Oh, yes - a mission and objective based 2D shooter! It feels that while we've been putting together the intermission screens, we've been neglecting the rest of the game. Well, don't be fooled. What we've done for the past several parts is get everything ready to commence with our main game loop. So, in the next part we're going to look at how to connect up missions with our intermission, to getting everything going!
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: