My questions are:
- How can I improve my implementation of the FSM model?
- Should Finite State Machines have functionality for Adding and Removing states from table? (Since I can only be in one state at a time I feel like there's no need for me to store the states when I can just call SetState(state)).
- Am I doing the transitions right with SetState(state)? (I feel like I can just handle the transitions inside each States update method. Maybe there's a better way but I'm not sure.)
Below is the code that I have these questions about.
Diagram
State Module:
local State = {}
State.__index = State
function State:New()
local newState = {
Init = function() print("Init ran") end,
Update = function() print("Updating") end,
Enter = function() print("Entering") end,
Exit = function() print("Exiting") end,
}
setmetatable(newState, self)
print("Created new state")
return newState
end
return State
StateMachine Module:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local State = require(ReplicatedStorage:WaitForChild("State"))
local StateMachine = {}
StateMachine.__index = StateMachine
function StateMachine:Create()
local machine = {}
machine.initState = State:New()
machine.currentState = machine.initState
machine.currentState.Init()
setmetatable(machine, self)
return machine
end
function StateMachine:Update()
if self.currentState ~= nil then
self.currentState:Update()
end
end
function StateMachine:SetState(state)
assert(state ~= nil, "Cannot set a nil state.")
self.currentState:Exit()
self.currentState = state
self.currentState.Init()
self.currentState.Enter()
end
return StateMachine
Here's the way I'm using my version of a FSM.
Example:
newZombie.stateMachine = StateMachine:Create()
newZombie.idleState = State:New()
newZombie.idleState.Init = function()
print("idle state init")
end
newZombie.idleState.Enter = function()
print("idle state enter!")
end
newZombie.idleState.Update = function()
print("idle state updating!")
if not newZombie.target then
print("Getting target")
newZombie.target = newZombie:GetNearestTarget()
end
if newZombie.zombieTarget then
print("Found target")
newZombie.stateMachine:SetState(newZombie.chaseState)
end
end
newZombie.chaseState = State:New()
newZombie.chaseState.Init = function()
print("chaseState init")
end
newZombie.chaseState.Enter = function()
print("chaseState enter")
end
newZombie.chaseState.Update = function()
print("chaseState updating!")
if newZombie.target then
local direction = (newZombie.target.Position - newZombie.rootPart.Position).Unit * 0.5
local distanceToTarget = (newZombie.target.Position - newZombie.rootPart.Position).magnitude
local MAX_ATTACK_RADIUS = 4
local ray = Ray.new(newZombie.rootPart.Position, (newZombie.target.Position - newZombie.rootPart.Position).Unit * 500)
local ignoreList = {}
for i, v in pairs(ZombiesServerFolder:GetChildren()) do
table.insert(ignoreList, v)
end
local hit, position, normal = Workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
if not hit.Parent:FindFirstChild("Humanoid") then
print("Walk Path")
end
if distanceToTarget >= MAX_ATTACK_RADIUS then
newZombie.rootPart.CFrame = newZombie.rootPart.CFrame + direction
else
newZombie.stateMachine:SetState(newZombie.attackState)
end
else
newZombie.stateMachine:SetState(newZombie.idleState)
end
end
newZombie.attackState = State:New()
newZombie.attackState.Init = function()
print("attackState init")
end
newZombie.attackState.Enter = function()
print("attackState enter")
end
newZombie.attackState.Update = function()
print("attackState updating!")
if newZombie.target then
local distanceToTarget = (newZombie.target.Position - newZombie.rootPart.Position).magnitude
local MAX_ATTACK_RADIUS = 4
if distanceToTarget >= MAX_ATTACK_RADIUS then
newZombie.stateMachine:SetState(newZombie.chaseState)
end
end
end
----------------------------------------------------
---- STARTING STATE ----
----------------------------------------------------
newZombie.stateMachine:SetState(newZombie.idleState)
----------------------------------------------------
Also in the NPC update function I'm updating the state machines current state update function.
if self.stateMachine then
self.stateMachine:Update()
end