How to design NPC that can be interrupted during a complex task, be able to resume it, and cancel it?

Let’s say NPC has a task to clean their desk. The task will consist of something like this:

  1. Find cleaning equipment and navigate to it
  2. Pick it up
  3. Navigate to the dirty desk
  4. Start wiping the desk with a cloth
  5. Stop wiping
  6. Navigate back to the storage room
  7. Drop off the equipment

At any point, player can interrupt the NPC, and force them to stop what they’re doing so that they can start dialogue with them. After dialogue ends, the NPC must be able to resume their task (animations, sounds, particles, etc.). Additionally, NPC might have a unexpected event where they need to cancel their current task, so they must stop cleaning, navigate back to the storage room, and drop off the equipment at faster pace than normal. They also should be able to be interrupted when they are cancelling their task.

How could I approach such complex NPC behavior? I’ve been told to use finite state machine, but I’m not sure how enter, exit, and update would help me achieve scalable interruption, and cancellation behavior.

It’s incredible that there is something that exists exactly as you described.

These are called “coroutines”.

Coroutines can create a task with:

local foo = coroutine.create(task); -- assuming task is a function

Now, the task can be interrupted using “coroutine.yield()”.

Example code:

local function task()
	print("hello");
	coroutine.yield();
	print("goodbye")
	return
end

local foo = coroutine.create(task); -- assuming task is a function

local success = coroutine.resume(foo); -- prints "hello"

local success = coroutine.resume(foo); -- prints "goodbye"

Finite State Machines should be used to check for a desired output.
For example, if you want to do a run animation but you want to check that you’re not stunned, you’re not in the air, etc.

1 Like

An alternate idea that may work is to use some sort of priority queueing system that queues tasks based on priority, then FIFO (higher priority tasks are executed first, and tasks of the same priority are executed on a FIFO basis).

Typical tasks should be queued with a low priority, and unexpected events should be queued with a higher priority so that the NPC is forced to execute those first.

tasks = [
  ("find_cleaning_equipment", 1), ("find_dirty_desk", 1), ("clean_dirty_desk", 1), ...
]

An unexpected event could then look like

("answer_door", 5)

which means it becomes the next task for the NPC to execute (it has the highest priority, as 5 > 1). As a consenquence, the next task will be the one that is interrupted, which means the NPC will simply resume what it was doing.

Handling animations/particles/etc. is up to you, as this action is separate from the task queueing system. You could just cancel the event when a higher-priority task is scheduled - there are many ways to attack this problem.

1 Like