Dynamic instantiation and assignment of External Behavior Trees

MrMemeseeks

New member
Hi all,

I'm implementing BD and what I would like to have is the following: I have classic "AI agents" who should be able to pickup different tasks/jobs (base building style).

The execution of those jobs is different depending on the type of job.
So what I would like to do is to separate those jobs into separate External Behavior Trees.
The agent has its own behavior tree with it's generic behavior. When an agent is available it should pickup a job.
My main questions are a bit in how to configure those jobs? My initial line of thought is that I have a JobManager which has a list of jobs that are ready to pick up (things like build a building, chop a tree). The jobs in this list should contain an "instance?" of the behavior tree of the job. And when an agent is picking up the job, it will assign the behavior tree to the "External Behavior Tree" node in the agent's own behavior. I have the feeling that I got the gist of it but i'm not really there yet.
Main questions are,
- How to configure the jobs? Including parameters that they need?
- How would I add jobs to the job manager list? Do I need to instantiate an instance of the job's behavior tree?

So the main thing is that I want to create jobs at runtime > Add them to a list > Agent should be able to fetch job from the list and execute it.

Thanks in advance ^^
 
I just went through a similar design. I was previously using separate BTs for each different job type, then using BeahviourTreeReferences for all the common branches/aborts. Then in code you set the ExternalBehaviourTree on each worker when their job changes.
But, I just changed things up to work in reverse (not 100% sure yet if this will work out but so far so good).
Now I have a single BT for all jobs for all workers that contains all of the common stuff, but there is one node in the BT for whatever job the agent currently has. This has to change at runtime so I made my own BehaviourTreeReference task to load the current AI job BT at runtime.

I'm not 100% sure yet how to swap different jobs at runtime but will probably just restart the BT for the agent once their assigned job has changed (which changes which external behaviour tree I get from the lookup at runtime).

Example code - this gives you a new BT task you can select when building up your BT in the editor. No variables need to be set because the ExternalBehaviour loads via code.
C#:
    public class LG_SetAiJobTree : BehaviorReference
    {
        public override ExternalBehavior[] GetExternalBehaviors()
        {
            // PUT YOUR OWN CODE HERE - you need to end up with an array of ExternalBhaviour because that is what the base class uses
            List<ExternalBehavior> btList = new List<ExternalBehavior>();
            btList.Add(LGAI_Manager.Instance.GetCurrentJobTree(this.gameObject.GetComponent<LGAI_Simple>()));

            // set the base class ExternalBehaviour array here in code instead of setting it up via the inspector
            this.externalBehaviors = btList.ToArray();

            return base.GetExternalBehaviors();
        }
    }
 
Last edited:
Thanks for the reply!
I think I'll end up with something similar.
Something I'm currently trying is the following:

I have a regular c# interface IJob which has a property called BehaviorTree.
Every class that implements this interface sets the property at runtime by loading the BT.asset from the Resources folder.
So this enables me to have regular c# classes which reference the accompanying BT.

These Jobs can be added to a JobManager which keeps track of what jobs need to be done.
So when an agent requests a job from the JobManager it will retrieve the BT from the Job and assigns it to the ExternalBT node.

This is work in progress and I didn't really test it yet. But what do you think?
Possibly instead of assigning the Job BT to a regular ExternalBT, I should make a custom task to which I can assign the job.

I'm still figuring out the ins and outs of Behavior Designer but I think your code snippet will be something which I need as well.
 
Should work, it would just be a matter of how and where you want to manage your BTs. So that's more about your own custom code.

You'll either be swapping the ExternalBehaviourTree out on the BehaviourTree component on each worker, or use the BehaviourTreeReference task within a common external tree (or using your own custom reference task). Or find some other creative way to set things up however you need it with custom tasks and variables.
That's one thing I like about BehaviourDesigner is the ability to quickly clone and customize or write new tasks to do whatever you need. I usually end up using all custom tasks as well as all custom base classes for Action, Movement etc to expose all the variables I need on my agents.
 
It is not entirely clear for me yet unfortunately.
I made a simple setup for clarity but I cannot get it to work fully.
1684572479888.png

