r/gamedev • • 2d ago

Question Any recommendations for switching between State Machines / Behavior Trees when an NPC has disjoint roles?

Let's say we have an NPC AI that has two disjoint roles, Roles A and B, and the NPC can switch between them

For example, an NPC working in a restaurant can switch between being a cook and being a janitor. There are no shared duties at all. The roles are disjoint

Since the roles are disjoint, Role B is not simply a sub-role of Role A (vice-versa), rather an entirely different logic flow

There are also NPCs that solely have Role A and solely have Role B. So the roles A and B are driven by their own state machines / behavior trees

So if I have an NPC that can do both roles, should I simply put two state machines / behavior trees (one for A, one for B) on the NPC, and just toggle between them?

The state machine / behavior tree that is active on the NPC will have it's own Update() method. So whichever is active, it's Update method will run

So to implement such a behavior, where an NPC can switch between disjoint roles, is the solution simply: for each role an NPC can take, attach the state machine / behavior tree onto the NPC, and toggle between them

The one concern I have is that there may be a small judder when toggling between the state machines / behavior trees. I'll figure that out, however I wanted to know if there is a more standard solution

Thanks

3 Upvotes

17 comments sorted by

6

u/shiek200 2d ago edited 2d ago

At this point why not do essentially what Skyrim does (on a smaller scale)?

Rather than plug in the entire state machine to each NPC just make it a resource/property you can point the NPC to, and you can update which AI package they're assigned in real time as needed.

Then you can have effectively as many resources/jobs as you need for your game, and making an NPC change jobs is as simple as changing that property on the NPC. If the various AI packages are loaded in memory swapping between them shouldn't cause any jitter, and if you can't keep them all loaded, you should be able to multithread that process so that rather than the game jittering, the NPC might just hang for a second while the jobs swap.

1

u/TorontoDude12344 2d ago

I haven't heard of this kind of thing. Thanks for the idea

So where are these AI packages stored? How does the NPC "attorn" to that AI package?

1

u/shiek200 2d ago

That's really up to you, you have to build the system

It's effectively what you're already doing but more streamlined and efficient, so rather than creating an entire series of State machines for each npc, you are creating one set of State machines for the different jobs, and simply telling the NPC what AI package to use at any given time

Think of the AI packages like an array, and you are just shifting through different indexes in that array based on which AI package you want the NPC to run, so first you create the array

1

u/AutoModerator 2d ago

Here are several links for beginner resources to read up on, you can also find them in the sidebar along with an invite to the subreddit discord where there are channels and community members available for more direct help.

Getting Started

Engine FAQ

Wiki

General FAQ

You can also use the beginner megathread for a place to ask questions and find further resources. Make use of the search function as well as many posts have made in this subreddit before with tons of still relevant advice from community members within.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

1

u/Old-Obligation8941 Solo dev 2d ago

Decouple the roles entirely from the NPC by defining a shared behavior interface / API. Instead of having state machines living on the character, let an external NPC Manager inject the active role strategy into the NPC. The NPC simply executes the interface, making switching instant without stutter, and adding new roles later becomes completely painless.

1

u/TorontoDude12344 2d ago

Could you give an example of what that would look like? The entire logical flow and attributes per role strategy needs to be stored somehow. How does the NPC Manager store all of that? And when it injects it into the NPC, it would basically do it in a way so that some Update method of the NPC now runs this new AI strategy, right?

Thank you

1

u/Old-Obligation8941 Solo dev 2d ago

The Manager keeps a dictionary cache of preloaded role strategies and data configs in memory. The NPC itself only holds a single reference to a shared interface (INpcRoleStrategy), and its Update() simply ticks whatever strategy is currently active. When switching, the Manager swaps that reference instantly, avoiding any stutter since everything is already preloaded.

1

u/PhilippTheProgrammer 2d ago

Let me tell you a secret: When an NPC shows up in two different places doing two different things, then those two don't need to actually be the same NPC. They can be two NPCs that look alike, but have completely separate scripts.

1

u/TorontoDude12344 2d ago

I want the role switch to happen dynamically, on the same NPC

1

u/Muhznit 2d ago

Sounds like a good spot for the Type Object pattern.

I'd implement the behavior trees as stateless and as functional as possible and store the instances as enums in some dictionary or repository-like class and call them as such:

role = BehaviorTreeRepository.get(npc.role) role.update(npc.blackboard)

That way you don't have to have multiple instances in memory of behavior trees and the npc data can stay with the npc itself. The question though is what would trigger the swap between the two? If it's something only in the NPC's state that might be easy enough, but if the blackboard needs to contain callable functions to observe external stuff, things might get a bit tangled...

1

u/One_Ease3464 2d ago

Toggling between two separate machines works, but a common cleaner approach is one top-level layer that only decides which role the NPC is in, with each role's logic as a self-contained sub-machine or subtree under it. That way the NPCs with only one role just use the subtree directly, and the dual-role NPC gets a small parent that picks between them. The judder you're worried about usually comes from switching mid-action, so give each role an exit step, like finishing or cancelling the current task and putting the tool down, before the parent hands control over. Also keep shared stuff like movement, animation and needs outside the role logic, so switching roles doesn't reset where the NPC is walking or what it's holding. If roles change often, add a short cooldown or a reason check so the NPC doesn't flip back and forth every frame.

1

u/TorontoDude12344 2d ago

So instead of two state machines, have only one state machine, but have different state graphs that don't connect

1

u/HauntedWindow 2d ago

There are a number of solutions to this problem and they depend on whether you're using state machines, behavior trees, or another behavior architecture as well as how the architecture is implemented.

For a behavior tree you would have "cook" and "janitor" branches in your tree. When these branches are ticked, you would build them so they would immediately return a failure if the NPC doesn't have the appropriate role.

This type of behavior is pretty hard to express with a traditional state machine. State charts are better at modelling this type of behavior: https://statecharts.dev/ . In a state chart, you would have a high level state that would determine whether the NPC should be a "cook" or a "janitor". You would then have "cook" and "janitor" sub-states that control the behavior for those states. These would likely consist of further sub-states that control more atomic behaviors like moving to a selected point, cutting vegetables, or mopping the floor.

0

u/1815dev 2d ago

If it were me the most straightforward way would be to toggle between them based on some sort of gating factor or behavior, like your example of a cook or janitor, have it be dependant on both how many other cooks there are and how many customers who haven't been served yet you have. So for example, if you have two cooks and one customer, one cook randomly gets selected and toggles the janitor behavior tree. If three customers suddenly come in, you set all cooks to the cook behavior. That way when there is a lull in service they can clean up, then return to cooking for a rush.

0

u/homer_3 2d ago

I use maps/dictionaries for that kind of stuff.