• (4 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »

Beginning SDL Part 4 - State Manager

#1 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 994
  • View blog
  • Posts: 4,158
  • Joined: 14-February 08

Posted 17 August 2009 - 02:32 PM

*
POPULAR

Beginning SDL - Part 4 - Game States

This tutorial has been a long time coming and for anyone who has been following the other tutorials I apologize.

In this tutorial I will cover one of the most useful topics in game programming, Game States. Game States are extremely useful, lets say you have a game with a menu screen, pause screen, play screen and game over screen.

You could go ahead and do something horribly convoluted and hard to maintain such as this-

if(menuState)
{
   // do this
}
if(playstate)
{
  // do this
}

etc....



As you can see eventually this is gonna be extremely hard to maintain and use, so the solution is to create a way to easily switch between states, remember our Game class, everything is nicely encapsulated and set out. We can use this class with our Game State class to make Game States that are essentially like separate games, these games can be easily switched between.

So first we will create a GameState.h modelled partially on our Game.h file, so create GameState.h and start coding...

#ifndef GAMESTATE_H
#define GAMESTATE_H

#include "Game.h"

class GameState
	{
	public:
		virtual void Init() = 0;
		virtual void Clean() = 0;
		
		virtual void Pause() = 0;
		virtual void Resume() = 0;
		
		virtual void HandleEvents(Game* game) = 0;
		virtual void Update(Game* game) = 0;
		virtual void Draw(Game* game) = 0;
		
		void ChangeState(Game* game, GameState* state) {
			game->ChangeState(state);
		}
		
	protected:
		GameState() { }
};
#endif



Notice the pure virtual functions (functions denoted = 0;) we use these so that we can have all our game states inherit from this GameState base class, inheritance is a topic that you should research if this concept seems strange to you.

Also notice we are passing in a pointer to a Game object, this is because GameStates may need to access some game functions as they are essentially Games themselves.

Once more thing to look at is the protected constructor, this is used so we can implement the class as a singleton, singletons are used to ensure there is only ever one instance of an object at any time, we use a protected constructor, then use a function that returns a pointer to a static instance of the class.

OK thats it for our GameState.h, since this is a pure virtual base class we will not write a .cpp file as each individual game state will overload these functions.

Now we need to make some changes to our Game.h file so open it up....

#ifndef  _GAME_H_
#define _GAME_H_

#include <SDL.h>

#include "Sprite.h"

#include <vector>

class GameState; // make sure this class knows about the GameState class.

class Game
	{
	public:
		
		Game();
		
		void Init(const char* title, int width, int height, 
				  int bpp, bool fullscreen);
		
		void ChangeState(GameState* state); // new function
		void PushState(GameState* state);  // new function
		void PopState(); // new function
		
		void HandleEvents(); // remove pointer to game class
		
		void Update();
		
		void Draw();
		
		void Clean();
		
		bool Running() { return m_bRunning; }
		void Quit() { m_bRunning = false; }
		
	private:
		
		// the stack of states
		std::vector<GameState*> states;
		
		SDL_Surface* m_pScreen;
		
		bool m_bFullscreen;
		bool m_bRunning;
		
};
#endif



Ok thats the header file, and now the cpp file......

void Game::HandleEvents()  // take pointer out and remove function body
{
}

void Game::Update() // remove any previous code from old tutorials
{
}

void Game::Draw() // remove any previous code from old tutorials
{
	SDL_Flip(m_pScreen);
}

void Game::Clean() // remove any previous code from old tutorials
{
}



OK, we removed these function bodies as we now want our individual states to handle their own drawing,updating and event handling.

Now make sure this compiles, it will just be a blank window at the moment and you will have to force it to quit.

Now we need to implement our GameStates into the Game class and also create the new functions that we wrote.

So again in the Game.cpp file


#include "Game.h"
#include "GameState.h"

// constructor
Game::Game()
{
  
}

void Game::Init(const char* title, int width, int height, 
				int bpp, bool fullscreen)
{
	int flags = 0;
	
	// initialize SDL
	SDL_Init(SDL_INIT_EVERYTHING);
	
	// set the title bar text
	SDL_WM_SetCaption(title, title);
	
	if ( fullscreen ) {
		flags = SDL_FULLSCREEN;
	}
	
	// create the screen surface
	m_pScreen = SDL_SetVideoMode(width, height, bpp, flags);
	
	m_bFullscreen = fullscreen;
	
	m_bRunning = true;
	
	printf("Game Initialised Succesfully\n");
	
}

/* 
Our new functions, ChangeState() takes a pointer to a GameState as a parameter and then pushes that state onto the vector of pointers to GameStates, before that it uses the clean function to remove the old state from the stack.
*/
void Game::ChangeState(GameState* state) 
{
	// cleanup the current state
	if ( !states.empty() ) {
		states.back()->Clean();
		states.pop_back();
	}
	
	// store and init the new state
	states.push_back(state);
	states.back()->Init();
}

/*
Whereas ChangeState() pushes a state onto the stack and removes the previous state, PushState() pauses the previous state before pushing a new state onto the stack, this state can then be removed and the previous state resumed. Extrememly useful for pausing.
*/
void Game::PushState(GameState* state)
{
	// pause current state
	if ( !states.empty() ) {
		states.back()->Pause();
	}
	
	// store and init the new state
	states.push_back(state);
	states.back()->Init();
}

/*
Remove and resume previous state.
*/
void Game::PopState()
{
	// cleanup the current state
	if ( !states.empty() ) {
		states.back()->Clean();
		states.pop_back();
	}
	
	// resume previous state
	if ( !states.empty() ) {
		states.back()->Resume();
	}
}

/*
These functions have now been changed so that they simply allow the current state to handle things, states.back() refers to the last element on the stack (the current state)
*/
void Game::HandleEvents()
{
	// let the state handle events
	states.back()->HandleEvents(this);
}


void Game::Update()
{
	// let the state update the game
	states.back()->Update(this);
}

void Game::Draw()
{
	// let the state draw the screen
	states.back()->Draw(this);
	//SDL_Flip(m_pScreen);
}

void Game::Clean()
{
	while ( !states.empty() ) {
		states.back()->Clean();
		states.pop_back();
	}
	
	// shutdown SDL
	SDL_Quit();
}



Right then our state manager is now set up, now onto creating a few states for our game.
I hope it hasn't been too hard going so far and everyone is still with me.

So check that this compiles, again you will have to force quit the program.

Now lets create a state, we will create a few states but each will simply be a different image as that is the scope of the tutorial so far, and it gives us an excuse to use our Sprite class from the previous tutorial.

So create a new file called MenuState.h

#ifndef _MENU_STATE_H_
#define _MENU_STATE_H_

#include "SDL.h"
#include "GameState.h"
#include "Sprite.h"

class MenuState : 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 MenuState* Instance()
		{
			return &m_MenuState;
		}
		
	protected:
		
		MenuState() {}
		
	private:
		static MenuState m_MenuState;
		
		SDL_Surface* menuSprite;
		
};

