SDL2 Shooter 2 tutorial
SDL2 Widget tutorial
SDL2 Adventure tutorial
Orb source code
— Creating a basic widget system —
Start up any modern game today and the first thing you'll be met with is a title screen with a menu system, offering the chance to start a new game, load a saved game, continue, visit the options page, etc. Up until now, none of the tutorials have featured any such menu system; they just prompt the player to press Space to start, and there's no way to adjust the options or anything else. In this tutorial series, we'll see how we can create a simple, yet functional, menu system using some widgets.
Extract the archive, run make, and then use ./widgets01 to run the code. You will see a window open like the one above. Use the Up and Down arrows on you keyboard to change the highlighted menu option. There's no much else to do at this point, so once you're done close the window to exit.
Inspecting the code
Starting out, let's look at structs.h, where our Widget struct is defined:
All our widgets will have a name, which will be used to uniquely identify them. They also have x and y coordinates, for drawing, and a label, which will be the text display. Something of note is that our widgets are double-linked lists, so they have pointers to the previous widget, as well as the next in the chain. This is to allow us to move back and forth through the list more easily. We'll see more on this when we come to handling the widget logic.
Also of note is the addition we've made to the App structure (this is a common structure in these tutorials). We've added in a pointer called activeWidget:
This will be used to point at our currently selected widget. We're doing this so that we can change it from anywhere in the application, as needed.
Our main widget code lives in widgets.c. We've got four function here, which handle everything to do with our widgets. We'll start with initWidgets:
widgetHead and widgetTail are static variables within the file. Here, we're just setting up the linked list of widgets, by memsetting widgetHead, and assigning the widgetTail to widgetHead. Nothing complex. Next, let's look at createWidget:
createWidget is a factory function, for creating our widgets. It takes care of a lot of the boiler plate for us. In this function, we're mallocing a Widget, memsetting it to zero all the data, then setting up the linked list info. First, we want the widgetTail's next to point at our newly created widget. Then, we need the newly created widget's prev to point at widgetTail. Finally, we set the widgetTail to be w. Basically, we're creating a new widget, appending it to the existing list, and telling our new widget's ancestor was the original tail. With that done, we use our STRCPY macro to set the name of the widget. Finally, we return the created widget.
All good so far. The next function to consider is doWidgets. This is where we'll handle all our widget processing:
Not a lot going on in this function right now. We're testing to see if Up has been pushed on the keyboard. If so, we're zeroing the Up key (to force it to be pressed again), and then setting app.activeWidget to the active widget's prev. In other words, when we press the Up key, we're going to move backwards through our linked list. We then test to see if the active widget has been set to widgetHead. If so, we've reached the top of the list, and so set activeWidget to the widgetTail. This causes the active widget to wrap around to the end of the list when we reach the top.
We're also testing the Down key, zeroing that, and moving the active widget onto the next in the list. If our active widget is NULL, we know we've reached the end of the list, and so set the widget to the top of the list (widgetHead's next).
This simple bit of logic allows us to walk our widget list forward and backwards by simply pressing Up and Down on the keyboard, wrapping around to the top or bottom of the list, as needed. The last function to look at in this file is drawWidgets:
Again, a simple function. We're looping through all the widgets in our linked list, and caling drawText for each, using the widget's label, and x and y coordinates. Something we're also doing is testing whether the widget we're drawing is the active widget. Depending on this, we want to change the colour of the text. If the widget is the active one, we'll set our SDL_Color variable (c) to be green, and also draw a right chevron symbol on the left-hand side, to add further visual clarity. Otherwise, we'll draw it in white.
That's it for widgets.c. For this tutorial, we'll be making use of a file called demo.c to handle all our main logic and drawing. Starting with initDemo:
Here, we're calling our createWidget function several times, to create 5 widgets: start, load, options, credits, and exit. For each one, we're setting the label, x, and y variables. Notice also that we're assigning app.activeWidget as the first widget we create: start. This will mean that our start widget is the one selected when our logic phase begins. Without this, nothing would be highlighted when drawing widgets (and it would also crash as soon as we push up, since we're next expecting the activeWidget to ever be NULL).
We're also assigning the app delegates to demo.c's logic and draw. These two functions are extremely basic, as we'll see, starting with logic:
logic does nothing but call doWidgets. Likewise, draw does nothing but call drawWidgets:
We've nothing else going on in demo.c right now, as all the widget processing is handled in widgets.c.
We're almost done with this short introduction, so let's quickly look at where the main setup happens. We're calling initWidgets in initGameSystem, in init.c:
We also need to call initFonts (done in text.c), so that we can draw our text. Finally, our call to initDemo happens in main (in main.c):
We're done with this very basic start to our widget system. Not a lot happens right now, but in the next tutorial, we'll look into allowing the widgets to invoke actions when they are selected.
The source code for all parts of this tutorial (including assets) is available here:
It is also available as part of the SDL2 tutorial bundle (with on-going updates):