Dealing with interruptions w/ Behavior Trees

I’m designing an AI for an NPC to do with melee combat.

For this I decided to go about using a common hybrid method between Behavior Trees and Finite State Machines.

Where there are a number of states the NPC can be in, such as:

  • Idle - No target, do nothing
  • Chase - Target nearby, move towards them
  • Attack - When close enough, attack
  • Return - No longer target, return to original position

And then the more exact behavior for each state is governed by a behavior tree inside.

The issue I am having is how to handle interruptions. For example, if the NPC is chasing a player (Chase state) but then is attacked by a different player. I would want the NPC to stop chasing its current target and target the other.

Do I simply just add monitors for every case? Checking for a new target at every step? If I do this then won’t I also need clean ups for every occasion? And if I do that then I can’t scale this model very well as eventually I’ll need many different clean up functions and monitors.

What is the best way of handling interruptions?

2 Likes

I would try making a task + priority system. Idle task has the lowest priority. Then, things like the NPC being attacked only need to increase the priority of the attack task corresponding to the attacker. This makes it so you do not need to think of entry and exit code for every state transition.

3 Likes

spent two days trying to figure this out myself. still interested in a solution?

I’ve moved on to different methods for AI but would still be keen to hear!

Oh would you mind telling me how you’ve chosen to approach it? maybe i can learn.

i figured out this interruption system based in coroutines:


function NPCai:startAdventure()
	local co = coroutine.create(function() 
		self:decide() -- going on our infinite adventure
	end)
	coroutine.resume(co) -- triggering coroutine

	print("when does this happen")

	local co2 = coroutine.create(function()-- simultaneous process
		while true do -- constantly checks whether interrupted changed
			if self.Interrupted then -- checks the status of Interrupted
				coroutine.close(co) -- this closes adventure coroutine 
				break -- break out of loop
			else
			task.wait(1) -- wait one second to check again (otherwise crashes)
			end -- is automatically closed
		end
	end)
	coroutine.resume(co2) -- triggering coroutine
	print(coroutine.status(co2))
end

this seems to work on a basic level. I plan on adding ‘uninterruptable’ functions by doing a check what function we are at and waiting if it is uninterruptable. possibly i should replace the loop with renderstep or heartbeat or whatever. i still need to trigger a variable change with an event and test if that works

dont know what downsides could be to this system. but it does seem to work.

1 Like

Actually looking over it today. Because bindable events also make a fake simultaneous thread you can just do this way easier and simpler no second co routine necessary:


function NPCai:startAdventure()
	local co = coroutine.create(function() 
		self:decide() -- going on our infinite adventure
	end)
	coroutine.resume(co) -- triggering coroutine

	print("when does this happen")
	
	InterruptionEvent.Event:Connect(function()
		coroutine.close(co)
		print(coroutine.status(co))
		-- interruption() function
	end)
	--at end of this coroutine it is suspended because it is still listening
	-- after interruption has been received the coroutine is dead
	-- go to interruption function which eventually calls startAdventure() again
	print(coroutine.status(co))
end

Oh well. we live and learn lol

2 Likes