#endif



So thats our first state, it inherits from our GameState base class, we also created an SDL_Surface so that we can draw something to the screen.

So now create the MenuState.cpp....

#include <stdio.h>

#include "SDL.h"
#include "Game.h"
#include "MenuState.h"

MenuState MenuState::m_MenuState;

void MenuState::Init()
{	
	menuSprite = NULL; // set pointer to NULL;
	menuSprite = Sprite::Load("menustate.bmp"); // load menu state bitmap
	printf("MenuState Init Successful\n");
}

void MenuState::Clean()
{
	printf("MenuState Clean Successful\n");
}

void MenuState::Pause()
{
	printf("MenuState Paused\n");
}

void MenuState::Resume()
{
	printf("MenuState Resumed\n");
}

void MenuState::HandleEvents(Game* game) //put our exit function back in business
										 // we can now quit with cross in corner.
{
	SDL_Event event;
	
	if (SDL_PollEvent(&event)) {
		switch (event.type) {
			case SDL_QUIT:
				game->Quit();
				break;
		}
	}
}

void MenuState::Update(Game* game) 
{
}

void MenuState::Draw(Game* game) 
{
   Sprite::Draw(game->GetScreen(), menuSprite, 0, 0); // we will write 
													  // GetScreen() in a second
   SDL_Flip(game->GetScreen());
}



