Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

Executive summary

Are there any good tutorials on Game State FSMs, particularly OOP/OOD-based and not switch-statement-based?

Summary

Is there a practical process for creating a game state engine other than using a switch statement, preferably with examples? Wikipedia's event-driven FSM article only shows a switch statement and the article on FSMs is strictly theory-based with no practical examples.

Background

Currently all my games use a very simplistic game state "engine" that is just a switch statement. It's very messy, violates DRY horribly, and does not scale very well.

Each state is treated as a level and must be checked at all three points throughout the game loop, i.e. Input, Processing, and Render. If a level or screen is added it must be added to all the switch statements.

Practical example of recreation of pac-man for educational purposes (seriously, this was a school project a few years ago):

enum STATE {
    LOADING,
    TITLE,
    LEVEL_ONE,
    LEVEL_TWO,
    LEVEL_THREE,
    LEVEL_FOUR,
    LEVEL_FIVE,
    LEVEL_SIX,
    LEVEL_SEVEN,
    WIN,
    GAME_OVER,
    CREDITS,
}GAME_STATE;


void Input() {
    keyboard->Update();

    //Check if the user wants to quit, if so, jump out.
    if(keyboard->KeyDown(KEY_ESC)) quit = true;
    if(quit) return;

    switch(GAME_STATE) {
        case LOADING:
            break;
        case TITLE:
            if(keyboard->isKeyPressed())
                NEXT_LEVEL = true;
            break;
        case LEVEL_ONE:
        case LEVEL_TWO:
        case LEVEL_THREE:
        case LEVEL_FOUR:
        case LEVEL_FIVE:
        case LEVEL_SIX:

        case LEVEL_SEVEN:

            if(keyboard->KeyPress(KEY_PAUSE)) return;
            InputCommon();
            break;
        case WIN:
        case GAME_OVER:
            if(keyboard->KeyDown(KEY_ENTER))
                GAME_STATE = CREDITS;
            break;
        case CREDITS:
            if(keyboard->isKeyPressed())
                quit = true;
            break;
    }
}

void Processing(int dead) {

    //Do nothing if user wants to quit.
    if(quit) return;

    if(keyboard->KeyPress(KEY_PAUSE)) return;

    deathTimer = dead;

    if(NEXT_LEVEL) {
        createFood();
        LoadLevel();
    }

    switch(GAME_STATE) {
        case LOADING:
            break;
        case TITLE:
            break;
        case LEVEL_ONE:
        case LEVEL_TWO:
        case LEVEL_THREE:
        case LEVEL_FOUR:
        case LEVEL_FIVE:
        case LEVEL_SIX:
        case LEVEL_SEVEN:
            ProcessLevel();
            if(GAME_STATE == LEVEL_SEVEN) {
                showWalls = true;
                sh->Index(BLEVELSEVEN)->SetAlpha(0);
                clear_bitmap(FOG_OF_WAR->GetImage());
                rectfill(FOG_OF_WAR->GetImage(), 0, 0, 226, _gw->GetHeight(), TRANSPARENT);
                rectfill(FOG_OF_WAR->GetImage(), 798, 0, _gw->GetWidth(), _gw->GetHeight(), TRANSPARENT);
                for(int i = 0; i < MAX_PLAYERS; i++) {
                    circlefill(FOG_OF_WAR->GetImage(), player[i]->GetX() + player[i]->GetSprite()->GetCenterX(), player[i]->GetY() + player[i]->GetSprite()->GetCenterY(), 90, TRANSPARENT);
                }
            }
            break;
        case WIN:
            break;
        case GAME_OVER:
            break;
        case CREDITS:
            break;
    }
}

