Behavior Tree "Gotchas"

This section simply lists a few easy-to-make errors when creating a behavior tree.

I’m happy to add more items to this list, so let me know if there are other “gotchas” that belong here! (Obviously you can just post them to this forum thread. Ultimately this will be migrated to our official documentation, and I’ll happily incorporate other suggestions there.)

For more information about Behavior Trees, see the Behavior Tree Documentation hub thread.

Selector nodes are not “if-else” statements!
When making children of a Selector, it’s easy to accidentally assume that since priority is left-to-right, if branch A starts with “If Goal is Set”, then B would only happen “If Goal is not set.”

However, that’s not the case! Any number of other reasons could cause branch A to fail. Perhaps there are other conditionals (either at that level or lower in the tree). Even without any other conditionals, the task itself can fail for some reason.

For example, if you just have “If Goal Is Set”, “Move To Goal”, the “Move To Goal” task could fail due to not finding a path to the goal. In that case, you may still enter Branch B even though Goal is Set.

So, to properly guard against that case (if you want to do something only when the goal is NOT set), you need to add a decorator for “If Goal Is Not Set”. That decorator is NOT redundant!

Decorators don’t all execute top-to-bottom!

While it’s true that “conditional” decorators do execute top-to-bottom in a group, that shouldn’t usually matter. (“Conditional” decorators shouldn’t be changing any values anyway, so they won’t have any effect on each other. Basically they act as a big list of “And” requirements; the only reason the order matters at all is as a possible performance optimization, but generally that won’t rise to a level where it actually matters.

Other types of decorators (“standard” decorators) don’t necessarily execute in any order relative to the conditional decorators. For instance, “Force Success” happens whenever the subtree (including the node “Force Success” is on and all of its decorators) is going to return for any reason. In that case, if it’s failing, “Force Success” will change the return value to true. So for example, if you have a node with “If Goal Is Set” and “Force Success” on it, it doesn’t matter what order those decorators are in! Force Success occurs when the node returns, which could be when “If Goal Is Set” returns false. So even if “If Goal Is Set” is above “Force Success”, success will STILL be forced when “If Goal Is Set” fails!

Similarly, “Loop” as a decorator also happens when the node returns, so all of the other decorators will be re-executed through each loop even if they are above “Loop” in the node.

If you want to make sure that some conditional happens only once and THEN a loop occurs, you’ll need the conditional to be on a higher parent node in the tree. The same goes for any conditionals which you do NOT want to force success on.

We intend to improve the UI of our Behavior Trees to show these types of decorators separately and put them in separate lists. However, there’s currently nothing stopping users from making nodes that behave in both ways, in which case the two parts of execution occur at different times. So be wary about presuming the order!

Blueprint Nodes: Events must be unregistered when aborting or deactivating!

Blueprint nodes can use any blueprint functionality they like, including Delay, Timer Callbacks, Timelines, and registration for other (“external”) events. Delays, Timer Callbacks, and Timelines (and other types of “latent” actions in the blueprint) are automatically canceled when the node is deactivated or aborted.

However, if you register for any other external events, you MUST unregister on abort and deactivate. Otherwise, your blueprint might continue to execute code even while the Behavior Tree is executing a completely separate subtree, which can cause bizarre side effects and bugs.

Using any “Tick” Events can in behavior tree blueprint nodes may be bad for perf!

I’ll add some more details here in a bit. Mieszko just suggested I add this point as I was posting the others. :slight_smile:

Hi Daniel! For clarity, can you give some explicit examples about the “registering for external event” gotcha for us non C++ folks? Do you mean things like passing references to other blueprints and then having that BP do some sort of event with the reference? If so, is simply clearing that reference to ‘none’ and clearing timers/etc. the preferred method of de-registering? Thanks!

As I mentioned above, Timers should be automatically cancelled on deactivate, so you don’t have to worry about that.

Other “external” events could be anything you may have implemented in your own game. Basically, by external, I mean an event that is called by code or blueprints outside of the behavior tree (or at least outside of the subtree in which your node is executing). One way that could be done is by calling a function on some other blueprint (like the AI pawn) which registers for that pawn to call the event on your node when the event occurs. If it’s done by calling a “register” function, you’d need to call “unregister”. If it’s done by setting a variable or reference, then clearing that reference to none would be correct. However you are causing yourself to get the event called from outside the normal tree flow, you need to make sure you aren’t receiving that event when your node is not active.

Fortunately, we handle the most common built-in events for you (Timers, Timelines, and Delays), so no need to worry about those!

I hope that helps. :slight_smile:

Bit of a long shot as this is an old thread but thought I’d see… I just upgraded to 4.25 preview 7 and I’m having issues with AISystem.cpp. In particular the registering and unregistering of components.

The *check *at the bottom always fires now and the ensure sometimes fires. It’s hard to make out what’s going on as values are optimized away. This wasn’t occurring in 4.24.

For the *check * I think it’s reference to an the AI actor controller hanging around. Or maybe it’s not being removed by RemoveSingle??

Feedback: I quite like the AI system but I’m ready to ditch it. It’s by far the biggest time sink in debugging issues and just getting it to work for this project. I can see a lot of love and thought was put into it but when using it in anger… it’s hell.



void UAISystem::UnregisterBlackboardComponent(UBlackboardData& BlackboardData, UBlackboardComponent& BlackboardComp)
{
    // this is actually possible, we can end up unregistering before UBlackboardComponent cached its BrainComponent
    // which currently is tied to the whole process.
    // @todo remove this dependency
**ensure(BlackboardDataToComponentsMap.FindPair(&BlackboardData, &BlackboardComp) != nullptr);**

    if (BlackboardData.Parent)
    {
        UnregisterBlackboardComponent(*BlackboardData.Parent, BlackboardComp);
    }
    BlackboardDataToComponentsMap.RemoveSingle(&BlackboardData, &BlackboardComp);

    // mismatch of Register/Unregister.
**check(BlackboardDataToComponentsMap.FindPair(&BlackboardData, &BlackboardComp) == nullptr);**
}




I am experiencing this exact issue. I have been fine thus far, by kicking off my AI in my controllers via:


GameLevelAIController::OnPosses()

I would call in C++ :


 
         // Set up Blackboard and Behavior Tree         UBlackboardComponent* BBCompPointer;         UseBlackboard(BlackboardAsset, BBCompPointer); 

I am doing this instead of manually managing A Blackboard component on the controller class / BP ( maybe that is bad )… but just by calling that in my game code, I get the ensure you pointed out due to the Blackboard Component getting initialized twice and not handling it correctly, which totally crashes a packaged build and although its fine in editor for me, it does endlessly hit the ensure in UnregisterBlackboardComponent().

I think this is a legit new bug in 4.25. Hope for fix or work around soon!

This definitely seems to be an issue introduced with 4.25.

I’m currently working on a blueprint-only project, and since updating to the 4.25 release today we are experiencing a crash whenever closing out of the game:


Assertion failed: BlackboardDataToComponentsMap.FindPair(&BlackboardData, &BlackboardComp) == nullptr 

This is the only thread I can find discussing this issue, and we’re not sure how to proceed, still looking for a blueprint workaround at the moment.

I’ve been looking into it and I’ve managed to work around it.

  1. I moved my initialization code to the first frame of the tick. It seems that required components weren’t being initialized in time (didn’t always happen)
  2. I removed all synced keys from my blackboard - for whatever reason they hang around when cleaning up and thus the ensure is fired

https://github.com/EpicGames/UnrealEngine/commit/d1de12ca8c5d44839a72a6328968deac06c92837
Enjoy.

Awesome. I came back here to reply, as I had gotten a proper UDN response on this. It is a known issue that already has a fix in UE GitHub if you have access to that.

And yeah, the suggested work around was to just nuke Synchronized keys as you say for now, as that is what is ultimately causing this issue. Just doing that was fine for me as I had only 1 sync key and I was no longer even using it. But I imagine if you need them right now, you will want to get that fix!

I, too, am having this issue.

However, using the workaround that Bino mentioned:

I disabled Instance Synced on the one key I have that was using it and it stopped crashing.

I should highlight that this is a bug and this approach is a work around. Its been fixed Unreal Engine Issues and Bug Tracker (UE-92936)

Workaround with synced keys also worked for me (blueprint project) Thank you Bino!

Note that the fix is in 4.26. Since this issue is a lot more wide spread than we thought I’ll merge the fix to 4.25.1.

Completely missed this message. Thanks! :slight_smile: My debugging skills failed me… completely missed it was called twice… Seems really obvious now facepalm.

Very new to BTs, so I have to ask.
Using the task Run Behavior allows you to select another BT to execute. But if these use different BBs, then the subtree won’t execute past the first composite.

Example:

In the bottom example, sub BT with different BB from what the main one is using won’t execute.
Is that intended?

Edit; to Run Behavior in another tree, the behavior trees needs to have the same blackboard - something that the top example did have, but the bottom did not.

Thanks a lot for sharing this information. When reading about decorators not all being executed top to bottom, what about services, if I have more than one?

bt-service-order.PNG

All my tests show that the services get executed top to bottom, but is this a guarantee? It is important if for example one service sets blackboard values, which another one relies on.