Search Unity

Behavior Designer - Behavior Trees for Everyone

Discussion in 'Assets and Asset Store' started by opsive, Feb 10, 2014.

  1. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    The Seek task doesn't do the actual movement - it relies on the NavMeshAgent for that. If you uncheck update rotation on the Seek task then it'll set NavMeshAgent.updateRotation to false so you can do your own rotation.

    That is correct. If you want the second branch to be able to be triggered then you should move it to the left of the Start Flow Graph branch and use a Lower Priority conditional abort: http://opsive.com/assets/BehaviorDesigner/documentation.php?id=89
     
    GamerPET likes this.
  2. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367

    The part on the right it's still in the thinking process.
    Right now I'm working on a "Fighter" kind of AI. He starts to "Seek" going to the Castle. When he detects something in rage, BD will receive an event with the "enemy" and we will go to "Pursue" that enemy.

    The part on the left, waits for an event from Bolt. I don't want the right parts of the BD to trigger until that event has been received. Basically my "Fighter" can be in the "ToBeSummoned" state and I don't want the Tree to run yet.

    Then I Syncronize the Variables and I go "Seek" the castle.

    What I want then to do is "Wait" for a new event from Bolt, something like (Enemy Detected). I think I will use the "Until Success" again. Then we will Pursue the Enemy.

    What I want to do then, is have some kind of checkup to see if the enemy get's out of range. If it get's out of range I want to restart the whole tree (except the 1st part. I want it to go to the Seek of the Castle).

    Another thing that I want is ... when the Pursue finishes... when my Fighter is near his Enemy, I want to send an event to Bolt so I can start Fighting.

    I'm still confused a bit on how to do this ... "Wait for S*** to happen and don't give a Fail yet". I'm also not sure how to do this checkup to see if my Enemy get's out of my range... and I also don't know how I can reset part of the Tree.

    Lot's of new information is coming my way :D

    EDIT: The way it is right now, my guy goes to that Seek state, but he never breaks out of it. That right sequence never triggers. I'm to tired to think right now :((

    EDIT2:

    Oh... ok... problem right now is that my guy still "Seeks" to go to the Castle and it's not going in the Pursue.
     
    Last edited: Dec 25, 2017
  3. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    It looks like you are using a parallel task which is running both movement related tasks (seek and patrol) so that is going to cause problems. You can instead organize that tree like the following image (using conditional aborts) and only one movement task will run at a time.

    tree.PNG
     
  4. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    I hope you woun't consider this "spam". If it's a problem tell me and I will delete my message.

    I'm doing a live stream right now ... where I work with Bolt & Behavior Designer. It will be cool if people want to join to see how I fail at doing stuff :D

    http://www.twitch.tv/gamerpet

    Thanks :D
    -----------------------------
    EDIT: Stream is now over. I will stream again tomorrow.

    BehaviorScreenshot.png
    This is how the Tree looks right now. Looks a bit messy. I also made it so the tree will restart when it get's to the end, so I have to be careful so I'm gonna send that "event" again... so the Fighter/Warrior will resume moving.

    Basically this is what this Tree is doing:

    1. When the unit has been summoned, we get to the right part of that 2nd Sequence.
    2. We sync our variables.
    3. We Seek for the "Castle". Meaning, we start moving left/right depending on what faction the unit is.
    4. We wait "Until Success". That is an event that comes from Bolt. That events checks to see if an Enemy is in Range.
    5. If the enemy is in range, we Interrupt our Seek, and we Pursue our new target.
    6. Then I'm waiting for "Until Success". If I'm within Rage (1.5) of my target.
    7. We then interrupt our pursue.
    8. We tell Bolt that we are in range, and then BOLT starts "Fighting" with that Unit.
    9. Then this Tree resets and waits for that "summoned" event. The event is called the same, but basically this event will be trrigered again when the unit will stop Fighting, so our Warrior/Fighter resumes "Seek"ing towards the Castle.

    The whole thing works... but I do have a feeling that it's "too complicated" ...

    Another thing that I'm doing is checking if my unit get's "Out of range". But I'm only doing that in Bolt, and only during the Fighting.

    I would like in BD to do a check when I'm PURSUING my target. My target might run from me, and I might keep trying to get to it while ... there will be other more important enemies near me.

    How do you suggest to stop the Pursue if my target get's "to far from my range". I want to break the pursue and go back at that "Seek" (Goes to the Castle) thing.

    Thank you and sorry for all my stupid questions.
     
    Last edited: Dec 26, 2017
  5. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    I have uploaded a new set of videos for those just getting started with behavior trees:





     
    Last edited: Dec 27, 2017
    fancyferret, red2blue and chiapet1021 like this.
  6. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    I highly recommend that you use conditional aborts - it'll simplify your tree pretty dramatically. This page also contains a set of resources for a complete behavior tree used within the Deathmatch AI Kit:

    http://opsive.com/assets/DeathmatchAIKit/documentation.php?id=3

    On this page I go through describing what each task is doing which should help in your situation.
     
  7. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Wow... thanks :D
    I'm going to watch your videos now.

    I tried to use conditional aborts but I just don't understand them yet. I will have to try harder.
     
  8. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    I think I had a mental breakthrough... a bit at least.

    When I did the AI for the Fighter/Warrior Unit, I made the Behavior Tree ... kind of like a state machine or a flow graph. With stuff going from Left to Right... and with States also in the Bolt.

    The way I think it should be done is, have a State in Bolt with some functions like "Look for Enemy", "Pick Enemy", "Deal Damage", and I think I need to have multiple running parts in my Behavior Tree and those multiple running parts they will call up the functions that they need.

    The way I did it ... I always had to keep track of 2 systems and see if I'm in the correct system in both parts.
     
  9. barcode802

    barcode802

    Joined:
    Apr 6, 2014
    Posts:
    26
    @opsive We are getting an error where behavior designer is causing errors preventing us from loading a scene using a simple script from a button. This error occurs when running our application on device (iOS/Android) but not in Editor. No errors appear in editor.

    The scene we are in and the scene we are loading aren't using Behavior Designer so we aren't clear on why this is occurring. We tried it with a completely empty scene as well. It is related to a null value and BehaviorDesigner.Runtime.

    This is the error in the console for iOS (in xcode):

    ```
    NullReferenceException: A null value was found where an object instance was required.
    at BehaviorDesigner.Runtime.Behavior.set_ExternalBehavior (BehaviorDesigner.Runtime.ExternalBehavior value) [0x00000] in <filename unknown>:0
    at Game.Scenes.SceneTransition.LoadSceneAdditive (UnityEngine.Object obj) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.UnityAction`1[T0].Invoke (.T0 arg0) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.InvokableCall`1[T1].Invoke (System.Object[] args) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.CachedInvokableCall`1[T].Invoke (System.Object[] args) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) [0x00000] in <filename unknown>:0
    at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <filename unknown>:0
    at UnityEngine.UI.Button.Press () [0x00000] in <filename unknown>:0
    at UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerEnterHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1].Invoke (.T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, Boolean pressed, Boolean released) [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchEvents () [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <filename unknown>:0
    at System.Text.RegularExpressions.IntervalCollection.Normalize () [0x00000] in <filename unknown>:0
    at UnityEngine.EventSystems.EventSystem.Update () [0x00000] in <filename unknown>:0
    at System.Collections.Generic.ICollection`1[T].get_Count () [0x00000] in <filename unknown>:0
    at System.Collections.Generic.ICollection`1[T].get_Count () [0x00000] in <filename unknown>:0
    at System.Collections.Generic.ICollection`1[T].get_Count () [0x00000] in <filename unknown>:0
    at System.Collections.Generic.ICollection`1[T].get_Count () [0x00000] in <filename unknown>:0
    UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object)
    UnityEngine.DebugLogHandler:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    UnityEngine.Logger:LogException(Exception, Object)
    UnityEngine.Debug:LogException(Exception)
    UnityEngine.Debug:LogException(Exception)
    UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchPress(PointerEventData, Boolean, Boolean)
    UnityEngine.EventSystems.StandaloneInputModule:processTouchEvents()
    UnityEngine.EventSystems.StandaloneInputModule:process()
    System.Text.RegularExpressions.IntervalCollection:Normalize()
    UnityEngine.EventSystems.EventSystem:Update()
    System.Collections.Generic.ICollection`1:get_Count()
    System.Collections.Generic.ICollection`1:get_Count()
    System.Collections.Generic.ICollection`1:get_Count()
    System.Collections.Generic.ICollection`1:get_Count()
    ```
     
  10. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    It looks like something is setting an external behavior tree value and that is throwing the error. I have a guess at what is causing it (though it should also occur in the editor). Can you send me a PM with your invoice number and the version of Unity you are using? I can then send you a potential fix.
     
  11. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Glad you are making progress. That is similar to what I did for the Playmaker sample project and it worked really well. If you haven't seen this video yet then I'd definitely take a look:

     
    GamerPET likes this.
  12. chaneya

    chaneya

    Joined:
    Jan 12, 2010
    Posts:
    401
    Justin,

    I just updated to the current version of Behavior Designer 1.5.11 along with the movement and tactical packs. I'm using the latest release of Unity 2017.3. I deleted all folders of the older version before importing the new versions.

    I'm getting 2 warning messages that reference the BehaviorDesignerGlobalVariables asset in the Resources folder under the Behavior Designer folder. (I don't use Global Variables in my Trees so I'm not sure if I need this)

    1.
    The referenced script on this Behaviour is missing!
    UnityEngine.Resources:Load(String, Type)
    BehaviorDesigner.Runtime.GlobalVariables:get_Instance()
    BehaviorDesigner.Editor.FieldInspector:GetVariablesOfType(Type, Boolean, String, BehaviorSource, String[]&, Int32&, Boolean)
    BehaviorDesigner.Editor.FieldInspector:DrawSharedVariable(Task, GUIContent, FieldInfo, Type, SharedVariable)
    BehaviorDesigner.Editor.TaskInspector:DrawSharedVariableValue(BehaviorSource, FieldInfo, GUIContent, Task, SharedVariable, Boolean, Boolean, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawObjectFields(BehaviorSource, TaskList, Task, Object, Boolean, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawTaskFields(BehaviorSource, TaskList, Task, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawTaskInspector(BehaviorSource, TaskList, Task, Boolean)
    BehaviorDesigner.Editor.BehaviorDesignerWindow:DrawPropertiesBox()
    BehaviorDesigner.Editor.BehaviorDesignerWindow:Draw()
    BehaviorDesigner.Editor.BehaviorDesignerWindow:OnGUI()
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr)

    2.
    The referenced script on this Behaviour (Game Object '<null>') is missing!
    UnityEngine.Resources:Load(String, Type)
    BehaviorDesigner.Runtime.GlobalVariables:get_Instance()
    BehaviorDesigner.Editor.FieldInspector:GetVariablesOfType(Type, Boolean, String, BehaviorSource, String[]&, Int32&, Boolean)
    BehaviorDesigner.Editor.FieldInspector:DrawSharedVariable(Task, GUIContent, FieldInfo, Type, SharedVariable)
    BehaviorDesigner.Editor.TaskInspector:DrawSharedVariableValue(BehaviorSource, FieldInfo, GUIContent, Task, SharedVariable, Boolean, Boolean, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawObjectFields(BehaviorSource, TaskList, Task, Object, Boolean, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawTaskFields(BehaviorSource, TaskList, Task, Boolean)
    BehaviorDesigner.Editor.TaskInspector:DrawTaskInspector(BehaviorSource, TaskList, Task, Boolean)
    BehaviorDesigner.Editor.BehaviorDesignerWindow:DrawPropertiesBox()
    BehaviorDesigner.Editor.BehaviorDesignerWindow:Draw()
    BehaviorDesigner.Editor.BehaviorDesignerWindow:OnGUI()
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr)

    If I try to recreate the BehaviorDesignerGlobalVariables asset using the menu option Tools/Behavior Designer/Global Variables, the Global Variable editor window opens however when I try to create any variable and click Add, nothing happens. The editor window just freezes until I click somewhere else. And then the variable name disappears.

    Also for some reason the Behavior Manager gameobject in my scene lost its reference to the Behavior Manager script. I fixed that by just adding component Behavior Manager to that Gameobject. But that's the first time I've had that happen when updating.

    PS: I think I fixed this in my project by deleting the BehaviorDesignerGlobalVariables asset and then when I again used the Global Variables Editor Window, it worked and created a new asset. I guess that first asset was corrupted somehow during import.

    Allan
     
    Last edited: Dec 28, 2017
  13. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    It looks like some guid references changed during the update so that's why you are getting those errors. If you aren't using global variables you can completely remove the global variables asset and it should correctly start from scratch. Reassigning the Behavior Manager makes sense if the guid was changed.
     
  14. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Ok... this is kind of driving me crazy to the point where I just want to stop using BD and just move back to State Machines, after all ... I did the AI for my units in 2 days (learning included) ... and with BD I still have almost no progress.

    This is what I'm trying to do:


    I watched your Playmaker video. It does make sense however I'm having some problems doing this with Bolt. I see that with Playmaker you are sending an ResumeBehaviorTree event with Success/Failure.

    I see that we also have this in Bolt:


    This is my tree right now:

    My logic process here is that the Selector will run ... any of the branches since it's kind of like an OR.

    My first branch is me "Seeking" which means that I will go to the castle and I have a OverlapSphere that checks if I have an enemy in range. Whenever I have an enemy in Range I populate an Variable called "currentEnemy".

    That's the moment when I want the Seeking to stop. My logic here is that I have to make the first Sequence FAIL, so we move to the next Sequence in order to do the Pursuing.

    Am I right? So my problem right now is ... how do I stop and fail the Seeking ... so we get to the next Sequence where we pursue.

    This is my Bolt logic:

    The ResumeHevaiorTree from up there is ... more for testing and trying purposes.

    Thanks for the help.
     
  15. chaneya

    chaneya

    Joined:
    Jan 12, 2010
    Posts:
    401
    Justin,

    Thanks again for a great asset!!

    I've mentioned this in previous posts but I just wanted to reiterate a few feature requests:

    I've come back to working on Behavior Trees after working on many other game related demands. My single biggest time sink is trying to identify the tasks that use specific shared variables within a Behavior Tree.

    (Requests in order of improved efficiency)

    1. being able to identify/highlight all tasks that use a specific variable through a search or some other mechanism
    2. ability to view the value of properties in the variables pane.....so I don't have to find an associated task that uses the property.
    3. ability to drag to reorganize variables in the variables pane instead of trying to click the tiny up and down arrows.
    3. ability to expand and shrink the Properties Panel (especially when variables section is selected since most of my properties extend beyond the width of the panel.)
    4. ability to hide/display the Comment field on individual tasks per task.
    5. A method to keep more extensive notes on tree branches. Consider your tutorials on the Opsive website where you show pictures of the overall tree and then you provide extensive notes on each branch. (What was I thinking when I crafted this branch? It appears to work but I can't remember why I made it this way. :))

    But really #1 is by far the most important.
    Allan
     
  16. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    You are correct. What you'll want to do is move the pursue branch to the left of the seek branch, and set a Lower Priority conditional abort on the sequence task that is the parent of the pursue task. This is a really similar setup to the branch in the "What is a Behavior Tree" at this time:



    The reason why your event isn't being received is because "Has Received Event" isn't being reevaluated. If you use a conditional abort and set that branch to a higher priority then "Has Received Event" will be reevaluated and it'll trigger. The circle status indicator indicates that the task is being reevaluated.
     
    GamerPET likes this.
  17. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Thanks Allan. I had a couple of those written down already and have wrote down the others. After we release version 2 of our character controllers we'll be doing a set of major BD updates and these will definitely be included.
     
    hopeful likes this.
  18. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    One more question. Why exactly does the Pursue have to be on the Left of the Seek branch?

    I'm asking because when the "Pursue" completes I will have to send/do some kind of "Fighting" state where the unit will be still, but still check for some other stuff.

    So should it be Fighting/Pursue/Seek?

    Is it the logic behind it ... that ... it will go to the Pursue, since the event is Failed, it will go to the next Branch which... will be the Seeking. So in a way seeking is more of a ... "Whenever some of those other important things are Failed, just do the last thing you can do... Seek".

    Another thing that I want to implement in another unit (an archer) will be "FLEE".

    So it can always be something like:

    If not > Fleeing then > Attack then > Seek

    hmm... it does make a little bit of sense... I guess.

    Man, behavior trees are really making me feel a bit stupid :D But I do have a feeling that's mostly this "how to think" barrier that once you go over it, you will be able to do amazing stuff.
     
  19. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Remember that behavior trees operate from top to bottom, left to right. In your original tree Pursue was never being evaluated because the Seek branch was active. By moving Pursue to the left of Seek that means that Pursue has a higher priority than Seek and will be evaluated first. A Lower Priority conditional abort is then used so the conditional task within the Pursue branch will be reevaluated. Without that conditional abort the Pursue branch would only be evaluated once (when the tree is initially traversing from top to bottom, left to right).
    Awesome. Yeah, I would never want to do something like the Deathmatch AI Kit tree without using something like behavior trees. The visual interface alone makes it worth it because you can play with your ideas much more easily.
     
    GamerPET likes this.
  20. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    BehaviorScreenshot.png
    Huge Success so far! But of course that I'm still finding small problems.

    The Sequence for the left will become true when I'm 100% sure that my unit is Summoned.

    The repeater for the Fighting is ... basically BD telling Bolt to "dealDamage" very 1 second. Seems to work fine.
    I wanted to use "Within Distance" for the fighting but that didn't really work. I know the currentEnemy which whom I want to check if I'm "Within Distance" but I could never made it fire.

    It fired however if I specified a Tag or a LayerMask, but it was basically triggerting with itself, so I just did that logic in Bolt.

    One HUGE problem that I'm having right now, is that the "currentEnemy" can become NULL. Meaning that my enemy is dead. This can happen anytime, even when the Unit is Fighting and Pursuing. How can I do a Reset that checks for the variable "currentEnemy" and then reset the whole SELECTOR. I don't want to reset the whole TREE because then that 1st part of the tree will wait for the "summoned" event which will never trigger because the Unit is already summoned.

    P.S. Well, the unit will not become Null, it will just become inActive since I'm sending it back to the Pool, but withing Bolt I'm setting "currentEnemy" to = Null. Maybe that's the place where I can trigger a Tree Reset? But again, I only want to reset the Selector and not the whole tree!

    Thanks & Happy New Year!

    EDIT: There is that Reset Behavior Tree that has a "Group". I'm not really sure how to use that Group thing. Maybe there is another way?

    EDIT2: I also need a way to check if my currentEnemy is still on "my range". How do you suggest to do that? Same thing, if not in my range, reset Branch.
     
    Last edited: Jan 1, 2018
  21. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Scrap what I said about the "check if my CurrentEnemy" is still on "my range" part. I actually did it!! :D

    I used a Conditional Evaluator where I specified the task "Within Distance". The target object is set up as "currentEnemy" and the Magnitude is my melee range (in case of the Fighting) and "my fight search radius" for the pursue.

    This is so cool. So if my target moves out of my melee range, I go to Pursue him! If he still runs and runs and he get's really out of my Fight Radius, then I just go back on Seeking.

    Of course, there would be other things to check out for. Maybe my target moves to far and I'm trying to chase to much, when I should just stop and deal damage to another enemy that is close to me. But I guess that I can improve the AI later.

    So the only thing (for now) that remains is that resetting of the branch when the enemy becomes null.
     
  22. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Happy New Year!

    What you'll want to do is immediately above the Selector that is highlighted in the screenshot of your previous post is add a Sequence task with the Self conditional abort type. Within the left branch of that you would then check if it is null and if it is it'll then back out of the tree. This is similar to the "Is Alive" check at the top of the Third Person Controller sample tree:

    http://opsive.com/assets/BehaviorDesigner/documentation.php?id=119

     
  23. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Well, the down side right now is that I'm getting that variable Null in two places.
    For example I'm using BD's "Within Range" action. And it works. If my unit is not Within Range, I will "fail" that branch and go back to Seeking. The down side is that since I'm doing this check in BD, I don't know how to make BD tell Bolt that "enemy is not in range anymore". My "currentEnemy" variable remains set to the enemy that I'm not chasing anymore. Because of this, Bolt will still think it's enemy is that old enemy and it will not look for a new enemy.

    Easiest way for me would be to not use Within Range and just do that "range" checkup in Bolt, and when that happens send an event to BD and Sync out the variables, but BD is doing those stuff so nice that I want to take advantage of that.


    I do have that action "enemyOutOfRange" that I want to send to Bolt, so Bolt knows how to Null that variable, but I'm not sure where to put this action. I was thinking on putting it into that Conditional Evaluator near Pursue or something, but no idea how to do it.

    So basically there are 2 checkups. Bolt has no more enemy so Bolt sets the value to NULL. BD has to figure that out and reset the Branch. 2nd: BD pursues/fights an enemy, that enemy is out of range or dies ... Branch get's reset.

    It's a bit delicate because the GameObject never get's to Null itself since I'm using an ObjectPool so I have to use ActiveInHierarchy.



    I think you suggested something like this? This kind of works, but when the unit dies the whole branch fails.

    P.S. I'm posting this message now. Let's see if Murphy's laws work. The moment I post this, I will find the answer myself.
     
  24. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    That is correct. What do you want it to do when the unit dies? You could add a repeater which repeats forever to the top of the tree which would start the tree back up again.
     
    GamerPET likes this.
  25. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Well. When the enemy of the unit dies, I want the unit to resume Fighting/Pursuing/Seeking. So a repeater in front of the Sequence that checks to see if the unit is Active in Hierarchy & Selector might actually work.

    I would still have this problem however. If my unit get's out of range. That unit will still be in the Hierarchy but I need to reset my Branch and Fight/Pursue/Seek a new enemy.

    I also need to tell Bolt that I have no new enemy so Bolt itself can do the Seeking. BD handles the movement, Bolt handles the OverlapSphere and runs trough all the enemies and picks one.

    This is a bit frustrating because I realise that what I want it's a bit basic, but because I don't yet fully understand this, I don't have progres.

    However I think now I understand conditional Aborts.

    If it's on Low Priority and I see that arrow pointing to the Right. That branch will re-evaluate itself while a branch TO THE RIGHT of it runs.

    If it's on Self, the Branch will re-evaluate itself while a CHILD of it runs.

    This is why I was able to put my branches like this:

    Fight > Pursue > Seek.

    Fight will re-evaluate itself (to see if receives a message from Bolt) while Pursue & Seek run.
    Pursue will re-evaluate itself (to see if it receives a message from Bolt) while Seek is running.

    MMMmmm...

    So let's recap:

    1. BD handles the Fighting/Pursue/Seek.
    2. While there is no "currentEnemy" Bolt is running an overlapSphere to detect enemies.
    3. When Bolt detects enemies it sends a message to BD that it detected an enemy.
    4. "currentEnemy" get's set and BD syncs all the variables.
    5. Now BD "Pursues" the new enemy.
    6. While pursuing BD also checks to see if currentEnemy is Within Range. If not, we Fail and go back to seeking.
    7. Right now I don't know how to tell BD & Bold that there is no current enemy anymore.
    8. When we get in Distance of 1.2 of the Current Enemy, we Fight. This check is done by Bolt.
    9. Bolt sends a message to BD that we are now Fighting.
    10. The unit fight until one of them dies. If the currentEnemy get's out of Melee range, the Fighter will go Pursuing. (this is actually kind of cool).
    11. If the currentEnemy dies or get's out of range, I need to set currentEnemy to NULL in BD & Bolt so we go back to Seeking.

    ----------------------
    Later with other units I will also want to do a "Flee" if there will be an enemy in range, etc ... but that's an adventure for another time :D

    Sorry for my lame questions. I do hope that at one point this will be useful for someone ... or at least me understanding this so I can do some videos.
     
  26. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    BehaviorScreenshot-v2.png
    This is the tree right now. Man I had to run on some weird places. The was a problem that the Unit was "killed", but the guy was still fighting with it. So I added another Conditional Evaluator that was checking to see if the unit is still in the hierarchy. This was never failing because the unit was still near that UNIT, it was just disabled :D

    Now my problem is that Syncronize Variables from the left, it's under that repeater. It only kind of runs once I think?

    Like for example after that Fighting part, I made it so I'm going to back to Seeking. Bolt knows that I have no more enemy so the variable "currentEnemy" is NULL in Bolt, but the variable remains SET in BD.

    Also check that Pursuing part. I finally made it so the Conditional Evaluator for the Pursuing will return success, that way we also go on the right of it when he fails so I'm sending the "enemyOutOfRange" message.

    What is weird that right now, when I'm writing this ... this feels wrong.

    One thing that I noticed is that the Actions that are running are FULL GREEN. Actiosn that ran before they have that Green check icon on them. So I tried to make that Pursuing part trigger to TRUE, so the Sequence from above them will run to the next task, and send that Bolt message.
     
  27. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Progress :)

    You can parent the Self Sequence task with a Selector, and then the right branch of that should notify Bolt that there is no more enemy.

    There's an option under the repeater to repeat forever. Take a look at this video for an overview of the repeater:



    It's tough to say without actually playing it but you can swap two conditional evaluators for two regular conditional tasks. Personally I prefer this because it makes the tree look cleaner.
     
  28. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    That repeater is set up to repeat forever. So the Sycronisation of the variables should happen every tick, yet it ... does not.

    Bolt = currentEnemy = Null.

    Yet in BD, currentEnemy remains set.

    https://streamable.com/alr1d

    Here is a short video with the two nodes.

    Here is a video with the whole thing in progress.
    https://streamable.com/0et6b

    You can see me spawning a Fighter & a Warrior (same AI).
    They go in range, they pursue eachother... when in Melee range they fight.

    You can see how at the end of the fight, the Fighter (blue guy) resumes Seeking, you can see how Bolt has "currentEnemy" as Null, yet that variable in BD still remains set.
     
  29. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Since the node stays green the task stays running so it is trying to synchronize. It's tough to say what is going on based off of that video but if you can tell me how to reproduce it from the bolt sample scene I should be able to give a better idea at what the cause is.
     
  30. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    So ... task = e running.
    Green check icon = task has already ran and returned Success.
    Red X icon = task has already ran and returned Failure.

    -----------------
    Not sure about the reproduction.

    My Unit has an OverlapSphere that runs trough all the enemies in range, and picks the closest one and set's it to "currentEnemy".



    I have a couple of checks to see if the unit is still "activeInHierarchy". If it's not, I set it to null, so that Null Check from above runs the part from below, doing the OverlapSphere again and picking a new target. Since it's in FixedUpdate it will continue to run that until it finds a target.

    When a target comes into range, I send BD a message "enemyDetected", BD knows how to Pursue, then I'm doing a check to see if I'm in "Melee" range. That's why you see that 1.1 there. When I am I send in melee range I send that mesage, withinMeleeRage.

    It's a bit weird that I'm sending that message "enemyDetected" & "withinMeleeRange" every frame but ... it seems to be ok.

    I think today I will start on the healer, which also has a Spell, and maybe the ability to Flee when an enemy get's in range.

    WISH ME LUCK!!! :D
     
  31. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Hey Justin Bro.

    Check out this video:
    https://clips.twitch.tv/BusyInquisitiveNeanderthalLeeroyJenkins

    You can see that I set up the Sync Variables to that Repeater that is repeating that forever. I expect it to sync the variables every tick. When my target get's into that Flee status, it get's that fleeTarget variable, but since BD is not syincing that variable, he does not know from who to Run. Weirdly enough, he just resumes "Seeking" even that ... the seek branch is not really active!! ... and somehow my Warrior also bugs out even that when the enemy is out of his range, he should have resume seeking himself.

    If I enambe that Sync Variables before the Flee, it works super super fine. The guy Flees niceley out of the Warrior's range, and the warrior will actually resume Seeking because the Archer runs so much that it get's out of the Warrior's "EnemyCheckRadius".

    No idea what is causing this.

    =====================================
    This is the archer status right after he stopped from Fleeing. This is with that SyncVariable enabled that is before the Flea. He Flees, he stops after 5 units and in theory he should resume Firing or Seeking.

    The problem here is that the guy is never ever doing anything else and I'm not sure why. I do have in Bolt a small function that sets the "nearEnemy" & "currentEnemy" to null after the Flee ends ..............

    BD-Archer-v.01.png

    I figured this part out! I am learning! The FLEE returns success so the rest of the branches don't run. I made the "Send Event" to return failure, that way we are going back on Pursuing & Seeking.

    1st Part still remains with that Syncronie Variable on Repeat.
     
  32. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    In your video the Synchronize Variable task stops being highlighted because you paused the game. Since that task doesn't return a status of running it's not currently active when you pause and the green highlight goes away so that is working correctly.

    Can you post the line numbers of the exception that you are getting?
     
  33. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    btw Justin, I'm live right now: http://www.twitch.tv/gamerpet

    You can see the whole thing in action.

    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. BehaviorDesigner.Runtime.Tasks.Movement.Flee.OnUpdate () (at Assets/Behavior Designer Movement/Scripts/Tasks/Flee.cs:33)
    3. BehaviorDesigner.Runtime.BehaviorManager.RunTask (BehaviorDesigner.Runtime.BehaviorTree behaviorTree, Int32 taskIndex, Int32 stackIndex, TaskStatus previousStatus)
    4. BehaviorDesigner.Runtime.BehaviorManager.Tick (BehaviorDesigner.Runtime.BehaviorTree behaviorTree)
    5. BehaviorDesigner.Runtime.BehaviorManager.Tick ()
    6. BehaviorDesigner.Runtime.BehaviorManager.Update ()
    Without pausing or anything, that thing somehow stops, it bugs out the units ... and I tired to spawn a new archer that in theory should have a "clear mind", yet he just bugs out himself not doing anything.
     
  34. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    That line points to:

    Code (csharp):
    1.  
    2.             if (Vector3.Magnitude(transform.position - target.Value.transform.position) > fleedDistance.Value) {
    3.  
    Which is likely caused by the target value being null. This does sound related to ensuring Bolt is synchronized so it has a target. If you can create a small test scene where that repeated Synchronize Variable task isn't synchronizing that should help me find the cause.
     
  35. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Yea. That's what I was thinking. The target get's to the Fleeing part and it is expecting an Enemy to run away from. Maybe that Repeat task is not doing that every frame... and only every 0.5 seconds (example) and when the Flee get's triggered, there is no Enemy and then everything bugs out.

    I will make a new scene for you but not sure how to and what to share with you.

    What if I give you access to the repo, and you can download the whole thing.
     
    Last edited: Jan 4, 2018
  36. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    I'm not sure from the Bolt side of things but from BD you can just send me the scene - I have a copy of BD :) If you'd rather zip up the whole repro project and send it to me that also works - support@opsive.com.
     
  37. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367
    Email send.
     
  38. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367


    I have something like this. A problem that I have is that my "currentHealAlly" might become NULL. The problem is that if I'm in "Pursue" and my target becomes Null, I start getting this error:

    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. BehaviorDesigner.Runtime.Tasks.Movement.Pursue.Target () (at Assets/Behavior Designer Movement/Scripts/Tasks/Pursue.cs:47)
    3. BehaviorDesigner.Runtime.Tasks.Movement.Pursue.OnUpdate () (at Assets/Behavior Designer Movement/Scripts/Tasks/Pursue.cs:38)
    4. BehaviorDesigner.Runtime.BehaviorManager.RunTask (BehaviorDesigner.Runtime.BehaviorTree behaviorTree, Int32 taskIndex, Int32 stackIndex, TaskStatus previousStatus)
    5. BehaviorDesigner.Runtime.BehaviorManager.Tick (BehaviorDesigner.Runtime.BehaviorTree behaviorTree)
    6. BehaviorDesigner.Runtime.BehaviorManager.Tick ()
    7. BehaviorDesigner.Runtime.BehaviorManager.Update ()
    8.  
    It makes sense. He is trying to pursue something that is Null.

    Question. How can I interrupt an action if my "Variable of type GameObject" becomes Null? I was thinking on doing a Conditional to check if "Is Null" but you can only do that with Strings.
     
  39. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    That's the correct approach - you can use the Compare Shared GameObject task to check for null.
     
  40. GamerPET

    GamerPET

    Joined:
    Dec 25, 2013
    Posts:
    367

    I think this works. I had to put that Abort Type to Self.
    It's a bit hard to debug since it only happens sometimes. So I had to spawn a lot of units and so far no error.

    Do you have any idea how to do a "Cooldown" of some sorts?

    I have that "healAlly" that you see on the left. Every tick, I Wait for "X" seconds, then you are allowed to go heal again. The thing is that if the branch resets for whatever reason, if you go back on seeking/pursuing, etc ... that Selector runs again and the Wait is being ignored somehow.

    Again it's hard for me to test this but I have seen a unit change targets and basically heal 2 units super super fast. Heal 1 unit, change target, heal the other the next frame.
     
  41. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    As your tree gets larger it gets harder and harder for me to follow along. If things are moving quickly I recommend placing a breakpoint (right click on the task) and then stepping through Unity to see exactly what is happening. This should then help you debug the cause. The wait task will never stop early unless it has been aborted.
     
  42. Duffking

    Duffking

    Joined:
    Oct 3, 2014
    Posts:
    24
    Hi, we're finding performance in the Behaviour Designer Editor window to be rather sluggish at the moment, in Unity 2017.3.0f3. I do see that the current version was uploaded using up to 2017.2.0, is this a simple incompatibility of sorts? It feels a ltitle laggy and cumbersome even on a newly created tree with no tasks in it and seems to get worse the longer I keep it open.
     
  43. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Hmm, have you tried profiling the editor to see what the bottleneck is? The Behavior Designer editor doesn't use any methods that changed in 2017.3 versus 2017.2 so a specific version isn't necessary. Can you also try 2017.2 to see if that works better for you?
     
  44. Duffking

    Duffking

    Joined:
    Oct 3, 2014
    Posts:
    24
    I've just had a look in the profiler and something seems to be spiking very hard, pretty frequently, but it doesn't appear to be behavior designer causing it. I think it's our source control for some reason. It's happening for all of us but only I seem to get any slowdown from it, and only once I open behavior designer. So I'm guessing what I'm seeing is a symptom of a problem with the version control rather than with behavior designer. We'll have a look into what's causing the spike on the profiler.
     
  45. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    2,142
    I took a look at the documentation, and searched this thread, but didn't see anything about it: would Behavior Designer work for goal oriented action planning? Is there anything I should know before giving it a try?
     
    kblood and blitzvb like this.
  46. blitzvb

    blitzvb

    Joined:
    Mar 20, 2015
    Posts:
    284
    In the same vein of question, I saw your utility node, is this fully functional as a utility AI?
     
  47. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Behavior trees aren't planners so there is a bit of a difference there. In the book Game AI Pro it does talk about taking the best of both AI techniques within this chapter. If you do try it let me know how it goes, it would be interesting to see if I can implement anything natively into Behavior Designer.
     
    EternalAmbiguity likes this.
  48. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Yes, for this one the Utility Selector can be used to evaluate curves as is done with utility AI. The Utility Selector implementation in Behavior Designer is based off of this chapter from Game AI Pro so that should give you an idea on how it works.
     
    blitzvb likes this.
  49. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    2,142
    There's something I don't understand. I took a look at the "Variables" section in the documentation, but didn't find it very clear. I have the following code:

    Code (csharp):
    1. public class CheckThirst : Conditional
    2. {
    3.     public double thirst;
    4.     public int importance;
    5.     // Checks stats script for this variable, then assigns importance based on where it's at
    6.     public override void OnAwake()
    7.     {
    8.         thirst = this.gameObject.GetComponent<HumanAttStats>().Thirst;
    9.         if (thirst > 80)
    10.             importance = 1;
    11.         else if (thirst > 60)
    12.             importance = 2;
    13.         else if (thirst > 40)
    14.             importance = 3;
    15.         else if (thirst > 20)
    16.             importance = 4;
    17.         else
    18.             importance = 5;
    19.     }
    20. }
    I then use Behavior Designer to run this:

    Unity_2018-01-25_04-11-10.png

    I also have a local variable:

    Unity_2018-01-25_04-11-21.png

    I want to use the "importance" variable within the single class to change that local variable in the tree, ultimately so that I can later check it. However, I don't see anywhere where I can assign that class variable "importance" to ThirstImportance. Can you tell me what to do here?

    The only thing I can think of is making a variable in my "HumanAttStats" class, and then having it access that instead. Seems like an obfuscation though.

    Additionally, how do I "return" success for a task on a void method like OnAwake()?
     
  50. opsive

    opsive

    Joined:
    Mar 15, 2010
    Posts:
    4,670
    Your importance variable should be of type SharedFloat so you can then assign your ThirstImportance variable to the field. Within your task you'll want to make sure you use the Value property as well:

    Code (csharp):
    1.  
    2. if (thirst > 80)
    3.    importance.Value = 1;
    4. ...
    5.  
    You don't need to - only OnUpdate has a return status of Success, Failure, or Running.