We can now put all of this knowledge together and implement as much as we can into our framework, with reusability in mind. We have quite a bit of work to do, so let's start with our abstract base class, GameObject
. We are going to strip out anything SDL-specific so that we can reuse this class in other SDL projects if needed. Here is our stripped down GameObject
abstract base class:
class GameObject { public: virtual void draw()=0; virtual void update()=0; virtual void clean()=0; protected: GameObject(const LoaderParams* pParams) {} virtual ~GameObject() {} };
The pure virtual functions have been created, forcing any derived classes to also declare and implement them. There is also now no load
function; the reason for this is that we don't want to have to create a new load
function for each new project. We can be pretty sure that we will need different values when loading our objects for different games. The approach we will take here is to create a new class called LoaderParams
and pass that into the constructor of our objects.
LoaderParams
is simply a class that takes values into its constructor and sets them as member variables that can then be accessed to set the initial values of an object. While it may just seem that we are moving the parameters from the load
function to somewhere else, it is a lot easier to just create a new LoaderParams
class than to track down and alter the load
function of all of our objects.
So here is our LoaderParams
class:
class LoaderParams { public: LoaderParams(int x, int y, int width, int height, std::string textureID) : m_x(x), m_y(y), m_width(width), m_height(height), m_textureID(textureID) { } int getX() const { return m_x; } int getY() const { return m_y; } int getWidth() const { return m_width; } int getHeight() const { return m_height; } std::string getTextureID() const { return m_textureID; } private: int m_x; int m_y; int m_width; int m_height; std::string m_textureID; };
This class holds any values we need when creating our object exactly the same way as our load
function used to do.
We have also removed the SDL_Renderer
parameter from the draw
function. We will instead make our Game
class a singleton, such as TextureManager
. So, we can add the following to our Game
class:
// create the public instance function static Game* Instance() { if(s_pInstance == 0) { s_pInstance = new Game(); return s_pInstance; } return s_pInstance; } // make the constructor private private: Game(); // create the s_pInstance member variable static Game* s_pInstance; // create the typedef typedef Game TheGame;
In the Game.cpp
, we have to define our static instance:
Game* Game::s_pInstance = 0;
Let's also create a function in the header file that will return our SDL_Renderer
object:
SDL_Renderer* getRenderer() const { return m_pRenderer; }
Now that Game
is a singleton, we are going to use it differently in our main.cpp
file:
int main(int argc, char* argv[]) { std::cout << "game init attempt... "; if(TheGame::Instance()->init("Chapter 1", 100, 100, 640, 480, false)) { std::cout << "game init success! "; while(TheGame::Instance()->running()) { TheGame::Instance()->handleEvents(); TheGame::Instance()->update(); TheGame::Instance()->render(); SDL_Delay(10); } } else { std::cout << "game init failure - " << SDL_GetError() << " "; return -1; } std::cout << "game closing... "; TheGame::Instance()->clean(); return 0; }
Now when we want to access the m_pRenderer
value from Game
, we can use the
getRenderer
function. Now that GameObject
is essentially empty, how do we achieve the code-sharing we originally had? We are going to derive a new generic class from GameObject
and call it SDLGameObject
:
class SDLGameObject : public GameObject { public: SDLGameObject(const LoaderParams* pParams); virtual void draw(); virtual void update(); virtual void clean(); protected: int m_x; int m_y; int m_width; int m_height; int m_currentRow; int m_currentFrame; std::string m_textureID; };
With this class we can create our reusable SDL code. First, we can use our new LoaderParams
class to set our member variables:
SDLGameObject::SDLGameObject(const LoaderParams* pParams) : GameObject(pParams) { m_x = pParams->getX(); m_y = pParams->getY(); m_width = pParams->getWidth(); m_height = pParams->getHeight(); m_textureID = pParams->getTextureID(); m_currentRow = 1; m_currentFrame = 1; }
We can also use the same
draw
function as before, making use of our singleton Game
class to get the renderer we want:
void SDLGameObject::draw() { TextureManager::Instance()->drawFrame(m_textureID, m_x, m_y, m_width, m_height, m_currentRow, m_currentFrame, TheGame::Instance()->getRenderer()); }
Player
and Enemy
can now inherit from SDLGameObject
:
class Player : public SDLGameObject { public: Player(const LoaderParams* pParams); virtual void draw(); virtual void update(); virtual void clean(); }; // Enemy class class Enemy : public SDLGameObject { public: Enemy(const LoaderParams* pParams); virtual void draw(); virtual void update(); virtual void clean(); };
The Player
class can be defined like so (the Enemy
class is very similar):
Player::Player(const LoaderParams* pParams) : SDLGameObject(pParams) { } void Player::draw() { SDLGameObject::draw(); // we now use SDLGameObject } void Player::update() { m_x -= 1; m_currentFrame = int(((SDL_GetTicks() / 100) % 6)); } void Player::clean() { }
Now that everything is in place, we can go ahead and create the objects in our Game
class and see everything in action. We won't add the objects to the header file this time; we will use a shortcut and build our objects in one line in the init
function:
m_gameObjects.push_back(new Player(new LoaderParams(100, 100, 128, 82, "animate"))); m_gameObjects.push_back(new Enemy(new LoaderParams(300, 300, 128, 82, "animate")));
Build the project. We now have everything in place to allow us to easily reuse our Game
and GameObject
classes.
18.225.95.245