Tutorial Index

Tutorial 2 - SDL Setup: A simple Game Loop


Introduction

In this tutorial, we'll build a basic program shell and game loop using SDL, which forms the basis for a 3D game engine. Later tutorials will build on this design, adding graphics, code and additional functionality to the game engine.

Concepts

Basic C++ programming
Object Technology
SDL Concepts
Subsystem Initialization
Event Loops

List of Files

Makefile - Used to 'make' and 'make clean' our program
main.cpp - contains game loop
module_registry.h - Registers program modules and associated memory management
module_registry.cpp
system_controller.h - Instantiates all major modules and handles event loops
system_controller.cpp
window.h - Instantiates SDL subsystems, handles screen updates
window.cpp

SDL Concepts

SDL is a multimedia library providing support for handling user input and most audio and video functionality, In these tutorials, we will use SDL for video rendering, audio mixing, and key/mouse handling.

Subsystem Initialization

SDL is modular, allowing us to load specific subsystems when needed. Until we load modules, those subsystems and their corresponding functionality will be inaccessible. For any modules that we load, we need to unload them upon termination of our program.

Initial SDL funtionality

Initialization:

SDL_Init(<flags>)

Restoring the system at termination:

SDL_Quit()

If at a point after SDL_Init() was called we desire to initialize another, previously uninitialized subsystem, we need to use a different function:

SDL_InitSubSystem(<flags>)

Examples of the flags we can use with the initialization functions can be found here.

So for our program we need to initialize the video subsystem:

SDL_Init(SDL_INIT_VIDEO)

This requests SDL to initialize the video subsystem. It has a return code of 0 for success, -1 otherwise. Once this is done, we need to create a surface upon which we may render things:

SDL_SetVideoMode(640, 480, 0, SDL_HWSURFACE)

In this case, we requested SDL to create a hardware-accelerated video surface of dimension 640x480 pixels, with a color depth of 0, which defaults to the current OS color depth. We now have a video surface to render! The combination of these two functions, SDL_Init and SDL_SetVideoMode, will create an SDL Window with the properties that correspond to the natural look of the operating system (OS). Inside the window will be a black portion of screen; this is our video rendering surface! But what do we render? We will describe video rendering in more detail later; we must first look at other portions of SDL.

Event Loops

Since SDL will handle I/O for our game engine, we need an event loop. An event loop functions almost exactly as it sounds: it is an infinite loop that waits for events, and upon receiving certain events, calls functions that modify the state of objects in the program. An example event loop is:

while(true){
      poll_for_event();
      if(event is detected){
           if(event is button press){
                handle_button();
           }
           if(event is mouse movement){
                handle_mouse_movement();
           }
     }
}

In the above example, we use an event loop to detect input from the player. When the player hits certain keys, the game responds accordingly, possibly shooting a missile or opening a box. Or, the actions might be much more simple: the user may have requested to exit the program. Exiting the program is an important operation, since without this the program will never terminate without some outside force killing the program in an ungraceful manner. We want to avoid this, so for this example, we focus solely on handling input used to exit the program.

SDL provides a built in event subsystem that makes it easy to handle events using a built-in structure: SDL_Event. SDL_Event is a union that contains a snapshot of the current state of the event system, such as keyboard presses, mouse movement, or other window based events. SDL_Event is composed of more specific SDL_Event structures, but that's more information than we currently require. The important idea here is that SDL_Event contains information about the current event.

To obtain a fresh copy of an event, SDL_Event, we must poll or wait for one. SDL provides many ways to update event information; for now we use:

SDL_PollEvent(<event pointer>)

This will fill up an event pointer with event information that we can examine and decide what we need to do next. This can be done in the following manner, but this is by no means the only way:

switch(event.type){
      case SDL_QUIT:
           return;
      case SDL_KEYDOWN:
           //key handling code
}