I have this simple setup for now. The goal is that the "Get Job Action" should set the job field for the agent.
My job object contains a external behavior tree which describes the behavior of the tree.
Then in the "Execute Job Action" I want to get the Behavior Tree from the job and load it.
But as I saw in multiple posts that GetExternalBehaviors is only invoked when the BT loads.

I saw in one of your posts that you provoke this by resetting the External Behavior field on your main Behavior Tree.
However I'm expecting to have multiple External Behavior Tree references in my main behavior tree. So I want to be able to explicitly reload the Execute Job Action (or any other external behavior tree).

Do you have any guidance on how I specifically can reload this external BT so it will load my Job's BT?

Don't mind the dirty code it's just for testing purposes ^^
C#:
public class ExecuteJobAction : BehaviorReference
    {
        private Agent agent;
        public override void OnAwake()
        {
            base.OnAwake();
            agent = GetComponent<Agent>();
        }

        public override void OnStart()
        {
            base.OnStart();
        }

        public override ExternalBehavior[] GetExternalBehaviors()
        {
            if (agent == null)
            {
                return base.GetExternalBehaviors();
            }
           
            if (agent.currentJob != null)
            {
                return new ExternalBehavior[]
                {
                    agent.currentJob.BehaviorTree
                };
            }
            else
            {
                return base.GetExternalBehaviors();
            }
        }
    }
 
Couple things...

In my tests I had to set the base class externalBehaviours before calling base.GetExternalBehaviours():
C#:
            // set the base class ExternalBehaviour array here in code instead of setting it up via the inspector
            this.externalBehaviors = btList.ToArray();

            return base.GetExternalBehaviors();

How I currently get the BT to reload after assigning an agent a new job:
C#:
            simpleAI.behaviorTree.ExternalBehavior = null;
            simpleAI.behaviorTree.ExternalBehavior = this.SimpleAI_DefaultBehaviour;

That does however cause a runtime error but it still seems to work. Waiting to hear back from Justin on how to properly reload the BT to get the ref task to reload without error. If there continues to be problems I'll just to back to swapping BTs for each job instead of using a common job BT.
The behavior "Behavior" on GameObject "__SomeAiObject" contains no root task. This behavior will be disabled.
 
I tried this approach right now. It seems to work. So when the agent doesn't have a job it will retrieve one. Set the CurrentJob property in the agent's class.
Then I restart the BT. This triggers a call to the LoadJobAction's GetExternalBehaviorTrees which loads the agent's job BT.

The restarting of the whole BT is something that doesn't sit right with me at all since this might have unforseen consequences when the BT grows.

I don't really understand the other implementations that I see on the forum here but I feel everyone ends up with basically this solution where they have to restart the BT in order to trigger the loading of external BTs.

Additionally based on that I see the request of loading BT's dynamically, is it something that is maybe on the roadmap or in the mind of the developer to make this more straightforward? Or am I missing something to do this more properly?

I can imagine that runtime changing of the BT might be a headache but I also imagine ways that it could be possible.

For now I will go for this approach and keep an eye on other threads with the same issue and see if there will be a better alternative.

1684591577757.png
 
Okay another approach I found. For some reason I thought that you can only add one Behavior Tree component to a GameObject.
Now it seems that this is not the case. So in order to avoid reloading the whole Agent's BT, I do the jobs logic in a separate BT component.
I think the downside is that I will not have a single clear oversight on what the agent is doing because I will have multiple BTs running separately for a single agent.. However if I separate them logically in different BTs this might be managable.

Any thoughts? ^^
 
That's just shifting where you manage the multiple BTs.

There are multiple ways to do it, just depends on your preference:
  1. Have a single huge BT with all jobs in it, then control which branch within the BT. This solves issues like not being able to see all of your tasks in the editor.
  2. Have one external BT per job, control which BT is assigned as externalBehaviourTree at runtime to a single BehaviorTree component
  3. Have one common BT for all jobs, use beahviourTreeReference to load the current job's external BT at runtime
  4. Have multiple BT components on the agent, one for each BT
There are likely be more ways to do it since you can write your own tasks and override how BehaviourTreeReference works.

