The Game class
So, now that we have an idea of what makes up a game, we can separate the functions into their own class by following these steps:
- Go ahead and create a new file in the project called
Game.h
:#ifndef __Game__ #define __Game__ class Game { }; #endif /* defined(__Game__) */
- Next, we can move our functions from the
main.cpp
file into theGame.h
header file:class Game { public: Game() {} ~Game() {} // simply set the running variable to true void init() { m_bRunning = true; } void render(){} void update(){} void handleEvents(){} void clean(){} // a function to access the private running variable bool running() { return m_bRunning; } private: bool m_bRunning; };
- Now, we can alter the
main.cpp
file to use this newGame
class:#include "Game.h" // our Game object Game* g_game = 0; int main(int argc, char* argv[]) { g_game = new Game(); g_game->init("Chapter 1", 100, 100, 640, 480, 0); while(g_game->running()) { g_game->handleEvents(); g_game->update(); g_game->render(); } g_game->clean(); return 0; }
Our
main.cpp
file now does not declare or define any of these functions; it simply creates an instance ofGame
and calls the needed methods. - Now that we have this skeleton code, we can go ahead and tie SDL into it to create a window; we will also add a small event handler so that we can exit the application rather than having to force it to quit. We will slightly alter our
Game.h
file to allow us to add some SDL specifics and to also allow us to use an implementation file instead of defining functions in the header:#include "SDL.h" class Game { public: Game(); ~Game(); void init(); void render(); void update(); void handleEvents(); void clean(); bool running() { return m_bRunning; } private: SDL_Window* m_pWindow; SDL_Renderer* m_pRenderer; bool m_bRunning; };
Looking back at the first part of this chapter (where we created an SDL window), we know that we need a pointer to an SDL_Window
object that is set when calling SDL_CreateWindow
, and a pointer to an SDL_Renderer
object that is created by passing our window into SDL_CreateRenderer
. The init
function can be extended to use the same parameters as in the initial sample as well. This function will now return a Boolean value so that we can check whether SDL is initialized correctly:
bool init(const char* title, int xpos, int ypos, int width, int height, int flags);
We can now create a new implementation Game.cpp
file in the project so that we can create the definitions for these functions. We can take the code from the Hello SDL section and add it to the functions in our new Game
class.
Open up Game.cpp
and we can begin adding some functionality:
- First, we must include our
Game.h
header file:#include "Game.h"
- Next, we can define our
init
function; it is essentially the same as theinit
function we have previously written in ourmain.cpp
file:bool Game::init(const char* title, int xpos, int ypos, int width, int height, int flags) { // attempt to initialize SDL if(SDL_Init(SDL_INIT_EVERYTHING) == 0) { std::cout << "SDL init success\n"; // init the window m_pWindow = SDL_CreateWindow(title, xpos, ypos, width, height, flags); if(m_pWindow != 0) // window init success { std::cout << "window creation success\n"; m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, 0); if(m_pRenderer != 0) // renderer init success { std::cout << "renderer creation success\n"; SDL_SetRenderDrawColor(m_pRenderer, 255,255,255,255); } else { std::cout << "renderer init fail\n"; return false; // renderer init fail } } else { std::cout << "window init fail\n"; return false; // window init fail } } else { std::cout << "SDL init fail\n"; return false; // SDL init fail } std::cout << "init success\n"; m_bRunning = true; // everything inited successfully, start the main loop return true; }
- We will also define the
render
function. It clears the renderer and then renders again with the clear color:void Game::render() { SDL_RenderClear(m_pRenderer); // clear the renderer to the draw color SDL_RenderPresent(m_pRenderer); // draw to the screen }
- Finally, we can clean up. We destroy both the window and the renderer and also call the
SDL_Quit
function to close all the subsystems:{ std::cout << "cleaning game\n"; SDL_DestroyWindow(m_pWindow); SDL_DestroyRenderer(m_pRenderer); SDL_Quit(); }
So we have moved the Hello SDL 2.0
code from the main.cpp
file into a class called Game
. We have freed up the main.cpp
file to handle only the Game
class; it knows nothing about SDL or how the Game
class is implemented. Let's add one more thing to the class to allow us to close the application the regular way:
void Game::handleEvents() { SDL_Event event; if(SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: m_bRunning = false; break; default: break; } } }
We will cover event handling in more detail in a forthcoming chapter. What this function now does is check if there is an event to handle, and if so, check if it is an SDL_QUIT
event (by clicking on the cross to close a window). If the event is SDL_QUIT
, we set the Game
class' m_bRunning
member variable to false
. The act of setting this variable to false
makes the main loop stop and the application move onto cleaning up and then exiting:
void Game::clean() { std::cout << "cleaning game\n"; SDL_DestroyWindow(m_pWindow); SDL_DestroyRenderer(m_pRenderer); SDL_Quit(); }
The clean()
function destroys the window and renderer and then calls the SDL_Quit()
function, closing all the initialized SDL subsystems.
Note
To enable us to view our std::cout
messages, we must first include Windows.h
and then call AllocConsole(); andfreopen("CON", "w", stdout);
. You can do this in the main.cpp
file. Just remember to remove it when sharing your game.
Fullscreen SDL
SDL_CreateWindow
takes an enumeration value of type SDL_WindowFlags
. These values set how the window will behave. We created an init
function in our Game
class:
bool init(const char* title, int xpos, int ypos, int width, int height, int flags);
The final parameter is an SDL_WindowFlags
value, which is then passed into the SDL_CreateWindow
function when initializing:
// init the window m_pWindow = SDL_CreateWindow(title, xpos, ypos, width, height, flags);
Here is a table of the SDL_WindowFlags
function:
Let's pass in SDL_WINDOW_FULLSCREEN
to the init
function and test out some fullscreen SDL. Open up the main.cpp
file and add this flag:
g_game->init("Chapter 1", 100, 100, 640, 580, SDL_WINDOW_FULLSCREEN))
Build the application again and you should see that the window is fullscreen. To exit the application, it will have to be forced to quit (Alt + F4 on Windows); we will be able to use the keyboard to quit the application in forthcoming chapters, but for now, we won't need fullscreen. One problem we have here is that we have now added something SDL specific to the main.cpp
file. While we will not use any other frameworks in this book, in future we may want to use another. We can remove this SDL-specific flag and replace it with a Boolean value for whether we want fullscreen or not.
Replace the int flags
parameter in our Game init
function with a boolfullscreen
parameter:
- The code snippet for
Game.h
:bool init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen);
- The code snippet for
Game.cpp
:bool Game::init(const char* title, int xpos, int ypos, int width, int height, bool fullscreen) { int flags = 0; if(fullscreen) { flags = SDL_WINDOW_FULLSCREEN; } }
We create an int
flags variable to pass into the SDL_CreateWindow
function; if we have set fullscreen
to true
, then this value will be set to the SDL_WINDOW_FULLSCREEN
flag, otherwise it will remain as 0
to signify that no flags are being used. Let's test this now in our main.cpp
file:
if(g_game->init("Chapter 1", 100, 100, 640, 480, true))
This will again set our window to fullscreen, but we aren't using the SDL-specific flag to do it. Set it to false
again as we will not need fullscreen for a while. Feel free to try out a few of the other flags to see what effects they have.