Module 3 - Build Game Manager
Now, let’s focus on the game mechanics.
We want our game to have the following states and progress like this:
State | Description | Notes |
---|
game ready | This represents a popular situation in games where you have players meeting up and you do not want players to start yet and the game is not keeping track of score. | When our game is in the “Ready” state, the course should not have gems in it. “Ready” cannot be set if the game state is currently “Playing”. |
game play | This represents when the game has started and we are keeping score. | In our game, we want players to be able to trigger this state manually, and start the game, whenever the game is in the “Ready” state. When the “Playing” state is triggered by a player, the course should be populated with gems. The “Playing” state can only be set when the current state is “Ready.” |
game over | This represents the end of the game. | The “Game Over” state should be triggered automatically when players collect all gems. There is no death state. |
Since all of our game progression and behavior can be represented in a few states, a simple class is useful for managing state.
A Game Manager is a common way to manage the state and the flow of a game. Let’s create one now.
- In the desktop editor, open the Scripts panel ( </> ).
- To create a new script, click the + icon.
- Name this new script: GameManager.
- Click the context menu next to the new GameManager script and select Open in External Editor.
We will start by defining some game states and switching between them to solve for our Game States requirements above.
At the very bottom of the GameManager file, add an enum for our game states:
- “Ready”,
- “Playing”,
- “Finished”
Export this enum so other components have access to this as well.
Here is what my file looks like now.
import * as hz from 'horizon/core';
class GameManager extends hz.Component<typeof GameManager> {
static propsDefinition = {};
start() {}
}
hz.Component.register(GameManager);
export enum GameState {
'Ready',
'Playing',
'Finished',
}
Variables to hold current game state Inside the GameManager Component class, and outside of the start() function, we need a variable to hold the current game state. Its value should be an enum value:
private gameState!: GameState;
The exclamation point instructs the compiler that this variable can be treated as a non-null value.
Then, inside of our start() function, set the initial value of this variable to Ready, which is our default state.
this.gameState = GameState.Ready;
There are many ways to manage and change the state of a game, and many examples of “finite state machines” can be found online. For this game and for many other worlds on Meta Horizon Worlds, a switch statement is a good solution.
Below the start() function, create a new public function, setGameState, which allows us to pass in a GameState enum value. Inside the function, add a switch statement with all of our enum values:
public setGameState(state: GameState): void {
switch (state) {
case GameState.Ready:
break;
case GameState.Playing:
break;
case GameState.Finished:
break;
}
}
The above defines the architecture for how the function operates. The value of state is passed in as the new state to which to switch. Note the break statements are required to exit the switch statement.
Now, we need to add the code to the function to manage the switching for each possible value of state: GameState.Ready, GameState.Playing, and GameState.Finished.
If someone passes in the current state, we can exit early. We shouldn’t do anything if this happens. Add this above the switch statement:
if (this.gameState === state) {
return;
}
switch (state) {
Use the switch statement to update the value of this.gameState according to our game requirements:
- The Ready state cannot be set if the game state is currently Playing state.
- The Playing state can only be set when the current state is Ready state.
public setGameState(state: GameState): void {
if (this.gameState === state) {
return;
}
switch (state) {
case GameState.Ready:
if (this.gameState !== GameState.Playing) {
this.gameState = GameState.Ready;
}
break;
case GameState.Playing:
if (this.gameState === GameState.Ready) {
this.gameState = GameState.Playing;
}
break;
case GameState.Finished:
this.gameState = GameState.Finished;
break;
}
}
We can use our new function to set the initial game state of the world. In the start function, replace the code from earlier with a call to our new function and pass in the default GameState.Ready state.
start() {
this.setGameState(GameState.Ready);
}
To test our new function, add a new console log after the switch statement, inside of our setGameState function.
console.log(`new game state is: ${GameState[this.gameState]}`);
Before we verify it works, we need to attach this script to an Empty Object, as we did with our PlayerManager script.
This time we can test by simply pressing the Play or the Reset button:
Toolbar in desktop editor
Console results:
Results in the Console tab
Success!
In this module, you:
- Created a Game Manager script to manage game progress
- Created a function to switch game states
- Created and exported an enum with game state definitions
Keep rolling!