Hello! Welcome back to our course on building Finite State Machines in Roblox.
In the last lesson, we took a major step by creating a reusable FSM ModuleScript. You wrote a constructor, FSM.new(), that can create new, independent FSM instances. Right now, those instances just hold data—an initial state and a configuration. They don't do anything yet.
Today, we're going to breathe life into our FSM by defining the behavior of each state. The learning outcome for this lesson is to implement a method on the FSM module to define states and their associated callbacks (enter, update, and exit). These callbacks are functions that run at specific points in a state's lifecycle, allowing us to script everything from starting animations to updating NPC logic.
1. The Core of State Behavior: Enter, Update, and Exit
Before we write any code, let's establish the theory. A state isn't just a name; it represents a specific behavior. To manage this behavior, we use three key types of functions, often called callbacks:
enter(): This function runs once when the FSM transitions into a state. It's for setup tasks. For example, when an NPC enters anAttackstate, theenterfunction might play a "battle cry" sound and start an attack animation.update(dt): This function runs repeatedly on every frame or game tick while a state is active. It's for ongoing logic. In theAttackstate, theupdatefunction might check if the NPC is in range to deal damage. Thedt(delta time) parameter is often passed in to make movement and other time-based logic frame-rate independent.exit(): This function runs once when the FSM transitions out of a state. It's for cleanup. When the NPC leaves theAttackstate, theexitfunction would stop the attack animation and maybe switch its music back to the default.
This enter/exit pattern is a fundamental concept in stateful programming. The video below explains this idea in the context of the "State Pattern," an object-oriented design pattern that is very similar to what we are building.
An introduction to finite state machines and the state pattern for game development
The video 'An introduction to finite state machines and the state pattern' from The Shaggy Dev provides an excellent explanation of why enter and exit methods are so useful.
Watch from 08:40 to 09:13. The narrator explains how adding enter and exit logic allows for actions like switching animations or cleaning up when a state changes.
To connect this to your goal of creating NPC AI, look at this simple state diagram.

If this were your NPC, what would happen in the enter callback for the Farming state? You might make the NPC equip a tool and start a farming animation. The exit callback would do the opposite: unequip the tool and stop the animation. The update callback could be used to check if the NPC's inventory is full, which would trigger a transition to the Storing state.
2. A Cleaner Approach to Defining States
In the last lesson, our constructor looked like this: FSM.new(initialStateName, statesConfig). While functional, passing a potentially huge statesConfig table into the constructor isn't always the cleanest or most flexible approach.
Let's refactor our module to use a "builder" pattern.
- The
FSM.new()constructor will now only be responsible for creating the FSM instance with its initial state. - We will add a new method,
FSM:defineState(), specifically for adding state definitions one by one.
First, let's simplify the constructor in your FSM ModuleScript.
Old Constructor:
function FSM.new(initialStateName, statesConfig)
local newFsm = {}
newFsm.states = statesConfig
newFsm.currentStateName = initialStateName
setmetatable(newFsm, FSM)
return newFsm
end
New, Simpler Constructor:
Update your FSM.new() function to match this. It now only takes the initial state name.
-- In your FSM ModuleScript
function FSM.new(initialStateName)
local newFsm = {}
newFsm.currentStateName = initialStateName
newFsm.states = {} -- The states table now starts empty
setmetatable(newFsm, FSM)
return newFsm
end
3. Implementing FSM:defineState()
Now for the main part of this lesson. We will implement a method that allows us to add a state and its callbacks to our FSM instance. The method will take two arguments: the name of the state (a string) and a definition table containing the enter, update, and exit functions.
Add the following function to your FSM ModuleScript.
-- In your FSM ModuleScript, after the constructor
function FSM:defineState(name, definition)
self.states[name] = definition
end
That's it! This method simply takes the name and the definition table and adds them to the instance's states table. The definition table will serve as a container for our callbacks. Any callbacks you don't provide will simply be nil, and our transition logic (which we'll build later) will know to simply skip them.
4. Defining States in Practice
Let's see how this works from our main Script. We'll recreate the Locked and Unlocked states for our turnstile, but this time we'll add enter callbacks to print messages.
Update your main Script to use this new pattern:
-- In a regular 'Script' in ServerScriptService
-- 1. Get the FSM "class" from our ModuleScript
local FSM = require(script.Parent.FSM)
-- 2. Create a new FSM instance with its initial state
local myTurnstile = FSM.new("Locked")
print("FSM instance created. Initial state:", myTurnstile.currentStateName)
-- 3. Define the states and their callbacks
myTurnstile:defineState("Locked", {
enter = function()
print("ENTERED Locked state. The turnstile is now locked tight.")
end,
exit = function()
print("EXITING Locked state. Unlocking...")
end
})
myTurnstile:defineState("Unlocked", {
enter = function()
print("ENTERED Unlocked state. You may now push.")
end,
exit = function()
print("EXITING Unlocked state. Locking up...")
end
})
-- 4. Verify that the states were added correctly
wait(1)
print("\nVerifying states:")
for stateName, definition in pairs(myTurnstile.states) do
print("- State '" .. stateName .. "' has been defined.")
if definition.enter then
print(" - It has an 'enter' callback.")
end
if definition.exit then
print(" - It has an 'exit' callback.")
end
end
When you run this code, the output will confirm that a new FSM was created and that both the Locked and Unlocked states were defined, each with its own enter and exit callbacks.
This pattern is very common in Roblox development. The following reading from the Roblox DevForum shows a similar FSM implementation where the configuration table also includes enter and exit functions for each state.
My state machine code is messy and doesn't have much functions to it
This post on the Roblox DevForum shows a complete FSM module and a configuration example. It's a great real-world reference for how these modules are structured.
Scroll down to the 'Config Example' code block. Notice how each state (sprint, attack, idle) is a table that contains enter and exit functions, just like in our defineState method.
Conclusion
Great work! You have now enhanced your FSM module with a clean and powerful way to define state-specific behavior. While our FSM still can't transition between states yet, we've laid the critical groundwork for making those transitions meaningful.
Key Takeaways:
- State Callbacks (
enter,update,exit) are the functions that contain the actual logic for what a state does. enteris for setup,updateis for ongoing logic, andexitis for cleanup.- A "builder" pattern, using a method like
defineState(), can lead to cleaner and more readable code than passing a single, massive configuration table to a constructor. - The state definition is a simple table containing functions, making it highly flexible.
In our next lesson, we will finally make our FSM move. You will implement a method to define the permissible transitions between states and another method to actually trigger a transition, which will correctly call the exit and enter functions you've just set up.