In this example, we examine the event's type, which is an enumeration describing the "type" of event to which this event structure corresponds. SDL_QUIT corresponds to the user clicking the close button on the SDL_Window GUI. SDL_KEYDOWN corresponds to the user hitting some key on the keyboard. Well what key was hit? We can find this out by digging further into SDL_Event. Another important member of the structure is key which corresponds to an SDL_KeyboardEvent. SDL_KeyboardEvent contains a 'screenshot' (keysym) of the current state of a keyboard (which keys are pressed). Using this, we can determine exactly what key was pressed, and then decide what we want to do with it!

if(event.key.keysym.sym == SDLK_ESCAPE){
      return;
}

Where key.keysym.sym contains a specific key event. We can just compare the sym with the enumerations provided by the SDL_Key class, which can be found here.

We have presented a basic rundown of handling keyboard events; however, this example does not handle simultaneous multiple keystrokes, or mouse input. We'll describe techniques to handle simultaneous keystrokes and mouse input in later tutorials.

Class Hierarchy

main.cpp is the root class that instantiates a copy of system_controller. This, in turn instantiates a copy of module_registry, and window. Thus, system_controller is aptly named as it acts like a hub, connecting all other modules.

system_controller, in later tutorials, will house more and more modules, and module_registry will handle all of the main memory management for these modules.

Class Explaination

Note that all files have comments in them at this stage in the game (possibly overlapping information with this web tutorial), in later tutorials the comments will be removed from areas of the program that have not changed from the last tutorial. This is done to reduce code bloat, and keep things looking clean. This shouldn't be a problem, as the first time a concept is introduced, it should be well documented. The next time a programmer encounters it, the concept should be understood, and the need for excess commentary is removed. As well, this system of documentation allows the programmer to zone in on new areas of code quickly, as only the commented areas are the important ones on which the programmer need focus.

main

The sole purpose of main is to instantiate a local copy of system_controller, and upon success, run the associated event loop. Also included is a try/catch exception block to handle any exceptions that may be thrown during the execution of our program. This main file is common to all future tutorials, however it will never change, and as such, will not be mentioned again.

module registry

The module registry is really an unnecessary class, but it was created for the purpose of acting like a central repository for any resources the game engine might need. The idea is that our game engine should be OO in design, with everything being a module. We simply plug it in and go. Well based on my previous experience with engine design, I noticed that almost every module needed access to other modules (input manager needed access to the rules module, sound module, window module). Instead of coding hard links to the other modules as private data members, I decided to create a level of indirection that cleaned up the classes a bit. Now, if a module needed access to another resource or module, it would request the module registry for said resource. Now each module only need a hard link to the module_registry itself.

As well, because any module that is to be accessible must register with the module registry, we can delegate the registry to handle all memory management associated with the modules. That is, once a module is registered, registry is responsible for deallocating the modules upon exit. Like I said, the class isn't really necessary, but it cleans up system controller quite a bit to where it's pretty obvious what every part of system controller is supposed to do. For any accessible module, two components are required to have things register properly with the module registry. The registry must accept the module with a register_module function, and then then the registry calls module->set_module_registry(*this); This is, like the visitor pattern, a double dispatch, as two separate calls are required to have this one action to complete. Once this is done, the module registry will have a link to the module, and the module will have a link to the registry.

system controller

The system controller is built with the idea that it ties all of the other pieces of the system together, initializing the modules properly, and starting the event loop. It's pretty simple in design, but gets slightly more complex in later tutorials.

window

Window represents our SDL window, and simply contains functionality to start said functionality. Eventually, it will also contain the necessary code to update the window with updated graphical information (drawing the frames), but for now we only need perform initializations.

References

SDL Documenation Wiki
Tutorial Code

Conclusion

Upon building the included test files, you should see a black SDL window pop up. That's it. That's all this entire tutorial does. Not very impressive, huh? Wait! Hit the escape key! Now start it up again, and hit the x (close) button. See, that's event handling, the code we have in there now takes into account those two specific events, and in both cases, exits the program normally.

Now you've got a basic shell inside of which we will continue to build our game engine in future tutorials. We've seen how to initialize and shut down SDL and how to handle events from the keyboard. In the next tutorial, we'll see how OSG ties into everything.