How could i make advanced ai for an NPC AI

Hello devs!

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

2 Likes

Using PathindingService is a must, you can use magnitude for the distancing or using a hitbox to detect the player with :GetPartBoundsInBox().

1 Like

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.
diagram

3 Likes

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.

1 Like

Okay! ill mark that as a solution i will try it out and if i have any problems with it i will reply back thanks for replying!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.