void Render() {
    //Do nothing if the user wants to quit.
    if(quit) return;

    //Start render process.
    _gw->StartRender();

    switch(GAME_STATE) {
        case LOADING:
            break;
        case TITLE:
            sh->Draw(_gw->GetBackBuffer(), sh->Index(0));
            break;
        case LEVEL_ONE:
        case LEVEL_TWO:
        case LEVEL_THREE:
        case LEVEL_FOUR:
        case LEVEL_FIVE:
        case LEVEL_SIX:
        case LEVEL_SEVEN:

            //BACKGROUND
            RenderLevel();

            //OBJECTS
            //PILLS
            for(int i = 0; i < MAX_PILLS; i++) {
                if(pills[i]->IsAlive()) sh->Draw(_gw->GetBackBuffer(), pills[i]->GetSprite());
            }
            //FOOD
            for(int i = 0; i < MAX_FOOD; i ++) {
                if(food[i]->IsAlive()) sh->Draw(_gw->GetBackBuffer(), food[i]->GetSprite());
                if(hasFood[i] == true) sh->Draw(_gw->GetBackBuffer(), dynamic_cast<AnimatedSprite*>(food[i]->GetSprite())->GetFrames()[i], 800, 100 + (i * 32), WHITE, 0, 255);
            }
            if(showWalls) {
                //WALLS
                for(int i = 0; i < MAX_WALLS; i++) {
                    rectfill(_gw->GetBackBuffer(), walls[i]->GetX(), walls[i]->GetY(), walls[i]->GetX() + walls[i]->GetWidth(), walls[i]->GetY() + walls[i]->GetHeight(), walls[i]->GetColor());
                }
                for(int i = 0; i < MAX_HIDDEN; i++) {
                    rectfill(_gw->GetBackBuffer(), hidden[i]->GetX(), hidden[i]->GetY(), hidden[i]->GetX() + hidden[i]->GetWidth(), hidden[i]->GetY() + hidden[i]->GetHeight(), hidden[i]->GetColor());
                }
            }
            if(showWaypoints) {
                for(int i = 0; i < MAX_WAYPOINTS; i ++) {
                    rectfill(_gw->GetBackBuffer(), waypoints[i]->GetX(), waypoints[i]->GetY(), waypoints[i]->GetX() + 7,waypoints[i]->GetY() + 7, BLACK);
                }
            }
            //Copy area for teleports
            copyTeleports();

            for(int i = 0; i < MAX_PLAYERS; i++) {
                if(player[i]->IsAlive()) {
                    switch(player[i]->GetFacing()) {
                        case Actor::RIGHT:
                            sh->Draw(_gw->GetBackBuffer(), player[i]->GetSprite());
                            break;
                        case Actor::LEFT:
                            sh->Draw(_gw->GetBackBuffer(), player[i]->GetSprite());
                            break;
                        case Actor::UP:
                            sh->Draw(_gw->GetBackBuffer(), player[i]->GetSprite());
                            break;
                        case Actor::DOWN:
                            sh->Draw(_gw->GetBackBuffer(), player[i]->GetSprite());
                            break;
                    }
                } else {
                    sh->Draw(_gw->GetBackBuffer(), player[i]->GetSprite());
                }
            }
            //ENEMIES
            for(int i = 0; i < MAX_ENEMIES; i++) {
                if(enemies[i]->IsAlive()) {
                    switch(enemies[i]->GetFacing()) {
                        case Actor::RIGHT:
                            sh->Draw(_gw->GetBackBuffer(), enemies[i]->GetSprite());
                            break;
                        case Actor::LEFT:
                            sh->DrawFlip(_gw->GetBackBuffer(), enemies[i]->GetSprite(), SpriteHandler::VERTICAL);
                            break;
                        case Actor::UP:
                            enemies[i]->GetSprite()->SetRotation(-90.0f);
                            sh->DrawRotate(_gw->GetBackBuffer(), enemies[i]->GetSprite());
                            break;
                        case Actor::DOWN:
                            enemies[i]->GetSprite()->SetRotation(90.0f);
                            sh->DrawRotateFlip(_gw->GetBackBuffer(), enemies[i]->GetSprite(), SpriteHandler::HORIZONTAL);
                            break;
                    }
                }
            }
            for(int i = 0; i < MAX_PUPS; i ++) {
                if(powerups[i]->IsAlive()) sh->Draw(_gw->GetBackBuffer(), powerups[i]->GetSprite());
            }

            //MOVED FOOD TO BEFORE TELEPORT COPY SO FOOD DOESN'T GET COVERED.

            for(int i = 0; i < MAX_PLAYERS; i++) {
                drawUI(i);
            }

            //Draw area for teleports.
            drawTeleports();

            //Draw fog of war for level seven.
            if(GAME_STATE == LEVEL_SEVEN) {
                sh->Draw(_gw->GetBackBuffer(), FOG_OF_WAR);
            }

            //Show level number
            textprintf_ex(_gw->GetBackBuffer(), UI_font->GetFont(), 20, 0, WHITE, -1, "LEVEL %d", GAME_STATE - 1);

            //Show PAUSE.
            if(keyboard->KeyPress(KEY_PAUSE)) {
                textout_ex(_gw->GetBackBuffer(), UI_font->GetFont(), "PAUSED", (_gw->GetWidth() / 2) - (UI_font->GetLength("PAUSED") / 2), (_gw->GetHeight() / 2) - (UI_font->GetHeight() / 2), WHITE, -1);
            }
            break;
        case WIN:
            sh->Draw(_gw->GetBackBuffer(), sh->Index(3));
            break;
        case GAME_OVER:
            sh->Draw(_gw->GetBackBuffer(), sh->Index(1));
            break;
        case CREDITS:
            sh->Draw(_gw->GetBackBuffer(), sh->Index(2));
            break;
    }

    //End render process, display to screen.
    _gw->EndRender();

}
share|improve this question
4  
Have you tried searching for "Game state" yet? gamedev.stackexchange.com/search?q=game+state –  Patrick Hughes May 23 '12 at 20:49
1  
Sometimes, a switch/case statement is way more clear than any complicated FSM construct. It says what it does, and it does what it says. Of course this doesn't scale up very well and there are several good options for proper FSMs, so I'm not posting this as an answer, but still. I wouldn't deeply change the example you've posted, there's no need to add more complication to such a simple thing. –  Laurent Couvidou May 23 '12 at 21:34

