First, a little background. Unreal Engine 4 has a pretty pivotal class called AGameMode , which every game has to have. If you look at the documentation for GameMode, it says that “The AGameMode class defines the game being played, and enforces the game rules.”

I’ve never seen an actual example of that, so I have no idea how it’s ideally supposed to happen. In practice, it does some very important stuff, like starting and restarting players, delegating login to AGameSession , etc.

Unfortunately, it also does some stuff that more advanced game developers won’t want. AGameMode includes a hardcoded state machine, which at certain states does stuff like spawning players, allows network connections, etc. For someone (an engineer) who wants control over when and how that happens, it’s a bit maddening.

It also appears to have been designed with one game type in mind (a multiplayer FPS, perhaps?)

Here’s the possible states:

/** Possible state of the current match, where a match is all the gameplay that happens on a single map */ namespace MatchState { extern ENGINE_API const FName EnteringMap; // We are entering this map, actors are not yet ticking extern ENGINE_API const FName WaitingToStart; // Actors are ticking, but the match has not yet started extern ENGINE_API const FName InProgress; // Normal gameplay is occurring. Specific games will have their own state machine inside this state extern ENGINE_API const FName WaitingPostMatch; // Match has ended so we aren't accepting new players, but actors are still ticking extern ENGINE_API const FName LeavingMap; // We are transitioning out of the map to another location extern ENGINE_API const FName Aborted; // Match has failed due to network issues or other problems, cannot continue // If a game needs to add additional states, you may need to override HasMatchStarted and HasMatchEnded to deal with the new states // Do not add any states before WaitingToStart or after WaitingPostMatch } 1 2 3 4 5 6 7 8 9 10 11 12 13 /** Possible state of the current match, where a match is all the gameplay that happens on a single map */ namespace MatchState { extern ENGINE_API const FName EnteringMap ; // We are entering this map, actors are not yet ticking extern ENGINE_API const FName WaitingToStart ; // Actors are ticking, but the match has not yet started extern ENGINE_API const FName InProgress ; // Normal gameplay is occurring. Specific games will have their own state machine inside this state extern ENGINE_API const FName WaitingPostMatch ; // Match has ended so we aren't accepting new players, but actors are still ticking extern ENGINE_API const FName LeavingMap ; // We are transitioning out of the map to another location extern ENGINE_API const FName Aborted ; // Match has failed due to network issues or other problems, cannot continue // If a game needs to add additional states, you may need to override HasMatchStarted and HasMatchEnded to deal with the new states // Do not add any states before WaitingToStart or after WaitingPostMatch }

As you can see just from the names, there’s things there that won’t apply to certain game types. What’s a match? My single player RPG doesn’t have a match. What does EnteringMap mean? No idea, my game doesn’t use the Unreal maps.

Another side effect is that in a couple places in the engine code, it sets the state of the FSM. This kind of thing, with engine code reaching into game code is bad, and we’ll remove it.

Now, this is combined with a hardcoded state machine. This is how you change states in this state machine.

void AGameMode::SetMatchState(FName NewState) { if (MatchState == NewState) { return; } MatchState = NewState; // Call change callbacks if (MatchState == MatchState::WaitingToStart) { HandleMatchIsWaitingToStart(); } else if (MatchState == MatchState::InProgress) { HandleMatchHasStarted(); } else if (MatchState == MatchState::WaitingPostMatch) { HandleMatchHasEnded(); } else if (MatchState == MatchState::LeavingMap) { HandleLeavingMap(); } else if (MatchState == MatchState::Aborted) { HandleMatchAborted(); } if (GameState) { GameState->SetMatchState(NewState); } K2_OnSetMatchState(NewState); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void AGameMode :: SetMatchState ( FName NewState ) { if ( MatchState == NewState ) { return ; } MatchState = NewState ; // Call change callbacks if ( MatchState == MatchState :: WaitingToStart ) { HandleMatchIsWaitingToStart ( ) ; } else if ( MatchState == MatchState :: InProgress ) { HandleMatchHasStarted ( ) ; } else if ( MatchState == MatchState :: WaitingPostMatch ) { HandleMatchHasEnded ( ) ; } else if ( MatchState == MatchState :: LeavingMap ) { HandleLeavingMap ( ) ; } else if ( MatchState == MatchState :: Aborted ) { HandleMatchAborted ( ) ; } if ( GameState ) { GameState -> SetMatchState ( NewState ) ; } K2_OnSetMatchState ( NewState ) ; }

As an engineer who is very fond of my Finite State Machines, this makes me cringe, a lot. It’s very difficult to expunge this hardcoded FSM as well, the easiest way to do that is to split AGameMode (and essentially leave it alone) and make a new base class. That’s what I’ve done, and what this series of articles will document.

These changes are currently in a branch in my own fork of UE4, and I will be contributing them as a pull request at some point.

Other articles in this series:

Refactoring AGameMode Part 1 – Why Do That Thing?

Refactoring AGameMode Part 2 – Splitting AGameMode

Refactoring AGameMode Part 3 – Replacing the hardcoded FSM

Refactoring AGameMode Part 4 – Using the new FSM