For those who don’t know what a FSM is, here is a summary.
I think FSM are well used in game development, from purposes like AI to game state management.
You know, you have to deal with splash screens, menus, dialogs and so forth, and you need a way to decide what to put on screen at each loop. You can arrange your code into the BUS (aka Big Ugly Switch) and check for a state flag. This works, and I used it many times. Sure, it is not beauty nor elegant (after all, it’s Big and Ugly!) and, if you plan to add features (states) later, you must put other case statement. It is Big because it grows fast.
But there is another way to do the same. If you model your software in a sequence of states, each of them can receive input and generate output, and something catch this output and put on the field another appropriate state which can continue the work, well, you have an OO solution to your problem. Which means that it’s elegant. Which means that you can speak of it with your friends. Which is cool. Well, which is not the Big Ugly Switch…
FSM are this solution. I implemented a really basic version in C++. I have two classes:
- FSMState, which is a single state in my machine
- FSMManager, which handle the states and make them work together.
In addition, I put a couple of enums which are application-related… you can get rid of them and use simple int values.
So, here are my definitions (fsm.h)
class FSMManager;
enum RawInputType
{
RIT_ESC, RIT_SPACE, RIT_AKEY
};enum HighInputType
{
HIT_IDLE=0, HIT_EXIT_GAME, HIT_SKIP_SPLASH
};enum StateId
{
// application states
SID_IDLE=0, SID_SPLASH, SID_MENU, SID_BRIEFING, SID_PLAY, SID_PAUSE,
SID_EXIT, SID_QUIT
};class FSMState
{
friend class FSMManager;public:
FSMState(int iId);// methods to program the state
void addInputPair(RawInputType, HighInputType);
void addTransition(HighInputType, int);// run the execution
virtual void run();protected:
void sendInput(RawInputType);
int getOutput();private:
int m_iId;
HighInputType m_lastInput;
std::map<RawInputType, HighInputType> m_inputTable;
std::map<HighInputType, int> m_transitionTable;};
class FSMManager
{
public:
FSMManager();void addState(FSMState*, bool current = false);
void sendInput(RawInputType);
FSMState* getCurrentState();private:
std::map<int, FSMState*> m_stateTable;
FSMState* m_pCurrentState;
};
The RawInpuType and HighInputType enums are filled with some sample values. The first maps a physical input, such the ESC key press. The second maps a logic input, application-related, such as “Player want to exit”.
FSMState has three public methods, one of them virtual:
- addInputPair simply maps physical inputs with logic inputs, in a way that is coherent with the state logic (ESC key may means “Exit from game” in the menu screen, but may means “Go to menu” while playing).
- addTransition maps the relative state to which go after a logic input is triggered. Each state has an ID which identify it (in this case it follows the StateId enums, but I used an int cause I plan to use this design to implement some AI too).
- run is virtual because real states will inherit from this class and reimplement. For test purposes I left it virtual and not pure virtual, but you can think of it as a pure virtual if you want.
The FSMManager class has a collection of states (pushed in via the addState method), but only one of them is the current state. Current state receive the raw input (which comes from another component, not present in this design) and produces an output (a new state if there is a transition, or itself) which updates the current state itself. This way a single input catching routine can pass the input through the FSMManager to the actual state, which has been programmed to give a result and maybe do a transition. At every loop the run method is called for the current state, which can perform drawings or just some logic (update of other object’s state or… what else you need).
Implementation of these methods are quite straight and boring. I put only the sendInput of the FSMManager, which actually performs the inner state transition:
void FSMManager::sendInput(RawInputType raw)
{
if(m_pCurrentState != 0)
{
m_pCurrentState->sendInput(raw);int output = m_pCurrentState->getOutput();
map<int, FSMState*>::iterator imap = m_stateTable.find(output);
if(imap != m_stateTable.end())
{
m_pCurrentState = imap->second;
}
else
{
// no state change
}
}
}
That’s all, bye.