You also have to consider how you will manage other common/repeated branches. Are those also BehaviourTreeReference tasks? Do you repeat groups of tasks in every BT? etc.

My game has many jobs so I don't want to use a single BT.
I also don't want to use multiple BT components on each agent.
That leaves me with choices 2 and 3. I've done both.
I didn't have any issues using option 2, but wanted to change designs to use option 3, which introduced some issues I'm currently working on (and posting about here on the forums). But, option 3 does give me the cleanest code based on my current design so I'm trying to make that work.
 
Last edited:
I think that my previous post (including the screenshot) matches point 3 the most?
So loading the job's BT using the BehaviorTreeReference. The problem I have here(and I believe that's also the issue you try to resolve) is that you cannot swap a BTReference at runtime since the BTReference will be loaded when the roots BT starts.

So in my screenshot I restart the BT in order to force a reload of the external BT.

Is the situation I'm describing correct?

I don't want to get blocked by this for too long now so I think I'm going for my current approach until I know how to swap an BTReference at runtime.
 
You definitely can reload a BTReference at runtime, but it only loads if the previous external BT is different.

That's why I null out my external tree before reloading my custom BTRef where it pulls the current job external tree at runtime. Even with that it throws an error but that doesn't seem to break anything. I noted this in post #6 above.
Also note that you restart the BT in code when your agent's job changes.

I'm trying to get a proper solution to that problem in this thread:
 
I saw your post indeed but there is something I don't understand. I hope you don't mind explaining.
As I understand from this part:
C#:
simpleAI.behaviorTree.ExternalBehavior = null;
simpleAI.behaviorTree.ExternalBehavior = this.SimpleAI_DefaultBehaviour;

You have your Agent code(simpleAI) which contains a reference to the BT that runs its behavior(simpleAI.BehaviorTree).
This BT scripts has a property "ExternalBehavior". But as far as I've seen when I set this property, it replaces the whole BT with this external BT.
What I would like is to replace only a single ExternalBehaviorReference node in my main BT.

To clarify where our differences are maybe. Does your agent always run a single Job behavior which fully describes the behavior of your agent?
Because I have my default Agent behavior (day night cycle, eat, etc.) and my dynamic behavior(job it does during the day). So I don't want to swap out my whole BT but only the part which describes what job it does.

Maybe this is where I get confused with your part? Do you replace the whole BT of your agent or also just a single part?

Thanks for the help btw ^^
 
My setup is like this:

My agents have a single BT component that is empty.
At runtime I initialize all my agents and assign an external BT DefaultJobs that I get from my AIManager. I never change this external tree, I only reload it when needed.
Each agent has my LGAI_Simple component which I use to track which job they have etc.

DefaultJobs contains all the common branches and a single custom BTRef task to lookup the agent's current job at runtime, placed in the abort priority position I need it in within the common tree. This common BT will grow over time as needed.

DefaultJobs looks like this In the editor (my custom BTRef task circled)
1684681682616.png

At runtime when I change jobs on an agent I store which external job tree they are now assigned to.
That assigned job tree is what is looked up by my custom BTRef task.

When I change jobs on an agent, after assigning the new job tree, I call the method to force the agent's BT component to reload, which reloads the DefaultJobs BT, which allows my custom BTRef task to run which loads the currently assigned external job BT for that agent.

Example: Force agent BT component to reload.
Note that SimpleAI_DefaultBehaviour comes from my AI Manager, that is set in the editor as a link to my DefaultJobs BT.
The InitSimpleAI() method is also in my AIManager, but you can run this type of code anywhere you need to.
C#:
        public void InitSimpleAI(LGAI_Simple simpleAI)
        {
            // for now, set null first so it will recognize the change
            simpleAI.behaviorTree.ExternalBehavior = null;
            simpleAI.behaviorTree.ExternalBehavior = this.SimpleAI_DefaultBehaviour;

            simpleAI.SetDefaultSpeeds();
        }

