I want to make an NPC that wanders randomly, either with waypoints or at random. When the NPC sees a player (using line of sight), it should approach them until it’s about 5 studs away, then perform an action. I’ve tried using state machines, but they feel too complex and often don’t work as expected
Honestly, you should get used to working with state machines. They’re not complex at all and will work as expected 100% of the time unless you’ve messed up in your implementation of it.
Your state machine would look something like this visualized.
yes i did a stat machine diagram but im not too used to state machines, i used this video as an example: https://www.youtube.com/watch?v=7M1LkjPaEFE&t=1753s but could get to work my idea right do you know any other modules more simpler?
Not really simple modules.
That one overcomplicates things with using multiple modules. A bit over organized for my liking.
Here is one that I used for awhile. It is either written my me or was modified by me. Honestly haven’t a clue. It is messy and a bit ugly but it works.
export type state = {
transitions: {[string]: string};
update: (... any) -> string;
enter: (... any) -> any;
exit: (... any) -> any;
}
export type StateMachine = {
CurrentState: string,
States: {[string]: state},
_switching: boolean,
Switch: (self: StateMachine, key: string) -> nil,
ForceSwitch: (self: StateMachine, key: string) -> nil,
Update: (self: StateMachine, dt, ...any) -> nil,
Destroy: (self: StateMachine) -> nil,
}
local function blank()
end
local ezState = {}
ezState.__index = ezState
-- constructor
function ezState.new(states: {string: state}, initialState: string): StateMachine
local self = {}
self.CurrentState = initialState:: string
self.States = {}:: {[string]: state}
for index, stateData: state in pairs(states) do
self.States[index] = {
-- transitions
transitions = stateData.transitions or {};
-- state functions
update = stateData.update or blank;
enter = stateData.enter or blank;
exit = stateData.exit or blank;
}
end
self._switching = false:: boolean
setmetatable(self, ezState)
self.States[self.CurrentState]:enter()
return self
end
-- methods
function ezState:Update(dt: number?, ...)
if not self._switching then
if self.States[self.CurrentState] then
local switch = self.States[self.CurrentState].update(dt, ...)
if switch then
self:Switch(switch)
end
end
end
end
function ezState:Switch(transitionKey: string)
local oldStateInfo = self.States[self.CurrentState]
local newState = oldStateInfo.transitions[transitionKey]
if newState then
self._switching = true
oldStateInfo.exit()
self.States[newState].enter()
self._switching = false
--self.EventObject:Fire("OnStateChanged", self.CurrentState, newState)
self.CurrentState = newState
end
end
function ezState:ForceSwitch(stateName: string)
local oldStateInfo = self.States[self.CurrentState]
local newState = self.States[stateName]
if newState then
self._switching = true
oldStateInfo.exit()
newState.enter()
self._switching = false
--self.EventObject:Fire("OnStateChanged", self.CurrentState, stateName)
self.CurrentState = stateName
end
end
function ezState:Destroy()
table.clear(self.States)
for index, _ in pairs(self) do
self[index] = nil
end
table.clear(self) -- this might work idk
self = nil
end
return ezState
Do not have time to write a sample of how you would set it up rn but if you need me to then I can later just let me know.