Skip to main content

Welcome to a Grasp lesson

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

Lesson 23

UI FSM for ScreenGuis

Recommended resources

Hello! Welcome back to our work on Finite State Machines.

In our last lesson, you did an excellent job translating a conceptual UI flow into a concrete state diagram. You mapped out the states like MainMenu and Settings and the transitions that connect them. That blueprint is the essential first step for building any robust UI system.

Today, we bring that blueprint to life. This lesson directly addresses the learning outcome: Implement a UI FSM that shows and hides different ScreenGuis based on the current state. We will take the logical structure you designed on paper and implement it in Luau, making different parts of your game's interface appear and disappear based on a managed state.

This will combine what you learned about state diagrams with the reusable FSM architecture you built in Module 3.

1. The Core Principle: enter and exit for UI Visibility

Think back to the FSM module you built. Each state had enter, update, and exit callbacks. For our UI FSM, the enter and exit callbacks are the perfect place to control which ScreenGui is visible.

The logic is simple and powerful:

  • When the FSM enters a state (e.g., MainMenu), the enter callback will run. We'll use it to make the MainMenuGui visible.
  • When the FSM exits that state, the exit callback will run. We'll use it to hide the MainMenuGui.

In Roblox, you can control the visibility of a ScreenGui using its Enabled property. Setting MainMenuGui.Enabled = true makes it visible and interactive, while MainMenuGui.Enabled = false hides it and disables its contents.


2. A Practical Walkthrough

Let's see this principle in action. The following video demonstrates a developer connecting their own FSM class to UI elements in Roblox. While their FSM implementation is slightly different from ours, the core concept of using callbacks to drive UI changes is exactly the same.

Roblox Studio - Finite State Machine EXPLAINED!

This video by CoodSlayer shows how to apply an FSM to a UI menu. Pay close attention to how state changes are used to update the visual properties of UI elements.

Watch from 05:23 to 10:53. The creator connects their FSM to a menu with 'Profile', 'Inventory', and 'Stats' buttons. Notice how the beforeLeave and afterEnter callbacks (which are conceptually identical to our exit and enter callbacks) are used to control button highlights and hide/show the corresponding frames.

The video shows that by defining behavior in the state callbacks, the code that triggers the transition (the button click) becomes very simple—it just has to tell the FSM which state to go to, without needing to know the details of how to update the UI.


3. A Professional Pattern: The Stateful UI Controller

The approach of using enter/exit callbacks works well. However, for UI that also involves animations, Roblox's own documentation highlights a very elegant pattern: a dedicated StatefulObjectController. This is essentially a specialized FSM ModuleScript designed specifically for controlling UI objects.

It elegantly combines state management with TweenService—a tool you're already familiar with—to handle properties like visibility, position, and rotation. Let's study this official pattern.

Create interactive UI | Documentation - Roblox Creator Hub

The official Roblox Creator Hub provides a tutorial for creating interactive UI. The key to this tutorial is a StatefulObjectController module. We will analyze this module to understand a more advanced, animation-ready way to manage UI state.

This article is comprehensive, so let's focus on the FSM-related parts: First, read the section 'Stateful object controller' to get an overview of the module's purpose. Skim the Luau code provided for the StatefulObjectController. Notice the hydrate and setState functions. They should feel very similar to the new() constructor and transitionTo() method of the FSM you've worked with. Now, jump down to the 'Create the settings script' section. Read the subsections for the 'Settings Button' and 'Settings Menu'. This is the most important part, as it shows how to use the controller to define states (like menuOpen, menuClosed) and their properties (like Visible and Position) for different UI elements.

Connecting the Dots

Let's quickly map the concepts from the StatefulObjectController to the FSM you already know:

  • StatefulObjectController.hydrate({...}) is analogous to your FSM.new(). It initializes the state machine for a specific object.
  • The states table, with its menuOpen and menuClosed keys, is your state configuration.
  • The properties table inside each state ({Visible = true}) acts like an enter callback, defining what should happen when that state becomes active.
  • controller:setState('menuOpen') is the equivalent of your FSM's transition function.

The key takeaway from this pattern is that it encapsulates state-specific properties directly within the state definition, making it very clean and readable.


4. Your Turn: Implement a Simple UI FSM

Now, let's put this into practice. We'll start with the basic enter/exit pattern to solidify the core concept.

Your Task:

  1. In a new Roblox place, create three ScreenGuis in StarterGui. Name them MainMenuGui, SettingsGui, and GameplayGui.
  2. Inside each ScreenGui, add a Frame and a TextLabel so you can tell which one is which. By default, set the Enabled property of SettingsGui and GameplayGui to false.
  3. Create a LocalScript inside StarterPlayerScripts. This script will contain your UI FSM.
  4. Using the state-driven FSM structure you've learned, write the code to manage the visibility of these three GUIs.

