Multiple External Behavior Trees in one Behavior Tree Reference

Nightkin

New member
Hello,

In my game, characters are all identical at design-time (they all come from the same prefab) but have several ScriptableObjects that define their identities when the game starts. For example, one character may be a thief, another may be a named character, a third one may be a guard etc. I want them all to have different behaviors that depend on their identities but also on the current level because the current level tells a story involving the player and different levels mean different stories to tell. One behavior for a character in a level might become irrelevant for the same character in the next level so not all are reusable and that's okay.

For example, a named character may need to come to the player to initiate a conversation, and depending on the outcome of that conversation may give a quest or not. This would be handled by an external Behavior Tree that would be useful only for this level and this character.

To do this, my idea would be to have a very simple high-level skeleton Behavior Tree running a Parallel node under a Repeater, and under that Parallel node a single Behavior Tree Reference which list of external Behaviors would be calculated once when the level starts. I could also have three BT components instead of one. One of them would run the tasks in parallel, another in sequence and another with a selector. I'm not entirely decided on that yet.

So I have a few questions about this approach.

Here is what that simple skeleton tree looks like at design-time:

2021-11-10_113510.jpg

Don't pay too much attention to the names of the external behavior trees; What's in them, what they do and how they do it is not relevant to this post.

The list of External Behaviors in the Behavior Tree Reference is currently filled by hand but I want to fill it automatically from data gathered from different sources, before the Behavior Tree Reference is consumed and replaced by the instantiated External Behavior Trees. How can I reference the Behavior Tree Reference in my code and fill its list before it instantiates the trees in said list?

My second question would be about what happens next. When the Behavior Tree Reference is replaced by instances of its contents, the resulting sub-trees all overlap each other (except the first one), like this:

2021-11-10_113934.jpg

But what I want it to look like is this:

2021-11-10_115158.jpg

(I know the order of the nodes does not match the list displayed in the first screenshot, this is just for the sake of example. I just dragged the five nodes manually.)

Not only the order in which the nodes are placed under their parent is supposed to be important, but debugging this would be too cumbersome because if I had 30 external behavior trees to instantiate, I would have to spend a lot of time dragging all the nodes until I find the one I want to debug.

Is there a way to make the editor place the nodes in the correct order and in a way that they are all visible without having to shuffle manually?

I know the order under a Parallel node matters little but I have another tree with a selector and yet another one with a sequence so for those two, this would be a big issue if the order wasn't respected. Not to mention that some of these sub-trees could have a conditional abort in them so priorities are important.

My third question is more general: is this a good idea to design my trees this way? I like the idea of having a big manager (each one of my three "Behavior Tree" components) which does very little and describes no decision making at design-time, they're only there to manage, and a group of "worker" external behavior trees that may or may not be needed depending on the situation (once again, this is when the level starts, I shouldn't need to replace an already instantiated external behavior tree later).

Thanks!
 
In fact, after thinking about it a little more, maybe it would be best for me to make one (or several) big tree containing all the external behavior trees, each one containing a simple "HasBehavior" conditional Task to make sure the rest of it is allowed to be evaluated. That Task would simply look for a particular symbol in a particular list or dictionary in the character to check if this character has this particular behavior. This is a bit like the alien in Alien Isolation where its BT is huge but parts of it are locked until the player progresses enough through the story, making the alien look like it becomes more and more intelligent with time.

This is better than my initial idea explained in the OP because I realize that adding behaviors also sometimes (often) requires adding parameters and that cannot be handled by simply modifying the "externalBehaviors" list of the BehaviorReference Task. Plus I want to be able to organize my nodes in a way that they are easy to find and read (and debug) in the BT editor.

So I'll probably take that route for now but my questions still stand.
 
How can I reference the Behavior Tree Reference in my code and fill its list before it instantiates the trees in said list?
You can subclass the BehaviorTreeReference task and then provide your own ExternalTrees at runtime by overriding the GetExternalBehaviors method.

When the Behavior Tree Reference is replaced by instances of its contents, the resulting sub-trees all overlap each other (except the first one), like this:
The nodes are arranged relative to the positioning within the external tree. The editor is not able to automatically rearrange your nodes so all of them are visible.

In fact, after thinking about it a little more, maybe it would be best for me to make one (or several) big tree containing all the external behavior trees, each one containing a simple "HasBehavior" conditional Task to make sure the rest of it is allowed to be evaluated. That Task would simply look for a particular symbol in a particular list or dictionary in the character to check if this character has this particular behavior. This is a bit like the alien in Alien Isolation where its BT is huge but parts of it are locked until the player progresses enough through the story, making the alien look like it becomes more and more intelligent with time.
That also sounds like a good solution :) There isn't a "right" way so if it gives you the functionality that you need then you are on the right track.
 
Thank you for your answer. Yes I ended up choosing the second solution (with the "HasBehavior" task as well as a "AddBehavior" and a "RemoveBehavior" tasks to be able to activate/deactivate sub-trees in real time), this gives me more control.
 
I have a related note/question.
Initially, I tried to implement something similar to the OPs task in code. Basic logic is following:

- The main "root behavior" is stored in a scriptable object "settings" along with a lot of other stuff
- There is also a list of additional sub-behaviors in the "settings" that can be attached to the BehaviorTreeReference in the root
I was pretty sure that the code below would work, but it doesn't. Even though the code itself works fine in the debugger, but the resulting BehaviorTree's BehaviorTreeReference has zero children in the initialized GameObject. And subsequently I get the ArgumentOutOfRange exception.

The solution that worked is to subclass from BehaviorTreeReference, and override GetExternalBehaviors method. But! The downsides are:
- that's definitely not an obvious solution
- I need to pull the customBehaviors throught the "Shared..." variables, which seems redundant..

May be there is a better "proper" way?

C#:
behaviorTree = GetComponent<BehaviorTree>();
behaviorTree.DisableBehavior();
behaviorTree.ExternalBehavior = settings.behaviorTreeRoot; //settings is a scriptable object, behaviorTreeRoot is a ExternalBehavior field
if(settings.customBehaviors.Length > 0) {
    var btRoot = behaviorTree.ExternalBehavior.FindTask<BehaviorTreeReference>();
    btRoot.Disabled = true;
    btRoot.externalBehaviors = new ExternalBehavior[settings.customBehaviors.Length];
    for(int i = 0; i < settings.customBehaviors.Length; i++) {
        btRoot.externalBehaviors = settings.customBehaviors;
    }
    btRoot.Disabled = false;
}
behaviorTree.EnableBehavior();
 
Without stepping through the code it's hard for me to say but subclassing the BehaviorTreeReference task does give you a lot more flexibility over what behavior trees are loaded. GetExternalBehaviors returns an ExternalBehavior[] so for that you do not need to use shared variables. If you want to set the variable at runtine you'd be able to access the property through the task through code.
 
Top