5 Answers 5

up vote 6 down vote accepted

If you don't mind a reference to a book, there's an article named "Large-Scale Stack-Based State Machines" in Game Programming Gems 5 which provides a very good implementation of what you seek. I've been using a variation of it for the last few years, and like it a lot. Most of the insights that I got from that article, I've already talked about in this answer, so there's no need to repeat myself.

But since it might not be easy to get your hands on that book, here's a free alternative. The first chapter of the Gamebryo Textbook describes an implementation which is pretty much based on the one presented in Game Programming Gems 5, and you can also find the source code here.

(Update: The links above are no longer valid, and I can't seem to find where they have been relocated. If someone does, please edit this answer)

And finally, despite being in C# and XNA, this sample from Microsoft provides a simple implementation which should be pretty easy to follow and convert to C++.

share|improve this answer
    
The links to Gamebryo book and code are dead. –  Karlos Zafra Apr 18 at 8:05

The first step really is to just move the contents of your switch statements to data files. Level layouts and such should be in map files. Special level logic can be moved to scripts, or at the veryeast moved off into their own functions/classes. You can register these data files or C functions at startup with a level manager, and your switch statements then turn into m_CurrentLevel->update().

Note that a lot of logic really should be common. There's little reason for each level to have its own input handling, since good non-irritating games generally have only one play style. Of course exceptions abound, especially if you implement menus had a game state. Even then, though, abstract things away: have a menu handler that runs for any active menu state, a gameplay handler for game play states, etc.

If in doubt, just think in terms of (good) OOP design.

share|improve this answer
    
"...There's little reason for each level to have its own input..." True, but each state may have different input requirements but performed with the same key(s). (In the example given each actual game level just falls through to a common input-handling function while the title screen and end-game screens do different things as well) –  Casey May 25 '12 at 16:06
    
Right, but again, that can be handled at a different level. There's plenty of reasons to have menus active during game play, for instance. Instead of having different states to deal with game mode and menu mode, allow both to coexist. Even when both want to use the same keys, a (very simple) input manager can easily deal with such things. –  Sean Middleditch May 25 '12 at 18:50

I would tend to create a Scene object that all the various game states inherit from, and just create a variable called "currentScene" in the Main object. Then instead of a switch statement in the main loop, just call currentScene->Update(); each Scene would know what it specifically needs to do for input handling and rendering. To switch the game's state just switch which Scene is in currentScene.

share|improve this answer

Have a look at the Boost Statechart library. I recently started using it after many years of trying out slightly different ways of implementing FSMs, and it's definitely my current favourite! There are some oddities that stop it from being perfect (see 'state' class vs. 'simple_state' for example), but it provides some very useful features, and allows you to write state machines that are far more compact and expressive than the equivalent switch-based code.

http://www.boost.org/doc/libs/1_49_0/libs/statechart/doc/tutorial.html

share|improve this answer
    
Thanks for the link, I took a cursory glance at the article within I'll read it all later. I've had problems with Boost in the past, e.g., the most recent version has compiler (MSVC 10) errors in it. As in, when I tried to compile my code and all I had of Boost was an included header (the multi-threading one and NOTHING ELSE) a compiler error was thrown pointing to the Boost code. –  Casey Jun 15 '12 at 0:24
    
With errors like that, the error is almost always in your own code. Boost is peer-reviewed by some very very clever people, so always assume your own code is at fault. I've had MSVC give me many errors in Boost but all of them have been down to my usage of it. I'd definitely recommend giving it a second chance, it's a fantastic library which I use all the time - Statechart, numeric_cast, circular_buffer, any, optional, format, foreach (though with range-based for in VC11 RC that's now obsolete) - brilliant! –  Ben Hymers Jun 19 '12 at 7:57
    
