Skip to main content

Welcome to a Grasp lesson

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

Lesson 4

FSMs with If-Elseif in Luau

Recommended resources

Hello! Welcome back.

In our last lesson, we finished our tour of FSM theory by learning how to read and interpret state diagrams. You practiced identifying states, transitions, and the events that trigger them, which is the crucial skill of turning a visual blueprint into a logical understanding of a system's behavior.

Now, it's time to move from theory to practice. This lesson kicks off Module 2, where we'll start building FSMs in Luau. Our goal today is to implement a simple FSM using if-elseif conditional logic. We'll take the diagrams we've been studying and translate their logic directly into code. This is the most straightforward way to start and builds directly upon your existing programming skills.

1. From Blueprint to Logic

Let's start with a very simple state diagram. In the last module, you saw diagrams for doors, traffic lights, and NPCs. Here is another classic example: a simple light switch.

Light Switch FSM Diagram
A simple state machine for a light, with 'On' and 'Off' states and the transitions connecting them.

If you were to describe the logic of this diagram in plain English, you might say:

  • "If the current state is On and the event is SwitchOff, the new state becomes Off."
  • "If the current state is Off and the event is SwitchOn, the new state becomes On."

This "if... and... then..." structure translates perfectly into conditional code. In Luau, this means using if and elseif statements. We'll need two key components:

  1. A variable to keep track of the machine's currentState.
  2. A block of conditional logic that checks the currentState and the incoming event to decide the nextState.

This approach is simple and intuitive, but as the number of states and transitions grows, it can become difficult to manage.


2. The 'Spaghetti Code' Problem

Before we write our own if-elseif FSM, it's helpful to see why this approach, while functional, has limitations. As you add more states (like jumping, attacking, double-jumping for a character), the nested if statements can quickly become a tangled mess.

Let's watch a short segment of a video that perfectly illustrates this problem and sets the stage for why organizing our logic with an FSM is so beneficial.

An introduction to finite state machines and the state pattern for game development

This video, 'An introduction to finite state machines' from The Shaggy Dev, begins by showing how character logic built with many if statements becomes hard to read and debug.

Please watch from 0:52 to 2:27. Pay attention to how the code complexity grows as more abilities like 'jump' and 'attack' are added.

This "spaghetti code" is what we want to avoid. An FSM gives us a clear structure to prevent this. Even with a simple if-elseif implementation, thinking in terms of states helps organize the code far better than a random collection of boolean flags.


3. A Basic FSM Structure in Code

Now that you've seen the problem, let's look at the first step toward a solution. The same video provides a clear, simple FSM implementation that uses conditional logic.

An introduction to finite state machines and the state pattern for game development

Let's continue with the same video. This next part defines what an FSM is and shows how to implement a basic version using enums (which we'll simulate with a table) and conditional branches.

Now watch from 2:27 to 6:03. This section defines the core rules of an FSM and demonstrates how to structure the code using if-else blocks for each state.

The key idea presented in the video is to have a main update or process function that contains a large conditional block:

  • First, check the current state (if currentState == "Idle" then...).
  • Then, inside that state's block, handle all the logic for that state, including checking for events that would cause a transition to another state.

This pattern is very common. In fact, it's sometimes referred to as "unstructured AI" when used for NPCs, as it lacks a formal framework but gets the job done for simple cases.

This pseudocode from the DevForum article "Using AI Behavior Frameworks For NPCs" shows the exact pattern for an NPC:

state = "WANDER"
while active:
    if state == "WANDER":
        wander()
        target = findTarget()
        if target:
            state = "CHASE"
    elif state == "CHASE":
        -- logic for chasing
        if target is close enough to attack:
            state = "ATTACK"
        else if target is lost:
            state = "WANDER"
    elif state == "ATTACK":
        -- logic for attacking
        if target is no longer close enough:
            state = "CHASE"

This structure is exactly what we are going to build.


4. Hands-On: Coding a Turnstile FSM

It's time to write some code. We'll implement an FSM for a turnstile, a classic example used in computer science. A turnstile has two states: Locked and Unlocked. It accepts two events: Coin (inserting a coin) and Push (pushing the arm).

Here is the logic:

  • Initial State: Locked
  • If Locked and you insert a Coin, it becomes Unlocked.
  • If Locked and you Push, it stays Locked.
  • If Unlocked and you Push, it becomes Locked.
  • If Unlocked and you insert a Coin, it stays Unlocked.

Below is a complete Luau script that implements this logic. You can create a new Script in ServerScriptService in Roblox Studio, paste this code in, and run it to see the output.

-- 1. Define the possible states for our machine
local states = {
    Locked = "Locked",
    Unlocked = "Unlocked"
}

-- 2. Set the initial state of the machine
local currentState = states.Locked
print("Initial state: " .. currentState)

-- 3. Create a function to process events and handle transitions
function handleEvent(event)
    print("-------------------------")
    print("Event received: " .. event)
    print("State before: " .. currentState)

    -- This is the core of our FSM logic
    if currentState == states.Locked then
        -- Logic for when the machine is in the 'Locked' state
        if event == "Coin" then
            currentState = states.Unlocked
            print("Action: Coin inserted. Unlocking turnstile.")
        elseif event == "Push" then
            -- No state change, but we can log the action
            print("Action: Pushed on a locked turnstile. It remains locked.")
        end

    elseif currentState == states.Unlocked then
        -- Logic for when the machine is in the 'Unlocked' state
        if event == "Push" then
            currentState = states.Locked
            print("Action: Arm pushed. Locking turnstile.")
        elseif event == "Coin" then
            -- No state change
            print("Action: Coin inserted into an already unlocked turnstile. No change.")
        end
    end

    print("State after: " .. currentState)
end

-- 4. Simulate a sequence of events to test the FSM
wait(1)
handleEvent("Push")  -- Should do nothing
wait(1)
handleEvent("Coin")  -- Should unlock
wait(1)
handleEvent("Coin")  -- Should do nothing
wait(1)
handleEvent("Push")  -- Should lock
wait(1)
handleEvent("Push")  -- Should do nothing

print("-------------------------")
print("Simulation finished.")

Take a moment to read through the script and match the if/elseif blocks to the turnstile logic we described. When you run it, check the Output window. You'll see a step-by-step trace of the FSM changing states based on the sequence of events, which is an essential technique for debugging.


Conclusion

Congratulations on building your first FSM in Luau! While simple, this if-elseif structure is a valid and direct way to translate a state diagram into functioning code.

Key Takeaways:

  • An FSM can be implemented by storing the currentState in a variable.
  • A function that takes an event as input can use if-elseif blocks to manage transitions.
  • The logic is typically structured as a series of checks: first on the currentState, then on the event.
  • This approach works well for simple machines but can become unwieldy and hard to maintain as the number of states and transitions increases.

You now have a foundational understanding of how FSM logic is implemented. However, you've also seen the potential downsides of this basic approach. In our next lesson, we will directly address these issues by refactoring our code. You'll learn how to implement a table-driven FSM, which is a much cleaner, more scalable, and more powerful way to structure your state machines in Luau.