Until recently, the ‘game’ wasn’t much of a game. There really wasn’t a beginning to the level while playing through. Once the level loaded, Suzy’s AI would immediately begin to run and off she would go. But, it didn’t matter how many of the fruit objects she picked up: one, three, ten…all. It didn’t make any difference. There was no true ending to the level. That is where the GameMode comes in.
The name “GameMode” is a bit misleading, as you may not expect the game mode to handle the state of the game. But according to the documentation here, the game mode keeps track of the current levels progress according to the rules of that particular game. In the case of Capuchin Capers, the rules are very simple. When the level starts, you are in a pre-game state, and are able to run around the level to get a feel for it’s size and shape. While in the pre-game state, you can’t see any of the fruit objects or the game UI (which could reveal the number of fruit objects if visible). The game proper doesn’t start until you enter the grass hut located near your beginning position. Once you enter the hut, the game state changes to the in-game state.
The in-game state is where you are competing with Suzy to find the most fruit objects. The UI is visible and Suzy’s AI, as well as the Director’s AI, is enabled and she will immediately begin searching for fruit objects. The game mode will remain in this state until one of two things happens. If Suzy finds half of the fruit objects (rounded down) she wins the competition. Or, if the player finds more than half of the fruit objects, they win. As you can see, Suzy wins all ties according to these simple rules. Once one of these two things occurs, the game will transition to the post-game state.
In the post-game state, the end of the level is executed, starting by taking the player’s camera and translating it high above the player’s head. From this bird’s-eye-view, the camera’s position will move to a location high above the hut, looking down towards it. From that known location, we can start the actual cinematic to end the level. These steps to this point are necessary, because we can’t know where the player is on the island when they or Suzy wins the match. We need a way to get the camera to a position that we can always guarantee, regardless of where the player was on the island at the end. Once the camera is in this “safe” location, we trigger the level sequence. The level sequence’s camera starts at the exact same location as the “safe” location, so the cinematics’ start is seamless. From there, we play the cinematic for the player as a reward for playing through the level.
In all of this, there are a lot of moving parts, even in a game as simple as Capuchin Capers. So, how is all of this done in the game mode? A state machine. The implementation of the state machine is actually fairly simple compared to it’s impact. Warning: C++ incoming. There is a base class named CCStateBase with the following declaration:
class CAPUCHINCAPERS_API CCStateBase
{
public:
CCStateBase();
virtual ~CCStateBase();
/** Called when the state is first entered. */
virtual void Enter();
/** Called each tick for this state. */
virtual void Update(float Delta);
/** Called before the state is exited. */
virtual void Exit();
void SetParent(ACapuchinCapersGameMode* ParentParam);
ACapuchinCapersGameMode* GetParent();
protected:
ACapuchinCapersGameMode* Parent;
};
Each of the states mentioned above (pre-game, in-game, and post-game) all derive from this class. The derived classes define what each of their methods should actually do. Obviously, the Enter() method for the in-game state would be completely different than that of the post-game state. This is really nice to work with, because it compartmentalizes the functionality of each state and makes it easier to visualize how everything flows. From the above code, it is easy to see that the game mode is the owner of the state machine and is set as “Parent” in each of the state machines objects.
In the game mode each of the three game states are created as objects, with a pointer to the base class CCStateBase being declared as well. That pointer, which is named “CurrentState”, is where all the magic happens. By using polymorphism we can smoothly change between states, with all the functionality that we have built into our state machine, and the game mode object never needs to be aware of the current state of the state machine. This is done, primarily, by the SetNextState() method:
void ACapuchinCapersGameMode::SetNextState(ECCGameState NextState)
{
// If this is the first call to this method,
// CurrentState will be nullptr. Otherwise,
// we need to exit the current state before
// entering the next.
if (CurrentState)
{
CurrentState->Exit();
}
switch (NextState)
{
case ECCGameState::IngameState:
CurrentState = &IngameState;
break;
case ECCGameState::PostgameState:
CurrentState = &PostgameState;
break;
default:
case ECCGameState::PregameState:
CurrentState = &PregameState;
break;
}
if (CurrentState)
{
CurrentState->Enter();
// Broadcast the state change to any
// listeners of this delegate.
OnGameStateChanged.Broadcast(NextState);
}
else
{
UE_LOG(LogTemp,
Warning,
TEXT("CurrentState is NULL."));
}
}
For a mechanism that controls the entirety of the game’s rules, this state machine is incredibly simple. There is quite a bit that isn’t being shown here, of course. This article isn’t meant to be a tutorial on how to implement a state machine. For that, you would definitely be better served to find a good book or tutorial aimed specifically at that. I can recommend “Game Development Patterns and Best Practices” by John P. Doran and Matt Casanova, as that is where I learned this design pattern.
In closing I have to say that the structure of the Unreal Engine is complex, and what every part is supposed to be doing isn’t always clear when you are just starting out. I never would have thought for a moment that the state machine that controls the game would go in the game mode, while all of the data such as player scores, would go in the AGameStateBase derived object. This can all be a bit confusing when first learning it, but once you understand what everything is doing it gets more clear and easier to grasp. Happy developing!