Here is a skeleton to guide you. Your goal is to fill in the states table with the correct enter and exit logic.

-- In a LocalScript in StarterPlayerScripts

-- Assume you have a simple table-based FSM handler
-- For example:
local UiFsm = {
    currentState = nil,
    states = {},
    -- A simple transition function
    transitionTo = function(self, newStateName)
        if self.currentState and self.states[self.currentState].exit then
            self.states[self.currentState].exit()
        end
        self.currentState = newStateName
        if self.states[self.currentState].enter then
            self.states[self.currentState].enter()
        end
        print("UI transitioned to state:", self.currentState)
    end
}

-- UI References
local player = game.Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local mainMenuGui = playerGui:WaitForChild("MainMenuGui")
local settingsGui = playerGui:WaitForChild("SettingsGui")
local gameplayGui = playerGui:WaitForChild("GameplayGui")

-- TODO: Define the states and their enter/exit callbacks
UiFsm.states = {
    MainMenu = {
        enter = function()
            -- What happens when we enter the MainMenu state?
        end,
        exit = function()
            -- What happens when we leave the MainMenu state?
        end
    },
    Settings = {
        enter = function()
            -- What happens when we enter the Settings state?
        end,
        exit = function()
            -- What happens when we leave the Settings state?
        end
    },
    Gameplay = {
        enter = function()
            -- What happens when we enter the Gameplay state?
        end,
        exit = function()
            -- What happens when we leave the Gameplay state?
        end
    }
}

-- Start the FSM in the MainMenu state
UiFsm:transitionTo("MainMenu")

-- To test, you can manually trigger transitions after a delay
wait(3)
UiFsm:transitionTo("Gameplay")

wait(3)
UiFsm:transitionTo("Settings")
Click here to see the completed solution
-- In a LocalScript in StarterPlayerScripts

-- Assume you have a simple table-based FSM handler
local UiFsm = {
    currentState = nil,
    states = {},
    transitionTo = function(self, newStateName)
        if self.currentState and self.states[self.currentState].exit then
            self.states[self.currentState].exit()
        end
        self.currentState = newStateName
        if self.states[self.currentState].enter then
            self.states[self.currentState].enter()
        end
        print("UI transitioned to state:", self.currentState)
    end
}

-- UI References
local player = game.Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local mainMenuGui = playerGui:WaitForChild("MainMenuGui")
local settingsGui = playerGui:WaitForChild("SettingsGui")
local gameplayGui = playerGui:WaitForChild("GameplayGui")

-- Define the states and their enter/exit callbacks
UiFsm.states = {
    MainMenu = {
        enter = function()
            mainMenuGui.Enabled = true
        end,
        exit = function()
            mainMenuGui.Enabled = false
        end
    },
    Settings = {
        enter = function()
            settingsGui.Enabled = true
        end,
        exit = function()
            settingsGui.Enabled = false
        end
    },
    Gameplay = {
        enter = function()
            gameplayGui.Enabled = true
        end,
        exit = function()
            gameplayGui.Enabled = false
        end
    }
}

-- Start the FSM in the MainMenu state
UiFsm:transitionTo("MainMenu")

-- To test, you can manually trigger transitions after a delay
wait(3)
UiFsm:transitionTo("Gameplay")

wait(3)
-- A common flow: go from Gameplay back to MainMenu, then to Settings
UiFsm:transitionTo("MainMenu") 
wait(3)
UiFsm:transitionTo("Settings")

When you run this code, you should see the UI switch from the Main Menu, to the Gameplay screen, back to the Main Menu, and finally to the Settings screen, with each one being visible for 3 seconds.


Conclusion

Fantastic! You have successfully implemented a functional UI Finite State Machine. You've taken the abstract concept of states and transitions and applied it to tangible ScreenGui objects, controlling their visibility in a clean, organized, and scalable way.

Key Takeaways:

  • A UI FSM's primary job is to control which UI elements are visible and interactive at any given time.
  • The enter and exit state callbacks are the perfect mechanism for managing UI visibility by setting the .Enabled property of ScreenGuis.
  • Professional patterns, like the StatefulObjectController from the Roblox documentation, build on this core idea to integrate animations and other properties directly into the state definitions.

In our next lesson, we will make this system interactive. You'll learn how to use button clicks to trigger FSM transitions and how to use TweenService within your state callbacks to create smooth, animated transitions between your UI screens.