By forcing the agent's BT to reload in the method above, it runs my custom BTRef which looks up the current external job tree on the agent.
C#:
    public class LG_SetAiJobTree : BehaviorReference
    {
        public override ExternalBehavior[] GetExternalBehaviors()
        {
            List<ExternalBehavior> btList = new List<ExternalBehavior>();
            btList.Add(LGAI_Manager.Instance.GetCurrentJobTree(this.gameObject.GetComponent<LGAI_Simple>()));

            this.externalBehaviors = btList.ToArray();

            return base.GetExternalBehaviors();
        }
    }

I then end up with my custom job tasks loaded in place of the BTRef task (entry point for my custom job tree in red, this replaced the BRRef from the first image)
1684682396385.png
 
Last edited:
Answers to your questions:

You have your Agent code(simpleAI) which contains a reference to the BT that runs its behavior(simpleAI.BehaviorTree).
My agent has a BT component. The external behaviour on that component is my DefaultJobs BT which goes on all agents and never changes, except when I need to force the BT to reload, but it ends up with the same DefaultJobs BT.

This BT scripts has a property "ExternalBehavior". But as far as I've seen when I set this property, it replaces the whole BT with this external BT.
The BT component on my agent is assigned the DefaultJobs BT and never changes. You could set that up in the editor, but I set this at runtime because I store the DefaultJobs in my AIManager in case I ever need to change it I don't have to do that on all of my agent prefabs.

What I would like is to replace only a single ExternalBehaviorReference node in my main BT.
Yup, I don't swap external behaviour trees on components, I only change what external behaviour tree is assigned to the agent when their job changes. The custom BTRef task grabs that at runtime to insert in place of itself when the DefaultJobs BT reloads.
 
Last edited:
That's some gold explanation thanks!

I think this is how I had it at some point and I think I will return to this implementation.
My concern with it was this part.
When I change jobs on an agent, after assigning the new job tree, I call the method to force the agent's BT component to reload, which reloads the DefaultJobs BT, which allows my custom BTRef task to run which loads the currently assigned external job BT for that agent.

I am not super happy about reloading the whole BT when I only want to swap jobs. But I understand that right now there is no other way because the BTReference.GetExternalBehaviors only gets invoked when the BT is loaded.
I'm not sure how that will work out for me in the future when the agent does multiple things and the BT gets reset in the middle of something. But I think this might be the way to go for now instead of multiple BT components.

I'm changing my mind all the time ? but your implementation looks solid and besides the restarting of the BT this is exactly what I want as well.

Thanks again
 
RE: concerns about reloading the tree

Personally I'm not worried about it. I set up my aborts and where my BTRefs are in the tree so that I could restart it at anytime and it would be fine.
Plus, if your agent is changing jobs, you may want to push them through a "wrap up current job" task or something like that to handle various scenarios.

You could delay when you switch your job and reload. For example, if you want to switch jobs you set some flag on the agent, that triggers some abort in your common tree to wrap things up. Once that is done, you know you can safely swap the job BT and reload the tree.

So even if you have any issues when reloading the tree there will be several things you can do to handle it.

I change my mind and my designs all the time too, I imagine that's normal for a complex indie game when you're learning as you go.

Cheers :)
 
I am not super happy about reloading the whole BT when I only want to swap jobs. But I understand that right now there is no other way...

You can always just do everything in one huge tree so you never have to reload or reset. That just might be hard to manage in the editor though.

Also, I don't know about any performance impacts with any of the options we've discussed. Maybe one huge tree is a terrible idea on 100's of agents, maybe it doesn't matter, I don't know. You probably aren't swapping jobs and reloading trees that often so I don't imagine that's a performance concern.
 
This is a nice discussion! As @bbjones mentioned, GetExternalBehaviors is only called when the tree reloads. If there are multiple external trees provided then it will load all of the external trees underneath the parent task.

If you want to switch out which external tree is used you have two options:
  1. Reload the entire tree when the external tree should be replaced.
  2. Load all of the possible external trees initially, and then use conditions to decide which tree to run at runtime.
I am gearing up for version 2 development and with that I'll look at this design again to see if there's anything that can be done to streamline it instead of requiring a complete tree reload.
 
You definitely can reload a BTReference at runtime, but it only loads if the previous external BT is different.

