Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

State-Based Gameplay Mechanics

Recommended resources

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 InRound state begins, we should teleport all players to the map's spawn points and reset their health.
  • When the PostRound state 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.

  1. In the Explorer window, create two Parts inside the Workspace.
  2. Name one LobbySpawn and the other MapSpawn.
  3. Position LobbySpawn where you want players to wait between rounds.
  4. Position MapSpawn where you want players to start when a round begins.
  5. Select both parts and in the Properties window, enable Anchored and disable CanCollide. You can also set Transparency to 1 so they are invisible.
Roblox Round System Thumbnail
Your workspace will have distinct areas for the lobby and the map, and we'll use our spawn parts to teleport players between them.

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.

  1. Open your server-side GameStateManager script.
  2. At the top of the script, add variables to reference our new spawn locations.
  3. Update the on function for the GameEvent.StartRound event.

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 LobbySpawn and MapSpawn.
  • Inside the StartRound event, we loop through all players.
  • For each player, we reset their health to full and teleport them to the MapSpawn's location using character: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 (or enter) 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.