Execute logic before and after a BehaviorTreeReference task

Noxalus

New member
Hello everyone,

Since several days, I try to find the best approach to do that and I don't succeed to find a proper solution that works as I would like.

The idea is that I want to create a new task that act like the BehaviorTreeReference but is able to perform logic before and after its execution. Sadly, we never enter into OnStart/OnUpdate/OnEnd of a BehaviorReference, this type is only used bu the BehaviorManager to "import" the reference external behavior tree(s) into the current tree.

So I had to find another solution. What I tried to do is to create a small behavior that contain a sequence of action that will contain a behavior tree reference (BTR) between 2 actions that will be executed no matter what is the result of the BTR but still return the result of the BTR at the end (thanks to a custom "Complete Sequence" composite inspired by the classic Sequence but execute all children).

1637234581342.png

So from that I created a new task inherited from BehaviorReference and overrided the GetExternalBehaviors by that:

C#:
[SerializeField]
private ExternalBehavior _behavior;

public override ExternalBehavior[] GetExternalBehaviors()
{
        var externalBehaviorTree = Addressables.LoadAssetAsync<ExternalBehaviorTree>("THE_TREE_ADDRESS").WaitForCompletion();
        var reference = externalBehaviorTree.FindTask<BehaviorTreeReference>();

        reference.variables = variables;
        reference.externalBehaviors = new ExternalBehavior[] { _behavior };

        return new ExternalBehavior[] { externalBehaviorTree };
    }

    return null;
}

But it doesn't work because, the behavior tree reference is not replaced, probably because the externalBehaviorTree returned corresponds to the asset so my modification are not taken into account.

What I should do is to reproduce what is done in the BehaviorManager to duplicate the tasks from the sub BTR like in the method AddToTaskList, but it doesn't seem as simple as it sounds.

Do you know how I could achieve what I want?

I have another question that could solve my problem: Is it possible to start a behavior tree to a specific task? And can we get the next task from the current one?

Thanks a lot for your answer and have a nice day!
 

Justin

Administrator
Staff member
The GetExternalBehaviors method is executed when the behavior tree is loaded. As a result there isn't a before/after the tree execution because the tree hasn't executed yet. In situations like this what I have seen a lot of people do is either:

1. Stop the behavior tree, reset the ExternalBehavior property with a fresh tree, and start the behavior tree again. This will cause GetExternalBehaviors to load again which you can then switch out the referenced tree.
2. Add any possible execution branch to the tree, and use a conditional task to decide if the branch should run.

Behavior Designer follows the standard behavior tree implementation which does not allow you to start the tree from a specific branch. Using one of the methods above will hopefully work for your use case though.
 

Noxalus

New member
Thanks a lot for your answer @Justin !

The first solution sounds great, but I'm not really sure to know how to achieve this 🤔

What I did is to create a method SwapBehaviorTreeReference() in my InternalState class (which inherits from BehaviorReference) with this content:

C#:
public void SwapBehaviorTreeReference()
[SerializeField]
private ExternalBehavior _behavior;
public void SwapBehaviorTreeReference()
{
    var externalBehaviorTree = Addressables.LoadAssetAsync<ExternalBehaviorTree>("THE_TREE_ADDRESS").WaitForCompletion();
    var reference = externalBehaviorTree.FindTask<BehaviorTreeReference>();
    reference.variables = variables;
    reference.externalBehaviors = new ExternalBehavior[] { _behavior };
    externalBehaviors = externalBehaviorTree;
}

And I removed the override of the GetExternalBehaviors() method.

Then where I loaded the behavior tree, I added this code:

C#:
_behaviorTree.EnableBehavior();
_behaviorTree.DisableBehavior(false);

var internalStates = _behaviorTree.FindTasks<InternalState>();
foreach (var internalState in internalStates)
{
    internalState.SwapBehaviorTreeReference();
}

_behaviorTree.EnableBehavior();
_behaviorTree.Start();

But it doesn't work as expected neither, the BehaviorTreeReference is not loaded :/

Here is a really simple example:

1637315435127.png
And at runtime I get this:
1637315369845.png
1637315386223.png

So only the InternalState action has been replaced by the InternalBehavior tree (because it inherits from BehaviorReference and it's what the BehaviorManager does with a BehaviorReference).

But there is something I don't actually understand, it's the separation between static and dynamic data. In the code above _behaviorTree corresponds to a BehaviorTree and the task "Work" is an InternalState which replace itself by the InternalBehavior tree. So when I execute the code to find all tasks of type InternalState I shouldn't be able to find any reference if the tree has properly loaded right? It could work if I search for the BehaviorTreeReference instead. But I do find a reference of an InternalState task, which let me think FindTasks() is actually executed on the behavior tree data, and not the "runtime version" of it.
 
Last edited:

Justin

Administrator
Staff member
To get started this post gives a good example of restarting with a new external tree:



But there is something I don't actually understand, it's the separation between static and dynamic data. In the code above _behaviorTree corresponds to a BehaviorTree and the task "Work" is an InternalState which replace itself by the InternalBehavior tree. So when I execute the code to find all tasks of type InternalState I shouldn't be able to find any reference if the tree has properly loaded right? It could work if I search for the BehaviorTreeReference instead. But I do find a reference of an InternalState task, which let me think FindTasks() is actually executed on the behavior tree data, and not the "runtime version" of it.
If you are performing the find on the BehaviorTree component then it will return the task found based on the tree loaded at runtime. At runtime the external tree is consumed and those tasks are a separate instance. You can also perform the find on the external tree asset itself and this will then use the external tree.
 

Noxalus

New member
Thank you again for your help @Justin !

I finally resolve my issue doing like that:

C#:
_behaviorTree.DisableBehavior(false);

_behaviorTree.ExternalBehavior = null;
_behaviorTree.ExternalBehavior = MyBehavior;

_behaviorTree.EnableBehavior();
_behaviorTree.DisableBehavior(false);

var internalStates = _behaviorTree.ExternalBehavior.FindTasks<InternalState>();
var behaviorReferences = _behaviorTree.FindTasks<BehaviorReference>();

if (internalStates.Count == behaviorReferences.Count)
{
    for (int i = 0; i < behaviorReferences.Count; i++)
    {
        var internalState = internalStates[i];
        var behaviorReference = behaviorReferences[i];

        behaviorReference.externalBehaviors = new ExternalBehavior[] { internalState.GetStateBehavior() };
    }
}

_behaviorTree.EnableBehavior();
_behaviorTree.Start();

But it will only work if the FindTasks method always returns the same order for both cases :unsure:
 
Top