Lately, I've realized that game state management is always vastly overcomplicated. Here's a brain dead simple system that does everything you probably need in a straightforward way.
Just what the heck is a game state?
Well, what happens when you boot up a game? You probably see some credit to the engine, get shown "the way it's meant to be played", and maybe watch a sweet FMV cutscene. Then you get launched into the menu, where you can tighten up the graphics on level 3 and switch the controls to accommodate your DVORAK keyboard. Then you pick your favourite level, and start playing. A half an hour later, you've had too much Mountain Dew, so you have to pause the game for a few minutes to stop the action to be resumed later. That's about 4 game states right there: introduction, menu, gameplay, pause screen.Alright, how do we start coding?
The job of a state is pretty simple. Generally, it needs to update something, and then draw something. Sounds like an interface to me.
public interface State {
public void update(float dt);
public void draw();
}
You'd then have concrete states like Menu or Play that implement this interface. Now, I'm going to put a little spin on it, by changing the type of the update method.
public interface State {
public State update(float dt);
public void draw();
}
Why did I do that? Well, one of the important parts about game states is the ability to change between them. A game wouldn't be very fun if all you could do was watch the intro FMV over and over again. So the update method now returns whichever state should be used next. If there's no change, it should just return itself.
public class Menu implements State {
public State update(float dt) {
if(newGameButton.clicked()) {
return new Play("Level 1");
}
return this;
}
public void draw() {
drawSomeButtons();
}
}
Now, the state management code becomes extremely simple, and doesn't require any separate manager class or anything. Just stick it in your main method or whatever holds the game loop.
State current = new Intro();
while(isRunning) {
handleInput();
current = current.update(calculateDeltaTime());
current.draw();
presentAndClear();
}
Wait, that's it?
Yup.For real?
Nah, just kidding. Here's something really cool about this method. Take the pause state. You have to be able to unpause and return to what you were doing, unchanged, right? Usually, a stack is advocated. You push the pause state on to the stack, and pop it off when you're done to get back to the play state. You would then only update and draw the topmost state. I say, screw the stack. Have the pause state take a State in its constructor, which is stored, and then returned instead of the pause state itself when the update method detects that the game should be unpaused. If the pause screen needs to be an overlay over whatever was going on before the game was paused, that's really easy, too!
public class Pause implements State {
private State previous;
public Pause(State previous) {
this.previous = previous;
}
public State update(float dt) {
if(resumeButton.clicked()) {
return previous;
}
return this;
}
public State draw() {
previous.draw();
applyFancyBlurEffect();
drawThePauseMenu();
}
}
There we go.