While writing the tutorial I realised I would have to write a function to return the screen as it is private, it is bad practice to just bluff it and make it public so try not to do it. Here is the GetScreen() function and it couldn't be simpler :)/>/>

SDL_Surface* GetScreen() {return m_pScreen;}



Just put this function in the public: part of the Game.h file.

Ok so we have now written pretty much all we need to test our first state, so lets update our main.cpp and check it out.

#include "Game.h"
#include "MenuState.h"
#include <iostream>

int main(int argc, char* argv[]) 
{
	Game game;
	
	game.Init("State Manager",640,480,32,false);
	
	game.ChangeState(MenuState::Instance());
	
	while(game.Running())
	{
		game.HandleEvents();
		game.Update();
		game.Draw();
	}
	
	// cleanup the engine
	game.Clean();
	
	return 0;
}



We added our ChangeState function and set the current game state to an Instance of the MenuState class.

Wow this is a long tutorial :D/>/> next I will show you how to move between states and also pause and resume states then I really need to stop writing this tutorial.

So we will create 2 new states PlayState and PauseState. Here is the code, paste(or type out if you like) into PlayState.h, PlayState.cpp, PauseState.h, PauseState.cpp

PlayState.h
#ifndef  _PLAY_STATE_H_
#define _PLAY_STATE_H_

#include "SDL.h"
#include "GameState.h"
#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;
		
		SDL_Surface* playSprite;
		
};

#endif



PlayState.cpp
#include <stdio.h>

#include "SDL.h"
#include "Game.h"
#include "PlayState.h"

PlayState PlayState::m_PlayState;

void PlayState::Init()
{	
	playSprite = NULL;
	
	playSprite = Sprite::Load("playstate.bmp");
	
	printf("PlayState Init Successful\n");
}

void PlayState::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;
		}
	}
}

void PlayState::Update(Game* game) 
{
}

void PlayState::Draw(Game* game) 
{
	Sprite::Draw(game->GetScreen(), playSprite, 0, 0);
	SDL_Flip(game->GetScreen());
}



PauseState.h


#ifndef  _PAUSE_STATE_H_
#define _PAUSE_STATE_H_

#include "SDL.h"
#include "GameState.h"
#include "Sprite.h"

class PauseState : 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 PauseState* Instance()
		{
			return &m_PauseState;
		}
		
	protected:
		
		PauseState() {}
		
	private:
		static PauseState m_PauseState;
		
		SDL_Surface* pauseSprite;
		
	};


#endif



PauseState.cpp

#include <stdio.h>

#include "SDL.h"
#include "Game.h"
#include "PauseState.h"

PauseState PauseState::m_PauseState;

void PauseState::Init()
{	
	pauseSprite = NULL;
	
	pauseSprite = Sprite::Load("paused.bmp");
	
	printf("PauseState Init Successful\n");
}

void PauseState::Clean()
{
	printf("PauseState Clean Successful\n");
}

void PauseState::Resume(){}

void PauseState::Pause() {}

void PauseState::HandleEvents(Game* game)
{
	SDL_Event event;
	
	if (SDL_PollEvent(&event)) {
		switch (event.type) {
			case SDL_QUIT:
				game->Quit();
				break;
		}
	}
}

void PauseState::Update(Game* game) 
{
}

void PauseState::Draw(Game* game) 
{
	Sprite::Draw(game->GetScreen(), pauseSprite, 0, 0);
	SDL_Flip(game->GetScreen());
}



So once you have these files in your project we are going to move between them. So we will start with our MenuState.cpp, so open it up....

#include "PlayState.h"  // include PlayState.h

// now we will go into the MenuState::HandleEvents(Game* game)
void MenuState::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_SPACE:
					  game->ChangeState(PlayState::Instance());
					  break;
			 }
		}
	}
}



