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), theentercallback will run. We'll use it to make theMainMenuGuivisible. - When the FSM exits that state, the
exitcallback will run. We'll use it to hide theMainMenuGui.
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 yourFSM.new(). It initializes the state machine for a specific object.- The
statestable, with itsmenuOpenandmenuClosedkeys, is your state configuration. - The
propertiestable inside each state ({Visible = true}) acts like anentercallback, 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:
- In a new Roblox place, create three
ScreenGuisinStarterGui. Name themMainMenuGui,SettingsGui, andGameplayGui. - Inside each
ScreenGui, add aFrameand aTextLabelso you can tell which one is which. By default, set theEnabledproperty ofSettingsGuiandGameplayGuitofalse. - Create a
LocalScriptinsideStarterPlayerScripts. This script will contain your UI FSM. - 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
enterandexitstate callbacks are the perfect mechanism for managing UI visibility by setting the.Enabledproperty ofScreenGuis. - Professional patterns, like the
StatefulObjectControllerfrom 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.