That's why I null out my external tree before reloading my custom BTRef where it pulls the current job external tree at runtime. Even with that it throws an error but that doesn't seem to break anything. I noted this in post #6 above.
Also note that you restart the BT in code when your agent's job changes.

I'm trying to get a proper solution to that problem in this thread:

I learned quite a bit from reading this thread. @bbjones following your method, I was able to get the similar results, since I'm also switching external tree references at runtime.

In your quoted post, you mentioned encountering errors, and I found a way to resolve those by modifying your swap sequence slightly.

I was getting two errors: A null reference, and an ArgumentOutOfRange exception. I got rid of the null reference by making sure I set a temporary idle tree instead of null. The ArgumentOutOfRange exception was a bit trickier to figure out, as it was coming from the BehaviorManager. Through some experimentation I found out manually invoking a Tick() got rid of that error. So now the tree swaps happen cleanly.

Code:
tree.DisableBehavior();
tree.ExternalBehavior = idleTree;
tree.ExternalBehavior = externalTree;
tree.EnableBehavior();
BehaviorManager.instance.Tick();
 
Hello together,

since this thread is not that old yet and I was struggling with a similar requirement, I wanted to share my approach for the situation and ask for your opinion on it.
For our game I have the requirement, that we have a workforce of generic agents that can have one or more professions assigned, and also have a local task list of things they should execute. You can imagine it like in The Sims, where you have some kind of free will but at any time the player can give the agents direct instructions. The professions will generate tasks when the actor is idle.
The tasks can reach from just playing an emote animation to a complex AI behavior that needs to execute (like finding a resource spot, collecting it, bringing the results back to storage). Also the agents have some motives like hunger and energy, when something reaches a critical level they need to interrupt everything and execute emergency tasks like eating something.

So how I approached the situation is by adding a two BehaviorTree components to my agents (main one and a sub behavior).
I then created a task system which just holds instructions what the task is about, and I made some task state observation system, maybe in classes it's more clear:

TaskSystem : MonoBehavior
[Serializable] QueuedTask { taskName }
abstract TaskBehavior { Start(), Update() ..., get State }
abstract TaskDefinition : ScriptableObject
PlayEmoteTaskDefinition : TaskDefinition
PlayEmoteTaskBehavior : TaskBehavior
SubBehaviorTaskDefinition : TaskDefinition
SubBehaviorTaskBehavior : TaskBehavior

The task behavior definitions are scriptable objects so I can easily configure them in the editor.
I then created a main factory which can dynamically create task behavior instances by resolving the matching definition for the taskName.

WIth the separation into different Task Behaviors I am able to implement any logic inside those (like playing an emote on the animator, or managing a complete sub behavior tree). For everything that involves a sub behavior tree, I can just create SubBehaviorTaskDefinition ScriptableObject instances in the editor and set it up with the ExternalBehaviorTree and some variable mapping logics.

Next step was creating some Tasks for Behavior Designer to pick available tasks, and call the task system to run the tasks. A very straight forward setup of my main BehaviorTree contains something like:

1703674910742.png
As long as the sub behavior tree is running:

1703675798719.png

I wrote then a "SubBehaviorController" script which is attached to the actor and will observe the execution of everything happening inside the second BehaviorTree component and notify this back to the TaskBehavior.

The "Perform Queued Task" action will kick off the execution of the TaskBehavior implementations, observe the Task behaviors for Success, Failure or Running and also reflect those states to the main behavior tree.

I think I'm on the right track to achieve what I need, but what is your opinion on it?
Also currently I need to somehow implement a way to cancel / interrupt the execution of the second BehaviorTree when the main tree get's some interrupt (like the agent is almost starving).
For sure I could add something using the event system or conditionals, but I don't want to include those Tasks in all my sub-behavior trees since it's just boiler plate setup.

I didn't find any way yet to react to conditional aborts or interrupts in my BehaviorTree Actions. I implemented OnEnd which is then cancelling the sub behavior when it is called unexpectedly (the sub behavior is still in Running state).
There seems to be a "OnConditionalAbort" event in the Actions, but it's only documented for Conditionals, is that a correct way to at least handle those? Or do I miss something in Behaviour Designer that allows me to cleanly react to those things?
 
Last edited:
Top