Including a header and nothing else should not break the code. Especially if none of it is being used. That said, I consider Boost to be the Alpha stage of the next iteration of the C++ standard, the TR1 group of MSVC to be Beta and the official Standard Release of implemented stuffs to be the Gold. I may use Beta software, but I'd never be caught dead using Alpha software. –  Casey Jul 28 '12 at 3:50

Yes, there is also something called a behavior tree, which is used for AI. If you want to research them, I would suggest starting here:

Introduction to Behavior Trees

Björn Knafla explains in very easy terms what is involved with this system. I'm currently implementing this on my internship. The advantage to a behavior tree is that you're making a decision instead of setting a state.

For instance, let's say you want to build some AI for the ghosts in Pacman. You want them to follow Pacman if they see him and stop following him when they lost sight of him. So, in a behavior tree:

Walking
-- Am I following Pacman?
-- -- Have I lost sight of Pacman?
-- -- -- Stop following Pacman.
-- -- -- <End>
-- (Else)
-- -- Do I see Pacman?
-- -- -- Follow Pacman
-- -- -- <End>

So, first time we walk the tree:

Walking 
Am I following Pacman? -> No
Else
Do I see Pacman? -> Yes
Follow Pacman
End

Next frame:

Walking
Am I following Pacman? -> Yes
Have I lost sight of Pacman? -> No
End

The trick is that the behavior tree gets evaluated every frame, but it only affects behavior. So, in the case of the Ghost AI, it only determines where to walk to, but it doesn't actually walk to anywhere.

Instead, you have a separate piece of code that looks something like this:

if (m_BrainData->target == StateTarget::ePacman)
{
    MoveTowardsPacman();
}
else
{
    MoveRandomly();
}

Why is that so significant? Because now you can blend between states. Now, if you want a Ghost to run a "scared" animation whenever he sees Pacman powered up, you simply evaluate the Ghost's "brain":

if (
   m_BrainData->target == StateTarget::ePacman && 
   m_BrainData->emotion == StateEmotion::ePanic
)
{
    m_AnimationPanic->Render();
    if (m_AnimationPanic->Finished())
    {
         m_BrainData->emotion = StateEmotion::eFleeing;
    }
}
else
{
    m_Animation->Render();
}

And you add the states to the behavior tree:

Walking
-- Am I following Pacman?
-- -- Have I lost sight of Pacman?
-- -- -- Stop following Pacman.
-- -- -- <End>
-- (Else)
-- -- Do I see Pacman?
-- -- -- Is Pacman powered up?
-- -- -- -- Am I not fleeing?
-- -- -- -- -- Panic
-- -- -- -- -- <End>
-- -- -- -- (Else)
-- -- -- -- -- Flee
-- -- -- -- -- <End>
-- -- -- (Else)
-- -- -- -- Follow Pacman
-- -- -- <End>
share|improve this answer
1  
The question wasn't about game AI, where you are indeed arguably better off using something like BTs (to increase expressiveness w.r.t. FSMs, at the cost of more complexity). The question was specifically about cleanly coding a state machine for game state management, where they are actually a very acceptable approach. -- Beware en.wikipedia.org/wiki/Law_of_the_instrument –  Eric May 24 '12 at 8:53

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.