And now we will go into PlayState.cpp

#include "PauseState.h" // include the pause state

// in handle events
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_SPACE:
				game->PushState(PauseState::Instance());
				break;
			 }
		}
	}
}



This time we use PushState(); instead of ChangeState();

now open up PauseState.cpp

void PauseState::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_SPACE:
				game->PopState();
				break;
				}
		}
	}
}



OK and we are done, compile and test :)/>/>

You should start with a menu state and then pressing space will take you to the playstate and then pressing space will pause and unpause the playstate. I know this is quite a simple demonstration but the code written here can be used in all of your 2d games, you can create states as individual levels or anything you want. Try making a menu in the menu state and then possibly a moving sprite in the playstate.

Wow that was a long tutorial, now you know why it took so long to appear. Next tutorial will focus on creating a game object base class and deriving some objects from it.

Happy Coding, as usual any questions welcome.

Here are the files I used in the project, sorry they're big :P/>/>
Attached File  paused.bmp (900.05K)
Number of downloads: 678
Attached File  menustate.bmp (900.05K)
Number of downloads: 635
Attached File  playstate.bmp (900.05K)
Number of downloads: 585

Kakashi is cool :)/>/>

This post has been edited by stayscrisp: 06 December 2012 - 03:46 AM
Reason for edit:: Small typo fixed


Is This A Good Question/Topic? 7
  • +

Replies To: Beginning SDL Part 4 - State Manager

#2 born2c0de  Icon User is offline

  • printf("I'm a %XR",195936478);
  • member icon

Reputation: 180
  • View blog
  • Posts: 4,667
  • Joined: 26-November 04

Posted 19 August 2009 - 06:15 AM

Nice work.
Psst...Naruto FTW!
Was This Post Helpful? 0
  • +
  • -

#3 scialytic  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 06-January 10

Posted 07 January 2010 - 10:28 PM

Wow, this is a great series of tutorials. It is really helping me to wrap my head around building a nice little engine. I do hope more will be coming soon.
Was This Post Helpful? 0
  • +
  • -

#4 boney  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 11
  • Joined: 17-January 10

Posted 23 January 2010 - 05:16 AM

please make the next soon! i'm getting addicted to your tutorials! never have i learned so much about how to make games and use c++ graphics in 3 hours!
Was This Post Helpful? 0
  • +
  • -

#5 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 994
  • View blog
  • Posts: 4,158
  • Joined: 14-February 08

Posted 25 January 2010 - 03:59 PM

Next part is on its way, its taking longer than expected :)
Was This Post Helpful? 0
  • +
  • -

#6 Programming_Newbie  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 17-May 10

Posted 17 May 2010 - 03:20 PM

Hi i have followed your tutorial which is great by the way, wish you had more!
However i have run into difficulies with the states, when i compile i get these error messages:

Can you tell from this what i may have missed or what i can do to fix it?

thanks

1>------ Build started: Project: Ninja Wars, Configuration: Debug Win32 ------
1>  Playstate.cpp
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\playstate.cpp(8): error C2259: 'PlayState' : cannot instantiate abstract class
1>          due to following members:
1>          'void GameState::Cleanup(void)' : is abstract
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(10) : see declaration of 'GameState::Cleanup'
1>  PauseState.cpp
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\pausestate.cpp(7): error C2259: 'PauseState' : cannot instantiate abstract class
1>          due to following members:
1>          'void GameState::Cleanup(void)' : is abstract
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(10) : see declaration of 'GameState::Cleanup'
1>  MenuState.cpp
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\menustate.cpp(8): error C2259: 'MenuState' : cannot instantiate abstract class
1>          due to following members:
1>          'void GameState::Cleanup(void)' : is abstract
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(10) : see declaration of 'GameState::Cleanup'
1>  Main.cpp
1>  Game.cpp
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\game.cpp(46): error C2039: 'Clean' : is not a member of 'GameState'
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(7) : see declaration of 'GameState'
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\game.cpp(77): error C2039: 'Clean' : is not a member of 'GameState'
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(7) : see declaration of 'GameState'
1>c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\game.cpp(113): error C2039: 'Clean' : is not a member of 'GameState'
1>          c:\documents and settings\louisa\my documents\visual studio 2010\projects\ninja wars\ninja wars\gamestate.h(7) : see declaration of 'GameState'
1>  Generating Code...
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Was This Post Helpful? 0
  • +
  • -

