Hello! Let's get started on the next part of our exploration into Finite State Machines.
In our last lesson, we did the crucial design work of identifying the high-level states for a typical match-based game: Lobby, Countdown, InRound, and PostRound. This analytical step is always the foundation of a good FSM.
Today, we move from design to implementation. This lesson is all about taking those abstract states and building the actual server-side FSM that will manage our game's main loop. We'll create the core script that acts as the "brain" for the game's progression, setting the stage for all the gameplay logic we'll add later.
Our goal is to create a server-side FSM to manage the main game loop using the identified states. By the end of this lesson, you will have a functional FSM script running on the server, ready to be expanded upon.
1. From Scratch to Library: A Practical Step
In previous modules, you did an excellent job building a reusable FSM module from the ground up. This gave you a deep understanding of how FSMs work under the hood. In professional game development, it's also common to use well-tested, pre-built libraries to save time and benefit from advanced features.
Today, we'll work with StateQ, a powerful and modern FSM library designed specifically for Luau and Roblox. It introduces an event-driven approach that's slightly different from the enter/update/exit pattern we've used before but is extremely effective for managing game flow.
To begin, let's get familiar with the library's core concepts.
StateQ: What's a finite state machine?
The StateQ library's documentation on GitHub provides a clear introduction. Reading this will help connect its terminology to what we already know about FSMs.
Please read the section titled 'What's a finite state machine?'. Notice how it defines States, Events, and Transitions. This should be a good review of the concepts from our first module.
As you can see, the core components are the same: a machine can only be in one State at a time, and it moves to a new state via a Transition. The key difference in StateQ is that transitions are triggered by named Events. This creates a very clear and readable structure where you explicitly "handle" an event to cause a state change.
2. Setting Up Your Server Environment
A game state manager must run on the server. The server is the single source of truth for the entire game; it dictates what state the game is in for all players.
- In Roblox Studio, navigate to
ServerScriptService. - Create a new
Scriptand name itGameStateManager.
Now, let's add the StateQ library to your project so our script can use it.
The StateQ GitHub page provides an .rbxm file that you can easily drag into Studio to install the library.
Please follow the instructions under the 'Non-Rojo users' section. You will need to click the 'latest release' link to download the .rbxm file. Then, drag that file from your computer's file explorer directly into the Roblox Studio viewport, and place the resulting Packages folder inside ReplicatedStorage.
We place the library in ReplicatedStorage because it's a conventional location for modules that might need to be accessed by both the server and the client, which is a good practice for organization even if only the server uses it for now.
3. Creating the Game State FSM
With the setup complete, it's time to write the code. Open your GameStateManager script.
First, let's study a practical example from the StateQ documentation to understand its syntax for defining states and transitions.
The library's light switch example is perfect for understanding how to structure the FSM definition.
Read through the code block in the 'Example usage' section. Focus on how StateQ.new() is called. Pay close attention to the syntax: [EventName] = { from = State, beforeAsync = function() ... end }. This structure defines which event can be triggered from which state.
As the example shows, you define a table of transitions, where each key is an Event name. The value is another table specifying the from state and a beforeAsync function that performs the transition logic and returns the next state. This from property is how the FSM enforces our designed game flow.
Now, let's apply this pattern to our game loop. Copy and paste the following code into your GameStateManager script.
-- GameStateManager script, located in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 1. Require the StateQ library we added to ReplicatedStorage
local StateQ = require(ReplicatedStorage.Packages.StateQ)
-- 2. Define the states for our game loop using an Enum-like table for clarity
local GameState = {
Lobby = "Lobby",
Countdown = "Countdown",
InRound = "InRound",
PostRound = "PostRound",
}
-- 3. Define the events that will trigger transitions between our states
local GameEvent = {
StartCountdown = "StartCountdown",
StartRound = "StartRound",
EndRound = "EndRound",
ResetLobby = "ResetLobby",
}
-- 4. Create the Game State FSM instance
print("Creating Game State FSM...")
local GameFSM = StateQ.new(
GameState.Lobby, -- The initial state of the machine
{
-- This transition happens when we handle the 'StartCountdown' event
[GameEvent.StartCountdown] = {
from = GameState.Lobby, -- Can only happen from the Lobby state
beforeAsync = function()
print("FSM Event: StartCountdown. Transitioning from Lobby -> Countdown.")
-- The return value of this function is the new state
return GameState.Countdown
end,
},
-- This transition happens when we handle the 'StartRound' event
[GameEvent.StartRound] = {
from = GameState.Countdown, -- Can only happen from the Countdown state
beforeAsync = function()
print("FSM Event: StartRound. Transitioning from Countdown -> InRound.")
return GameState.InRound
end,
},
-- This transition happens when we handle the 'EndRound' event
[GameEvent.EndRound] = {
from = GameState.InRound, -- Can only happen from the InRound state
beforeAsync = function()
print("FSM Event: EndRound. Transitioning from InRound -> PostRound.")
return GameState.PostRound
end,
},
-- This transition brings the loop back to the beginning
[GameEvent.ResetLobby] = {
from = GameState.PostRound, -- Can only happen from the PostRound state
beforeAsync = function()
print("FSM Event: ResetLobby. Transitioning from PostRound -> Lobby.")
return GameState.Lobby
end,
},
}
)
print("Game State FSM created successfully. Initial state is:", GameFSM:getState())
Let's break down this script:
- Require Library: We get access to the
StateQlibrary. - Define States/Events: We create tables that act like Enums for our states (
GameState) and events (GameEvent). This prevents typos and makes the code cleaner than using raw strings everywhere. - Instantiate FSM: We call
StateQ.new()and pass two arguments:- The initial state:
GameState.Lobby. - A table defining all valid transitions. Each transition is keyed by its
GameEventand specifies whichfromstate it's allowed in. ThebeforeAsyncfunction currently just prints a message and returns the target state.
- The initial state:
If you run your game now, you should see the print statements in your Output window confirming that the FSM was created and is in its initial Lobby state. You have successfully created the skeleton of your game loop manager!
Conclusion
Excellent work! You've taken the theoretical states from our last lesson and forged them into a concrete, server-side FSM. This script is the foundational component for managing your entire game's flow.
Key Takeaways:
- Game State Managers are critical server-side scripts that act as the single source of truth for the game's progression.
- Using a dedicated library like
StateQcan accelerate development and provide robust, event-driven FSMs. - We created an FSM by defining our states (
Lobby,Countdown, etc.), the events that cause transitions (StartCountdown, etc.), and the allowed flow between them. - The current implementation uses
printstatements as placeholders for future logic, successfully creating the structural "skeleton" of our game loop.
Next Lesson Preview:
With our FSM structure in place, the next step is to breathe life into it. In the next lesson, we will implement the core transition logic that makes the FSM cycle through the states in the correct order, such as automatically triggering the StartCountdown event when enough players join the lobby.