So in the last tutorial we created a GameObject class that took advantage of the sprite class from earlier tutorials. It didn't really do too much but it was only the start. In this tutorial we will really harness the power of C++ using inheritance and polymorphism.
So to start things off lets create a class derived from our GameObject class and call it Player, this will be a controllable character.
Create Player.h and Player.cpp
#ifndef PLAYER_H
#define PLAYER_H
#include "GameObject.h"
class Player : public GameObject // this is how we inherit from GameObject
{
public:
Player() {}
~Player() {}
void Load(char* filename);
void Update();
void Draw();
void Clean();
private:
SDL_Surface* m_pSprite;
};
#endif
#include "Player.h"
void Player::Load(char* filename)
{
/* For the moment our player does not need to have any specific loading function
so we can just use the load function from the GameObject parent class, like so. */
GameObject::Load(filename);
}
void Player::Update()
{
// nothing for now
}
void Player::Draw()
{
/* Again we are drawing a sprite so lets reuse the function found in the parent class */
GameObject::Draw();
}
void Player::Clean()
{
/* And again we free the surface using the function from game object */
GameObject::Clean();
}
Before I continue I think that you should know of a few basic rules of thumb for inheritance, a derived class should model the "is a" concept, for example here I have derived player from gameobject, this fits the rule because player "is a" game object. If you find that an object models the "has a" concept then
you should think about creating a member inside the class for that object. For example a player "has a" weapon so it should have a member of type weapon.
Another rule of thumb is to only derive from a class if an object has different behaviour not if the object only changes some data, this can be achieved using a simpler solution of member variables. I got an example from a book of if you had created an enemy class and then you derived a boss class from it, say you wanted to create a boss that was faster or had more health, would you then derive a super boss from boss? you could but you would find that the super boss class is a little empty as you only really had to
change the health and acceleration of the boss (I know at this point my player class seems to be breaking that rule but it will change and I am really just trying to show how this kind of thing works
Hope that is some food for thought, but anyway onwards...
So now in our playstate we can create an object of type player instead of game object in the same way and naruto will pop up again.
#ifndef _PLAY_STATE_H_
#define _PLAY_STATE_H_
#include "SDL.h"
#include "GameState.h"
#include "GameObject.h"
#include "Player.h" // add our new header
#include "Sprite.h"
class PlayState : public GameState
{
public:
void Init();
void Clean();
void Pause();
void Resume();
void HandleEvents(Game* game);
void Update(Game* game);
void Draw(Game* game);
// Implement Singleton Pattern
static PlayState* Instance()
{
return &m_PlayState;
}
protected:
PlayState() {}
private:
static PlayState m_PlayState;
Player * player; // create a player instead of a game object
};
Playstate.cpp
#include <stdio.h>
#include "SDL.h"
#include "PlayState.h"
PlayState PlayState::m_PlayState;
void PlayState::Init()
{
player = new Player(); // now we create a player
player->Load("naruto.bmp"); // load in the same way
printf("PlayState Init Successful\n");
}
void PlayState::Clean()
{
player->Clean(); // clean in the same way
printf("PlayState Clean Successful\n");
}
void PlayState::Pause()
{
printf("PlayState Paused\n");
}
void PlayState::Resume()
{
printf("PlayState Resumed\n");
}
void PlayState::HandleEvents(Game* game)
{
SDL_Event event;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
game->Quit();
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_RIGHT: {
break;
}
}
}
}
}
void PlayState::Update(Game* game)
{
}
void PlayState::Draw(Game* game)
{
SDL_FillRect(game->GetScreen(),NULL,0x000000);
player->Draw(); // draw in the same way
SDL_Flip(game->GetScreen());
}
Ok that should look the same as we had before when you compile it. Now we are going to code something that essentially encapsulates the entire point of OOP, hopefully I can do justice to just how powerful writing games in this way is and how much easier and more readable your code will become. Even though in the player class we added functions different to the ones in the game object class what was really happening is that the functions being called were still those of the base class (GameObject). This is where virtual functions come in.
If we wish to specify that we want to use the functions in a derived class then we need to prefix the base class functions with the virtual keyword
#ifndef GAME_OBJECT_H
#define GAME_OBJECT_H
#include "Game.h"
#include "Sprite.h"
class GameObject
{
public:
GameObject() {}
virtual ~GameObject() {}
virtual void Load(char* filename);
virtual void Update();
virtual void Draw();
virtual void Clean();
private:
SDL_Surface* m_pSprite;
};
#endif
Now when you run this code the functions called for player are those we added earlier (essentially the game object functions). This may not seem to helpful on its own but with this we can use polymorphism which means we can refer to a derived object through a pointer to its base class, now I will show you
how great this idea is.
Lets open up PlayState.h and add a container, in this case a vector of pointers to GameObjects.
#ifndef _PLAY_STATE_H_
#define _PLAY_STATE_H_
#include "SDL.h"
#include "GameState.h"
#include "GameObject.h"
#include "Player.h"
#include "Sprite.h"
#include <vector> // add the vector class
class PlayState : public GameState
{
public:
std::vector<GameObject*> GameObjects; // create a vector of pointers to game objects
void Init();
void Clean();
void Pause();
void Resume();
void HandleEvents(Game* game);
void Update(Game* game);
void Draw(Game* game);
// Implement Singleton Pattern
static PlayState* Instance()
{
return &m_PlayState;
}
protected:
PlayState() {}
private:
static PlayState m_PlayState;
Player * player;
};
#endif
And now in Playstate.cpp we can push objects into this container and then write some loops to call each of their functions
#include <stdio.h>
#include "SDL.h"
#include "PlayState.h"
PlayState PlayState::m_PlayState;
void PlayState::Init()
{
player = new Player();
player->Load("naruto.bmp");
GameObjects.push_back(player); // push the newly created player onto the vector
printf("PlayState Init Successful\n");
}
void PlayState::Clean()
{
for(unsigned int i = 0;i < GameObjects.size();i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Clean();
}
printf("PlayState Clean Successful\n");
}
void PlayState::Pause()
{
printf("PlayState Paused\n");
}
void PlayState::Resume()
{
printf("PlayState Resumed\n");
}
void PlayState::HandleEvents(Game* game)
{
SDL_Event event;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
game->Quit();
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_RIGHT: {
break;
}
}
}
}
}
void PlayState::Update(Game* game)
{
for(unsigned int i = 0;i < GameObjects.size();i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Update();
}
}
void PlayState::Draw(Game* game)
{
SDL_FillRect(game->GetScreen(),NULL,0x000000);
for(unsigned int i = 0; i < GameObjects.size(); i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Draw();
}
SDL_Flip(game->GetScreen());
}
Lets look at one of these loops in more detail
for(unsigned int i = 0;i < GameObjects.size();i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Update();
}
essentially this loop says that while i is less than the amount of objects within the container it should call the Update function, before that it checks that the vector is not empty if it is then it just skips
over. Notice we didnt have to explicitly call each function for the player, we looped through a vector of pointers to its parent class, but because of the virtual keyword the appropriate function for that derived object is called. This would work for any object derived from game object. Pretty damn cool huh?
Just to do another test lets create a game object and then a player object with a slightly larger image so we can make use of our vector and see this in action on multiple objects, of course feel free to derive more classes from game object and try them out too.
#ifndef _PLAY_STATE_H_
#define _PLAY_STATE_H_
#include "SDL.h"
#include "GameState.h"
#include "GameObject.h"
#include "Player.h"
#include "Sprite.h"
#include <vector>
class PlayState : public GameState
{
public:
std::vector<GameObject*> GameObjects;
void Init();
void Clean();
void Pause();
void Resume();
void HandleEvents(Game* game);
void Update(Game* game);
void Draw(Game* game);
// Implement Singleton Pattern
static PlayState* Instance()
{
return &m_PlayState;
}
protected:
PlayState() {}
private:
static PlayState m_PlayState;
Player * player;
GameObject * gameobject; // create another object
};
#endif
PlayState.cpp
#include <stdio.h>
#include "SDL.h"
#include "PlayState.h"
PlayState PlayState::m_PlayState;
void PlayState::Init()
{
player = new Player();
player->Load("naruto.bmp");
gameobject = new GameObject(); // create our object
gameobject->Load("ryuk.bmp"); // load a different file
/* The order you push objects onto the vector determines the order the are drawn in
so i have placed my game object on first as it is a larger image that would cover my player object */
GameObjects.push_back(gameobject); // push onto the vector
GameObjects.push_back(player);
printf("PlayState Init Successful\n");
}
void PlayState::Clean()
{
for(unsigned int i = 0;i < GameObjects.size();i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Clean();
}
printf("PlayState Clean Successful\n");
}
void PlayState::Pause()
{
printf("PlayState Paused\n");
}
void PlayState::Resume()
{
printf("PlayState Resumed\n");
}
void PlayState::HandleEvents(Game* game)
{
SDL_Event event;
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
game->Quit();
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_RIGHT: {
break;
}
}
}
}
}
void PlayState::Update(Game* game)
{
for(unsigned int i = 0;i < GameObjects.size();i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Update();
}
}
void PlayState::Draw(Game* game)
{
SDL_FillRect(game->GetScreen(),NULL,0x000000);
for(unsigned int i = 0; i < GameObjects.size(); i++) {
if(!GameObjects[i]) continue;
GameObjects[i]->Draw();
}
SDL_Flip(game->GetScreen());
}
Notice that we simply create the game object and then pushed it onto the vector, the loops took care of the rest. You should see your two images on screen in the correct order. I advise you to look at this code a few times and really get to grips with it and then try to derive your own classes from game object and push them onto the vector.
As you can see this is extremely powerful and allows for some really nice readable and reusable code, next time I will improve it even further using an abstract base class and loading objects from a file, bet you can't wait
Heres the image I used for the game object
ryuk.bmp (428.96K)
Number of downloads: 294
Happy Coding!
This post has been edited by stayscrisp: 25 April 2011 - 07:28 AM





MultiQuote






|