#7 Fib  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 161
  • View blog
  • Posts: 554
  • Joined: 12-March 09

Posted 26 May 2010 - 08:15 AM

Great tutorial stayscrisp! Game states is one thing I could never really wrap my head around, and polymorphism always kind of confused me. I'm getting there though :) Thanks! Keep them coming!
Was This Post Helpful? 0
  • +
  • -

#8 v0rtex  Icon User is offline

  • Caffeine: db "Never Enough!"
  • member icon

Reputation: 223
  • View blog
  • Posts: 773
  • Joined: 02-June 10

Posted 27 June 2010 - 12:06 AM

Awesome Work! This is exactly what I was looking for :bigsmile:
Thanks stayscrisp.
Was This Post Helpful? 0
  • +
  • -

#9 ZOMBIE!!!  Icon User is offline

  • D.I.C Head

Reputation: 27
  • View blog
  • Posts: 206
  • Joined: 28-October 09

Posted 28 July 2010 - 03:33 PM

Quick question: wouldn't SDL_Flip go into the Update function of the gamestate, considering you are updating the screen?

This post has been edited by ZOMBIE!!!: 28 July 2010 - 03:33 PM

Was This Post Helpful? 0
  • +
  • -

#10 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 994
  • View blog
  • Posts: 4,158
  • Joined: 14-February 08

Posted 29 July 2010 - 02:21 AM

SDL_Flip is part of drawing so it makes sense to have it after blitting and other drawing in the DRaw function. You could place it in your Update function but it just doesn't make as much sense to me.
Was This Post Helpful? 0
  • +
  • -

#11 Moerbius  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 01-September 10

Posted 01 September 2010 - 07:08 AM

Hi.
Thanks for all these tutorials.

I'm following it on Code Blocks with fedora 13 64 bit.

Everything works fine except when in pause state it wont change to play state again...

console output:

Game Initialised Succesfully
MenuState Init Successful
MenuState Clean Successful
PlayState Init Successful
PlayState Clean Successful
PauseState Init Successful
PauseState Clean Successful
Segmentation fault (core dumped)

Keep the good work
Was This Post Helpful? 0
  • +
  • -

#12 stayscrisp  Icon User is offline

  • フカユ
  • member icon

Reputation: 994
  • View blog
  • Posts: 4,158
  • Joined: 14-February 08

Posted 02 September 2010 - 04:20 AM

It seems to me tht you are using the ChangeState function rather than the PushState function when going into the pause state. PushState pushes a state onto the top of the stack of game states but doesn't remove the previous state, when you use ChangeState it removes the current state and pushes on a new state which means if you pop the current state off the stack there are no longer any states on the stack. Which isn't what you want.
Was This Post Helpful? 0
  • +
  • -

#13 Moerbius  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 01-September 10

Posted 02 September 2010 - 07:00 AM

You right

In play state i was using changestate instead pushstate

thanks
Was This Post Helpful? 0
  • +
  • -

#14 Regashi  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 17
  • Joined: 15-July 10

Posted 20 September 2010 - 05:03 PM

for some odd reason, i've copied everything.. but i keep getting this error when i try to build the project. heres my build log, could you help me out please?

Edit:

never mind, it seems i didn't set up the properties right. im sorry to have wasted your time.

This post has been edited by Regashi: 20 September 2010 - 05:34 PM

Was This Post Helpful? 0
  • +
  • -

#15 Fib  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 161
  • View blog
  • Posts: 554
  • Joined: 12-March 09

Posted 15 February 2011 - 02:51 PM

What is your reasoning for using a stack data structure to store game states?

My first thought would be to use a queue data structure.
Was This Post Helpful? 0
  • +
  • -

  • (4 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »