Announcement

Collapse
No announcement yet.

Behavior Tree "Gotchas"

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    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.
    Last edited by Daniel Broder; 05-14-2014, 06:54 PM.
    Daniel Broder
    Senior Gameplay Programmer, Epic Games

    #2
    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!
    Daniel Broder
    Senior Gameplay Programmer, Epic Games

    Comment


      #3
      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!
      Daniel Broder
      Senior Gameplay Programmer, Epic Games

      Comment


        #4
        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.
        Daniel Broder
        Senior Gameplay Programmer, Epic Games

        Comment


          #5
          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.
          Daniel Broder
          Senior Gameplay Programmer, Epic Games

          Comment


            #6
            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!
            www.towerofguns.com

            Comment


              #7
              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.
              Daniel Broder
              Senior Gameplay Programmer, Epic Games

              Comment


                #8
                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.


                Code:
                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);
                }
                Last edited by Bino; 05-07-2020, 10:35 AM.

                Comment


                  #9
                  Originally posted by Bino View Post
                  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 a lot of love and thought was put into it but when using it in anger... it's hell.


                  Code:
                  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:

                  Code:
                  GameLevelAIController::OnPosses()
                  I would call in C++ :

                  Code:
                   
                           // 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!

                  Comment


                    #10
                    Originally posted by nakedeyes View Post

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

                    Code:
                    GameLevelAIController::OnPosses()
                    I would call in C++ :

                    Code:
                    // 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:
                    Code:
                    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.

                    Comment


                      #11
                      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

                      Comment


                        #12
                        https://github.com/EpicGames/UnrealE...68deac06c92837
                        Enjoy.

                        Comment


                          #13
                          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!

                          Comment


                            #14
                            Originally posted by AJ_Lakeman View Post

                            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:
                            Code:
                            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, too, am having this issue.

                            However, using the workaround that Bino mentioned:

                            Originally posted by Bino View Post
                            2) I removed all synced keys from my blackboard - for whatever reason they hang around when cleaning up and thus the ensure is fired
                            I disabled Instance Synced on the one key I have that was using it and it stopped crashing.


                            Comment


                              #15
                              I should highlight that this is a bug and this approach is a work around. Its been fixed https://issues.unrealengine.com/issue/UE-92936
                              Last edited by Bino; 05-09-2020, 07:47 PM.

                              Comment

                              Working...
                              X