Hello! Let's get started.
In our last lesson, we built a fantastic timed transition system. Our server-side FSM now manages a countdown, broadcasting the time remaining to all clients, who then see a live timer on their screen. This makes the game feel much more dynamic.
However, the states themselves don't affect gameplay yet. Players can still run around freely in the lobby, and when the round starts, nothing actually happens except for a text label changing. Today, we'll bridge that gap.
Our learning outcome is to connect state transitions to gameplay logic, such as teleporting players when a round starts and resetting stats when it ends. This is where the FSM truly begins to shine, acting as the central orchestrator of your game's rules and flow.
The FSM as a Gameplay Orchestrator
The core idea is simple: we will use the on callbacks within our FSM's event table to trigger gameplay actions. When the FSM transitions into a new state, the corresponding on function is the perfect place to execute logic that should happen at that exact moment.
For example:
- When the
InRoundstate begins, we should teleport all players to the map's spawn points and reset their health. - When the
PostRoundstate begins, we should teleport players back to the lobby.
This pattern of triggering actions on state entry is fundamental to FSM design. To see another practical example, let's briefly look at how an FSM can be used to control a tool, like a gun in a game.
State Machines Can Make Your Roblox Game Better!
This video by qweekertom, which we've looked at before, demonstrates a simple gun FSM. The creator explains how transitioning into states like 'Shoot' or 'Reload' triggers the corresponding gameplay logic.
Watch the segment from 08:51 to 10:20. Notice how the FSM has states for idle, shoot, and reload. Entering the shoot state fires the gun, and entering the reload state performs the reload animation. This is the exact same principle we'll apply to our game loop: entering a state will trigger a specific set of gameplay actions.
Just as the gun FSM orchestrates shooting and reloading, our Game State FSM will orchestrate the start and end of rounds.
Planning Our Gameplay Logic
Before we modify our FSM, let's look at a standard implementation of a round system to see what kind of logic is typically involved. This will give us a clear blueprint for the code we need to write.
How to make an advanced round system
This post from the Roblox Developer Forum provides an excellent server script for an advanced round system. While it doesn't use an FSM, its startRound and endRound functions contain the exact gameplay logic we want to integrate into our state machine's callbacks.
Please read the code for the startRound() and endRound() functions. In startRound(), notice the actions it performs: it resets a list of alive players, adds all current players to that list, and connects to each player's Humanoid.Died event. This is the setup logic for a round. The endRound() function is simpler, mainly responsible for cleanup and preparing the transition back to intermission. We will place similar logic into our FSM's on callbacks.
Now that we have a clear idea of what to do, let's implement it step-by-step.
Step 1: Prepare the Workspace
First, we need to define the physical locations for our lobby and our game map.
- In the
Explorerwindow, create twoParts inside theWorkspace. - Name one
LobbySpawnand the otherMapSpawn. - Position
LobbySpawnwhere you want players to wait between rounds. - Position
MapSpawnwhere you want players to start when a round begins. - Select both parts and in the
Propertieswindow, enableAnchoredand disableCanCollide. You can also setTransparencyto1so they are invisible.

Step 2: Implement Round-Start Logic
Now, let's modify the GameStateManager script to handle what happens when a round starts. We will add logic to the on callback of the StartRound event to teleport and reset players.
- Open your server-side
GameStateManagerscript. - At the top of the script, add variables to reference our new spawn locations.
- Update the
onfunction for theGameEvent.StartRoundevent.
Your script should look like this:
-- In your GameStateManager server script...
-- ... (other services)
-- Define spawn locations
local LOBBY_SPAWN_LOCATION = workspace:WaitForChild("LobbySpawn")
local MAP_SPAWN_LOCATION = workspace:WaitForChild("MapSpawn")
-- ... (GameFSM, GameState, GameEvent definitions)
-- Inside your StateQ.new() table:
[GameEvent.StartRound] = {
from = GameState.Countdown,
on = function()
print("FSM: Entered InRound state.")
-- Gameplay Logic for starting the round
for _, player in ipairs(game.Players:GetPlayers()) do
local character = player.Character
if character then
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
-- Reset health and teleport player
humanoid.Health = humanoid.MaxHealth
character:SetPrimaryPartCFrame(MAP_SPAWN_LOCATION.CFrame)
end
end
end
-- Announce state change to clients
broadcastStateChange(GameState.InRound, { duration = ROUND_DURATION })
-- Countdown loop for the round
for i = ROUND_DURATION, 1, -1 do
task.wait(1)
broadcastStateChange(GameState.InRound, { timeRemaining = i })
end
-- After the round timer ends, transition to PostRound
if GameFSM:getState() == GameState.InRound then
GameFSM:handle(GameEvent.EndRound)
end
end,
},
Key Changes:
- We get references to
LobbySpawnandMapSpawn. - Inside the
StartRoundevent, we loop through all players. - For each player, we reset their health to full and teleport them to the
MapSpawn's location usingcharacter:SetPrimaryPartCFrame(). This is a robust method for moving a player model. - This logic runs before we broadcast the state change, ensuring players are in position the moment the round officially begins.
Step 3: Implement Round-End Logic
Similarly, let's add logic to teleport players back to the lobby when the round is over. We'll modify the EndRound event.
Update your EndRound event definition in the GameStateManager as follows:
-- In your GameStateManager server script...
-- Inside your StateQ.new() table:
[GameEvent.EndRound] = {
from = GameState.InRound,
on = function()
print("FSM: Entered PostRound state.")
-- Gameplay Logic for ending the round
for _, player in ipairs(game.Players:GetPlayers()) do
local character = player.Character
if character then
-- Teleport player back to the lobby
character:SetPrimaryPartCFrame(LOBBY_SPAWN_LOCATION.CFrame)
end
end
broadcastStateChange(GameState.PostRound)
task.delay(POST_ROUND_DURATION, function()
if GameFSM:getState() == GameState.PostRound then
GameFSM:handle(GameEvent.StartCountdown)
end
end)
end,
},
Now, when the round ends, all players will be instantly moved back to the lobby spawn point.
Conclusion
Excellent work! You have now connected key gameplay mechanics directly to your FSM's state transitions. Your game is no longer just a sequence of timers; it's an organized system where states have tangible consequences. Players are teleported, their health is reset, and the entire match flow is managed cleanly by the state machine.
Key Takeaways:
- The
on(orenter) callbacks in an FSM are the ideal place to execute one-time actions associated with a state change. - Common gameplay logic to include: teleporting players, resetting stats (health, ammo), clearing inventories, and connecting/disconnecting player-specific events.
- By centralizing this logic within the FSM, your code becomes more organized and easier to debug than if it were scattered across multiple scripts.
Next Lesson Preview:
Our FSM now controls where players are and what state they're in. But what if we want to change what players can do based on the state? In the next lesson, we will implement our final piece of game state logic: using the FSM to manage player controls, such as disabling their movement and actions while they are in the lobby.