Question about using "Game States"

Started by
10 comments, last by BambooCatfish 11 years, 3 months ago

Ok so lets say I have this:



enum GameState
{
   load, pause, idle, movePlayer, etc...
}

how is a good way to handle being in different states? The only way I know would be to use conditional branches in the update loop. ie:




privave void update()
{
     if (gameState == GameState.something)
              updateGameStateSomething();
     else if (gameState == GameState.somethingElse)
              updateGameStateSomethingElse();
     
     //etc...


}

A better way?

Thanks

Nick

Advertisement
I use state classes instead. Rather than have an enum for what state the game is in, I use a different type for each state with a common interface. Then I call a virtual function on the state object for the various things like updates, render, etc.
L.Spiro's excellent article:

http://lspiroengine.com/?p=351
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

You should use the simplest solution that accomplishes the task. If you only have need for a simple conditional to handle the states, then that's a good way to do it. I like OOP and use it a lot in my engine, but there's no need to over-complicate things with it if it's not appropriate.

It's always easier to start with simple code and make it complex later if you need to, than it is to start with complex code and make it simple later.

I like LazyFoo's state machines article myself:

http://lazyfoo.net/articles/article06/index.php

Right on, thank you guys for this info!

I actually had the L Spiro one saved in my bookmarks byt forgot about it (wayy to many folders/nesting :-/ ). I read through them both a couple of times and am still tryin to wrap my head around it.

So in the L Spiro one each state is a class/object inherited from a base class "GameState" or something and the information is passed from a instance/pointer of the Game class/object, with each state grabbing what it needs? I like this approach if thats the case.

The other one is alot to read and I have to read it a few times to get more of a understanding from it.

A follow up question... I am using scenes right now, similar to the states TitleScene, GameScene, OptionsScene, etc. I had thought the scenes would handle the states internally but from these approaches I should ditch scenes and make each scene a state? Would that mean its better to keep all my data inside the main game class instead of some sort of scene Object?

Right now i have something that looks like this ( in general)


class MainGame()
{
   
     SceneObject scene;

   public MainGame(){} //constructor

    pubic void init(){} // init method.
 

    public void update()
   {
     scene.update();
   }

   public  void draw()
  {
       scene.draw();
   }

   
  void changeScene(SceneObject newScene){}

}


class GameScene: SceneObject
{
      List<Enemies> enemies;
      PlayerObject playerObject;
     TIleMap map;
 
     public SceneObject() {} // constuctor

     public void init() {}   // init method.
      
     pubic void update(){}   //update stuff
     
     public void draw() {}   //daw stuff.
}

If this doesnt make sense let me know and I will try to explain better.

Sorry double post.... I had a thought.

I may have asked the wrong question or got my terminology wrong (still got some good info though!).

The project I am working on is (going to be) a simple turn based game where the player can move X number of sqares on a tile map and attack once per turn, then the enemies do the same. I am currently working on one thing at a time and right now the move action is what I am working on. I was thinking about states for this perticualar action:

1) pre-move state : checks what tiles are available to potentially move to. any tiles that are bad are displayed red.

2.) get user input: lets user use keyboard/mouse to select tile to move to.

3.) perform move: if tile is good for moving, move player there.

4.) clean up: reset whatever variables are needed.

Thats what I was thinking about when talking about states... handled the same way? Then if i have an attack action:

1.) check what is close enough to attack.

2.) let user to select from list of potential targets.

3.) attack target, deal damage, add effects, etc.

4.) check for death, clean up, etc.

i dont know how to handle that modularly.

The scene is like a mini-program. It's a unit of exchangeable logic that can be plugged in to the main loop. You can (and probably will) have some sub-states within a scene, but if you get enough that things get cluttered then you should probably look at modifying your scene structure some, or using an internal dispatcher for a specific scene, since it looks like that's what we're going to be discussing here.
1) pre-move state : checks what tiles are available to potentially move to. any tiles that are bad are displayed red.
2.) get user input: lets user use keyboard/mouse to select tile to move to.
3.) perform move: if tile is good for moving, move player there.
4.) clean up: reset whatever variables are needed.

In cases like this it doesn't make sense to have multiple scenes (since we'd just end up hauling a huge amount of state from scene to scene in a big circle), but we definitely want specific stages to the repeating logic within the scene itself, and a small and simple finite state machine can make that happen for us.

First thing to do is to make room for all of the state within the scene to live comfortably. That means let everything have its own state variable that exists even when that specific variable isn't being altered by the current state. This way we can have the option of 'backing up' to the previous state and still having sensible values from when we left it.

Second thing, use bigLongFunctionNamesThatAreVeryExplicitEvenThoughTheyLookFunny() to handle each state branch in the series.

Finally, set up the scene's update function to implement the state machine and just switch on the current state.
int update() {
  switch(m_phase) {
    case START_TURN: doPhaseStartTurn(); break;
    case PLAYER_INPUT_MOVEMENT: doPhasePlayerInputMovement(); break;
    case OTHER_CRAP: doPhaseOtherCrap(); break;
    default: x /= 0;
  }
}

Alternatively you can put function pointers in an array or container and call by index based on the phase.

Now that will work up to a pretty good degree of complexity, but if things get too crazy for that kind of architecture then you can implement a state-stack, which would be a situation where instead of there being a single active state that's updated by the main loop there's a state in the main loop which can contain a state that can contain a state, and so on. That takes up more resources since each active state in the stack has all of its resources loaded even if they're not being used by the member state, but it also means that each state can have access to the resources of the states that are below it on the stack.

For instance if you have your main menu at the bottom of the stack, then your world map on top of that, then your item menu on top of that then you can proc the world map's draw() function from your item menu scene. You could draw the world map, apply a blur filter, then draw the menus on top of that for some nice effects and better immersion. When you're done with the item menu then you just signal its destruction to the world map that owns it and then on the next frame the world map can destroy the menu scene and resume normal execution. If you want to return to the main menu then the world map signals to the man menu state that it's done and then the main menu destroys the world map scene and takes control of execution on the next frame, etc.

I don't know if I explained that clearly. Did it make sense?
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

Thanks it makes sense. That's how I was doing things. I guess I'll just keep doing that until it becomes to complex and I need to change.

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.

This topic is closed to new replies.

Advertisement