SDL Game Development
上QQ阅读APP看书,第一时间看更新

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:

  1. Go ahead and create a new file in the project called Game.h:
    #ifndef __Game__
    #define __Game__
    
    class Game
    {
    };
    
    #endif /* defined(__Game__) */
  2. Next, we can move our functions from the main.cpp file into the Game.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;
    };
  3. Now, we can alter the main.cpp file to use this new Game 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 of Game and calls the needed methods.

  4. 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:

  1. First, we must include our Game.h header file:
    #include "Game.h"
  2. Next, we can define our init function; it is essentially the same as the init function we have previously written in our main.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;
    }
  3. 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
    }
  4. 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.