I know people have already given some pretty good answers, but I'll throw in my two cents.
Just giving you the .hpp file, I have a GameStateManager and a GameStateObject (the intention of the latter is to be extended by classes that are a game state - such as main menu, playing level, editing level, etc.)
Both are in one .hpp:
#ifndef GAME_STATE_MANAGER_HPP
#define GAME_STATE_MANAGER_HPP
#include "Graphics.hpp"
#include "GlobalDefines.hpp"
#include <stack>
class GameStateManager;
class GameStateObject
{
public:
/* Passed in parameter is the game state name. Can be left blank for an auto-generated game state name
* If a non-empty string is passed in, it must not begin with _ (underscore) - if it does, the auto-generated name is used
*
* gameStateManager MUST NOT be null - otherwise you cannot use this as a valid game state object
*
* If you want multiple instances of an object to be allowed as separate states, then leave the string blank
* If you only want 1 instance of the object to be allowed as a state, specify your own name here
*/
GameStateObject (GameStateManager *gameStateManager, std::string gameStateName = "");
~GameStateObject ();
// called every time this state becomes active
virtual void OnEnterState () { }
// called right before this state is left
virtual void OnLeaveState () { }
// a game state object should at least draw something
virtual void Draw2d () = 0;
virtual void Update (double dTime) { }
virtual void MouseClick(sf::Mouse::Button button, sf::MouseState state, int x, int y) { }
virtual void MouseMoved(int x, int y, int dx, int dy) { }
virtual void MouseDragged(int x, int y, int dx, int dy) { }
virtual void MouseWheel(int dir) { }
virtual void KeysDown(sf::Event::KeyEvent key) { }
virtual void KeysUp(sf::Event::KeyEvent key) { }
virtual void TextEntered(char ch) { }
virtual void Resize(int w, int h) { }
virtual void FocusChanged(bool gained) { }
std::string GetName ();
private:
std::string m_gameStateName;
GameStateManager *m_gameStateManager;
static std::string GetUniqueName ();
};
class GameStateManager
{
public:
GameStateManager();
/* Push a new state on the state stack.
* stateName must be a valid name of a state object that has already been added through AddState
* Returns true if the state was found in the pool of states, false otherwise
*/
bool PushState ( std::string stateName );
/* Pops the current top-of-the-stack state off the stack, and returns control to the previously pushed state
* This function does not remove the state from the pool at all
*/
std::string PopState ();
GameStateObject* GetCurrentState ();
// the update functions
void Draw2d (); // a game state object should at least draw something
void Update ( double dTime );
void MouseClick ( sf::Mouse::Button button, sf::MouseState state, int x, int y );
void MouseMoved ( int x, int y, int dx, int dy );
void MouseDragged ( int x, int y, int dx, int dy );
void MouseWheel ( int dir );
void KeysDown ( sf::Event::KeyEvent key );
void KeysUp ( sf::Event::KeyEvent key );
void TextEntered ( char ch );
void Resize ( int w, int h );
void FocusChanged ( bool gained );
private:
// The following two functions are private, because they're only called by the GameStateObject constructor
/* Adds a state to the pool of states.
* This function does not make the newly added state active, it only allows it to be made active later on.
* stateName MUST be a non-empty string
* If a state with the specified stateName already exists, return is false, otherwise it's true
*/
bool AddState ( GameStateObject *stateObject ); // stateName must be a non-empty string, and must not repeat with a previous state
/* Removes a state from the pool of states.
* If the state to be removed is currently the active one (on top of the stack), then it's popped off before being removed from the pool.
* Returns true if state is found, false otherwise
*/
bool RemoveState ( std::string stateName );
std::stack <GameStateObject*> m_statesStack;
std::vector<GameStateObject*> m_states;
class EmptyState : public GameStateObject
{
public:
EmptyState (GameStateManager *gsm) : GameStateObject(gsm, "") { }
void Draw2d () { }
};
GameStateObject *m_emptyState; // to avoid having lots of 'if' 'else' comparisons
bool m_needToPopTop;
friend GameStateObject;
};
#endif
The startup code pushes a MainMenu class that extends the GameStateObject.
The thing that works fairly well with this is that a state doesn't need to know what it's parent state is, all it has to call is GameStateObject::PopState() to return full control to the previous state. Seems to have worked fairly well for me so far.