I hope that’s what it was about since that’s what I’ve answered.
It would be awkward if I’ve misinterpreted it. :P
This is a good point. I left transitions out to keep the example simple, but they are indeed important.
In the more complicated system I like to use (the only published example being Easter Panic), all state classes have an activate
and deactivate
function, (though in my case it’s not often I have to use them because class constructors and destructors are involved).
If transitions are needed, a modified version of my example would be as thus:
enum class GameState : uint8_t
{
None,
TitleScreen,
GameScreen,
// Other states
};
GameState currentState = GameState::TitleScreen;
// Having a nextState variable means that currentState
// stays valid until the next loop,
// which can be useful
GameState nextState = currentState;
// Use this function to change state
// If you decide that nextState isn't needed then it's
// easier to switch the implementation of this function
// than it is to go around changing all the cases of `nextState = something;`
void changeState(GameState gameState)
{
nextState = gameState;
}
void loop(void)
{
if(!arduboy.nextFrame())
return;
arduboy.pollButtons();
if(nextState != currentState)
{
// Deactivate the old state
// currentState is technically 'previousState' here
switch(currentState)
{
case GameState::TitleScreen:
deactivateTitleScreen();
break;
case GameState::GameScreen:
deactivateGameScreen();
break;
default:
break;
}
// Make the transition now the old state is deactivated
currentState = nextState;
// Activate the next/current state
switch(nextState)
{
case GameState::TitleScreen:
activateTitleScreen();
break;
case GameState::GameScreen:
activateGameScreen();
break;
default:
break;
}
}
// Handle updating
switch(currentState)
{
case GameState::TitleScreen:
updateTitleScreen();
break;
case GameState::GameScreen:
updateGameScreen();
break;
default:
break;
}
// Handle drawing
arduboy.clear();
switch(currentState)
{
case GameState::TitleScreen:
drawTitleScreen();
break;
case GameState::GameScreen:
drawGameScreen();
break;
default:
break;
}
arduboy.display();
}
The downside to the example I’ve given is that it’s a bit more difficult to add new states than in other systems, but I think the simplicity makes up for that a bit. (I would hope that it’s simple enough that even less experienced programmers can understand the general idea.)
@MLXXXp’s suggestion of an array of function pointers (sometimes referred to as a ‘jump table’, ‘branch table’ or ‘dispatch table’) is also a good one since it is quite easy for the compiler to optimise and will possibly generate less code in many cases.
However, it is important to remember that the values of the enum are significant and the array ought to be bounds-checked to prevent indexing with an invalid address, which could lead to some very strange bugs.
(On certain non-Arduboy systems, a lack of bounds checking could be exploited as an attack vector for arbitrary code execution.)
I think ultimately the main reason people don’t use jump tables more often is that a lot of people struggle with C++'s function pointer notation. The technique itself is fairly flexible and straightforward.