Search Unity

[AI SCRIPTING] Panda BT: Behaviour Tree scripting

Discussion in 'Assets and Asset Store' started by ericbegue, Feb 26, 2016.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I was talking about the first fallback, its second member is confusing me (plus a double neg = :(
     
  2. dragonmbois

    dragonmbois

    Joined:
    Oct 29, 2014
    Posts:
    2
    Hai guys, before i ask something, let me thanks to Eric Begue who develop this library, and im from Indonesia ^^ and now im working for my theses.

    i want to ask, can i customized "wait time" like "wait (delay attack)" ?

    here my BT

    Code (CSharp):
    1.  
    2. tree("Root")
    3.     parallel
    4.         repeat mute tree("Moving")
    5.         repeat mute tree("Attack")
    6.         repeat mute tree("Running")
    7.  
    8.  
    9. tree("Moving")
    10.     while attackState
    11.         sequence
    12.             Search_Nearest_Enemy
    13.             Set_Destination_Player
    14.             Wait 1.0
    15.             Move_To_Destination
    16.  
    17. tree("Attack")
    18.     while attackStateLooping
    19.         sequence
    20.             Wait 2.0             // << this is time i want to custom
    21.             NormalAttack
     
    Last edited: Apr 18, 2017
  3. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    The first fallback is there to handle whether the tree succeeds or fails (keep in mind that the while-repeat only complete in failure). The tree succeeds when a unit has been deployed, otherwise it fails. "tryDeployingUnit" was not a good name, it indeed leads to a confusing double negation/inverted logic. I renamed that to "isUnitDeployed", which is more straight. I also renamed the tree to just "DeployUnit". With those corrected, the script should make more sens:

    Code (CSharp):
    1. tree("DeployUnit")
    2.     sequence
    3.         isUnitDeployed(false)
    4.         fallback
    5.             while not isUnitDeployed
    6.                 repeat
    7.                     sequence
    8.                         chooseMostNeededUnit // If we went through all units, this fail.
    9.                         fallback
    10.                             sequence
    11.                                 tree "DeployUnitForward"
    12.                                 isUnitDeployed(true) // When deploying succeeds, stop.
    13.                             Succeed // We want to continue trying with next unit.
    14.             isUnitDeployed // Succeed the tree only when a unit has been deployed.
     
    Last edited: Apr 16, 2017
  4. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @dragonmbois
    Thank you for your interest in Panda BT, and it's great that you are using it in your theses! If you like this package, please leave a review on the Asset Store.

    Also, welcome on the Unity forums! The usual welcoming message around here is: "please use code tags".

    If you want to wait for a variable amount of time, you would need to handle the variable on the C# side. Also, you can reuse implemented tasks to define new ones. The Wait task is defined on the PandaBehaviour component, so you can defined your new task and pass your variable to the Wait task, like this:
    Code (CSharp):
    1. using UnityEngine;
    2. using Panda;
    3.  
    4. public class CustomTaskWithVariable : MonoBehaviour
    5. {
    6.     public float attackDelay = 2.0f;
    7.  
    8.     PandaBehaviour _bt = null;
    9.  
    10.     void Start()
    11.     {
    12.         _bt = this.gameObject.GetComponent<PandaBehaviour>();
    13.     }
    14.  
    15.     [Task]
    16.     void Wait_attackDelay()
    17.     {
    18.         _bt.Wait( attackDelay );
    19.     }
    20. }
    21.  
    Then you can use this new task in the BT script, like this:
    Code (CSharp):
    1. tree("Attack")
    2.     while attackStateLooping
    3.     sequence
    4.         Wait_attackDelay // << Use the custom task here
    5.         NormalAttack
     
    Last edited: Apr 16, 2017
  5. dragonmbois

    dragonmbois

    Joined:
    Oct 29, 2014
    Posts:
    2
    thank you so much for your help @ericbegue ! :)
     
  6. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Gotcha, so you wanted to garanty that the tree returned a success is that why you added isUnitDeployed?
     
  7. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    Yes, but only when a unit has been deployed, otherwise the tree should fail. Basically, the returned status of the tree is the same as isUnitDeployed, which reflects whether a unit has been deployed or not.
     
    laurentlavigne likes this.
  8. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Hi guys,

    Panda BT 1.4.1 is on his way to the Asset Store. This version contains some fixes and the new feature discuss in this post. That is, a new method on the PandaBehaviour component (GetTree), which allows you to retrieves a tree by name on the C# sharp side. So you can, for instance, decide at run time which tree to run.

    As usual, if you don't want to wait until the package hits the Asset Store, pm me and I'll send you the package.
     
    laurentlavigne likes this.
  9. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    In 1.4.1 I'm getting these errors when double clicking on a method in the panda pro editor and anytime the panda script is being parsed:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. Panda.TaskImplementation.Get (System.Object[] implementers) (at Assets/Plugins/PandaBehaviour/Core/Panda/BTLInterpreters/BTRuntimeBuilder.cs:641)
    3. Panda.GUIBTScript.GetTaskImplementation (Panda.GUINode task) (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/LiveCode/BTEditor/GUIBTScript.cs:1637)
    4. Panda.GUIBTScript.OpenScript (Panda.GUINode task) (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/LiveCode/BTEditor/GUIBTScript.cs:1622)
    5. Panda.GUINode.OnGUI () (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/LiveCode/BTEditor/GUINode.cs:334)
    6. Panda.GUILine.OnGUI () (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/LiveCode/BTEditor/GUILine.cs:513)
    7. Panda.GUIBTScript.OnGUI () (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/LiveCode/BTEditor/GUIBTScript.cs:1104)
    8. Panda.BehaviourTreeEditor.DisplayBTScript (Int32 i) (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/Editor/BehaviourTreeEditor.cs:367)
    9. Panda.BehaviourTreeEditor.OnInspectorGUI () (at Assets/Plugins/PandaBehaviour/Core/PandaUnity/Editor/BehaviourTreeEditor.cs:322)
    10. UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editor, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1229)
    11. UnityEditor.DockArea:OnGUI()
    12.  
    Code (CSharp):
    1. Unexpected top level layout group! Missing GUILayout.EndScrollView/EndVertical/EndHorizontal?
    2. UnityEditor.DockArea:OnGUI()
    3.  
     
    Last edited: May 4, 2017
  10. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    bug: when a task has too many definition or is not defined, deleting that task in the panda pro editor does nothing.
     
  11. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    bug: in the following configuration it's not possible to move LaunchForce to be the second child of fallback.
    EDIT: it is possible but require first to slide LaunchForce to be the first child of fallback and then move sequence as 1st child of fallback ....
    force.PNG
     
  12. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    bug: in the pro editor, right clicking a node sometime opens the context menu and sometimes slides the node
     
  13. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    Nice BT system , it saves lot of coding and most conditions values can be tweaked in the BT file.
    What is the impact on code performance or memory usage ?
     
    Last edited: May 5, 2017
  14. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    double tab on line 3? outdent lines 3 to 5 and verify your formating
     
  15. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    Yep this could be the issue, i got it working finally, if would be good to have some advanced BT text editor integrated in the editor to avoid such issues.
    There is also Behaviour Bricks that is free ( the downside this is a DLL so you can't quickly fix issues yourself), i am evaluating both plugins about productivity before picking one.
     
  16. HyenaGamesDev

    HyenaGamesDev

    Joined:
    Aug 4, 2013
    Posts:
    32
    Greetings Everyone. I'm pretty new to BT's and Panda. I'm working on my first BT, just trying to get an enemy to chase the player when the player moves within range. I have it working... sorta. But I have a problem. The enemy has a task to target the player. that task only seems to be called once every second. So as the player zips around, the enemy doesn't update it's destination very quickly. Here is what my BT looks like...

    Code (csharp):
    1.  
    2. tree ("Root")
    3.     fallback
    4.         tree ("AttackPlayer")
    5.         tree ("ChasePlayer")
    6.         tree ("Wander")
    7.  
    8. tree ("AttackPlayer")
    9.     repeat
    10.         sequence
    11.             IsPlayerWithinReach
    12.             Attack
    13.  
    14. tree ("ChasePlayer")
    15.     repeat
    16.         sequence
    17.             IsPlayerNear
    18.             SetDestination_Player
    19.             MoveTo_Destination
    20.  
    21.  
    22. tree ("Wander")
    23.     while
    24.         sequence
    25.             not IsPlayerNear
    26.             not IsPlayerWithinReach
    27.         sequence
    28.             Wander
    29.             repeat Succeed
    30.  
    It's the ChasePlayer tree that seems to work, but update slowly. It's like the Tick for the AI only gets called once per second or something... Is that right? Is there a way to speed it up? Am I doing something wrong?

    Thanks,

    Chris
     
  17. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    If nodegraph is your thing then yes. I tried a few BT things and Panda is the only one I still use because blackboard turns into spaghetti real quick and code coupling in panda is super easy. I use panda as a top structure type thing and my code is so minimalist now I sometimes feel guilty ;)
    Turn on those vertical indentation lines in your editor - it'll help.

    @CVogel remove all the repeats and see if that helps. Don't forget to outdent.
     
  18. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    Chase must recalculate the position as long as the player is near
    1. tree ("ChasePlayer")
    2. while
    3. IsPlayerNear
    4. sequence
    5. SetDestination_Player
    6. MoveTo_Destination
    Also your tree ("AttackPlayer") looks like it needs a "while" conditionning.
    1. tree ("AttackPlayer")
    2. while
    3. IsPlayerWithinReach
    4. Attack

    You ("Wander") looks like incorrect because "while" doesn't have any conditionning and sequence instead.
    It's surprising the BT compiled ? o_O

    I have Node Canvas, the inconvenient is each task you create is a class , this can make lot of classes in the project to manage. As you said PandaB is more lightweight and is very readable and somewhat more practical as you can put as many tasks as you want in a class.
     
    Last edited: May 5, 2017
  19. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Recreating the force from RTYPE I can't get it to behave, mixing and matching mute and repeat at random. BT is probably overkill for something this simple but I'd like to give it one last chance before I rip the bt out of this simple project and just straight up code it in c#.
    I'm probably doing something very wrong in this BT code, any idea?

    Code (CSharp):
    1. //This Behaviour Tree controls the force.
    2. tree("Root")
    3.     //Controll movement, aiming, firing and dying.
    4.     parallel
    5.         repeat mute tree("fire")
    6.         repeat mute tree("from input to input mode")
    7.         repeat mute tree("from input mode to movement")
    8.  
    9. tree("fire")
    10.     Sequence
    11.         IsFireButtonPressed
    12.         Fire
    13.  
    14. tree("from input to input mode")
    15.     sequence
    16.         IsLaunchButtonDown
    17.         //toggle mode
    18.         fallback
    19.             sequence
    20.                 fallback
    21.                     IsMode(InputMode.TETHERED)
    22.                     IsMode(InputMode.RETRIEVE)
    23.                 SetMode (InputMode.LAUNCH)
    24.             SetMode(InputMode.RETRIEVE)
    25.  
    26.  
    27. tree("from input mode to movement")
    28.     fallback
    29.         while IsMode(InputMode.RETRIEVE)
    30.             tree("retrieve")
    31.         while IsMode(InputMode.LAUNCH)
    32.             tree("launch")
    33.  
    34. tree("retrieve")
    35.     sequence
    36.         SetSpeed(0.5)
    37.         parallel
    38.             fallback
    39.                 repeat SetDestinationToPlayer
    40.             MoveToDestination
    41.         TetherToPlayer(true)
    42.         SetMode(InputMode.TETHERED)
    43.  
    44. tree("launch")
    45.     fallback
    46.         sequence
    47.             isTethered
    48.             TetherToPlayer(false)
    49.             SetSpeed(2.0)
    50.             SetDestinationToJettison
    51.             MoveToDestination
    52.             SetSpeed(0.5)
    53.             SetMode(InputMode.FREE)
    54.         tree("dance freely with player")
    55.  
    56. tree("dance freely with player")
    57.     sequence
    58.         SetSpeed(0.3)
    59.         //match vertical position
    60.         SetVerticalDestinationToPlayer
    61.         //mirror horizontal position
    62.         parallel
    63.             fallback
    64.                 sequence
    65.                     IsPlayerIn(ScreenPosition.LEFT)
    66.                     SetHorizontalDestinationTo(ScreenPosition.RIGHT)
    67.                 sequence
    68.                     IsPlayerIn(ScreenPosition.RIGHT)
    69.                     SetHorizontalDestinationTo(ScreenPosition.LEFT)
    70.             MoveToDestination
    71.  

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Panda;
    5.  
    6. public class Force : MonoBehaviour {
    7.     [Task]
    8.     public bool IsLaunchButtonDown()
    9.     {
    10.         return Input.GetButtonDown ("Launch");
    11.     }
    12.  
    13.     public enum InputMode{RETRIEVE, LAUNCH, FREE, TETHERED}
    14.     InputMode inputMode = InputMode.RETRIEVE;
    15.  
    16.     [Task]
    17.     public bool IsMode(InputMode mode)
    18.     {
    19.         return inputMode == mode;
    20.     }
    21.  
    22.     [Task]
    23.     public bool SetMode(InputMode mode)
    24.     {
    25.         inputMode = mode;
    26.         return true;
    27.     }
    28.  
    29.     Vector3 destination;
    30.     [SerializeField] PlayerController player;
    31.  
    32.     [Task]
    33.     public bool SetDestinationToPlayer()
    34.     {
    35.         destination = player.forceTether.position;
    36.         return true;
    37.     }
    38.  
    39.     [Task]
    40.     public void MoveToDestination()
    41.     {
    42.         if (Vector3.Distance (transform.position, destination) > .1f) {
    43.             transform.position = Vector3.MoveTowards (transform.position, destination, currentSpeed);
    44.         } else
    45.             Task.current.Succeed();
    46.     }
    47.  
    48.     [Task]
    49.     bool isTethered = false;
    50.     [Task]
    51.     public bool TetherToPlayer(bool t)
    52.     {
    53.         transform.parent = t ? player.transform : null;
    54.         isTethered = t;
    55.         inputMode = t ? InputMode.TETHERED : InputMode.FREE;
    56.         return true;
    57.     }
    58.  
    59.     float currentSpeed;
    60.  
    61.     [Task]
    62.     public bool SetSpeed(float speed)
    63.     {
    64.         currentSpeed = speed;
    65.         return true;
    66.     }
    67.  
    68.     [Task]
    69.     public bool SetDestinationToJettison()
    70.     {
    71.         destination = player.forceTether.position + currentSpeed * 50 * Vector3.right;
    72.         return true;
    73.     }
    74.  
    75.     [Task]
    76.     public bool SetVerticalDestinationToPlayer()
    77.     {
    78.         var position = transform.position;
    79.         position.y = player.transform.position.y;
    80.         return true;
    81.     }
    82.  
    83.     public enum ScreenPosition {RIGHT, LEFT}
    84.     [Task]
    85.     public bool IsPlayerIn(ScreenPosition spos)
    86.     {
    87.         return player.transform.position.x < Camera.main.transform.position.x ? spos == ScreenPosition.LEFT : spos == ScreenPosition.RIGHT;
    88.     }
    89.  
    90.     [Task]
    91.     public bool SetHorizontalDestinationTo(ScreenPosition spos)
    92.     {
    93.         var distance = Mathf.Abs( Camera.main.transform.position.z - player.transform.position.z);
    94.         var frustrumHeight = 2.0f * distance * Mathf.Tan (Camera.main.fieldOfView - 0.5f - Mathf.Deg2Rad);
    95.         var frustrumWidth = frustrumHeight * Camera.main.aspect;
    96.         float minXWorldSpace = Camera.main.transform.position.x - frustrumWidth / 2 * .6f;
    97.         float maxXWorldSpace = Camera.main.transform.position.x + frustrumWidth / 2 * .6f;
    98.         destination.x = spos == ScreenPosition.LEFT ? minXWorldSpace : maxXWorldSpace;
    99.         return true;
    100.     }
    101.      
    102.  
    103. }
    104.  
    beam.PNG
    EDIT: I removed the BT and went full c#, that's what I was trying to do, it's still lacking a few subtleties from the original but it's very manageable

    Code (CSharp):
    1. void Update()
    2.     {
    3.         if (Input.GetButtonDown ("Fire"))
    4.             GetComponent<ShootingController> ().Fire ();
    5.        
    6.         if (Input.GetButtonDown ("Launch"))
    7.             switch (inputMode) {
    8.             case InputMode.FREE:
    9.             SetMode(InputMode.RETRIEVE);
    10.             break;
    11.             case InputMode.LAUNCH:
    12.                 SetMode (InputMode.RETRIEVE);
    13.                 break;
    14.             case InputMode.RETRIEVE:
    15.             SetMode(InputMode.FREE);
    16.             break;
    17.             case InputMode.TETHERED:
    18.             SetMode(InputMode.LAUNCH);
    19.             break;
    20.             }
    21.  
    22.         switch (inputMode) {
    23.         case InputMode.FREE:
    24.             SetSpeed(.2f);
    25.             SetVerticalDestinationToPlayer ();
    26.             SetHorizontalDestinationTo (IsPlayerIn (ScreenPosition.LEFT) ? ScreenPosition.RIGHT : ScreenPosition.LEFT);
    27.             MoveToDestinationB ();
    28.             break;
    29.         case InputMode.LAUNCH:
    30.             TetherToPlayer (false);
    31.             SetSpeed (2.0f);
    32.             destination = player.forceTetherFront.position + 20 * Vector3.right;
    33.             if (MoveToDestinationB ()) {
    34.                 SetSpeed (.2f);
    35.                 SetMode (InputMode.FREE);
    36.             }
    37.             break;
    38.         case InputMode.RETRIEVE:
    39.             SetSpeed (.5f);
    40.             SetDestinationToPlayer ();
    41.             if (MoveToDestinationB ()) {
    42.                 TetherToPlayer (true);
    43.                 SetMode (InputMode.TETHERED);
    44.             }
    45.             break;
    46.         }
    47.     }
     
    Last edited: May 5, 2017
  20. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    This looks too simplistic for needing BT, it looks more like managing player input and movement. BT must stay high level, you are almost coding the same C# version using BT and that's not the goal of BT.

    For example the "fire" tree could become this
    1. tree("fire")
    2. fallback
    3. Fire
    The Fire Task will deal with input ,detail functionnality that does not need external tweakable parameters doesn't need to figure in BT.
    1. tree("from input to input mode")
    2. fallback
    3. manageAndSetMode

    Is not your while could block the flow ?
    1. tree("from input mode to movement")
    2. fallback
    3. while IsModeRetrieve
    4. tree("retrieve")
    5. while IsModeLaunch
    6. tree("launch")

    "retrieve" using exagerated methods names.
    1. tree("retrieve")
    2. sequence
    3. SetSpeedAndMove(0.5)
    4. TetherPlayerSetMode

    1. tree("launch")
    2. fallback
    3. sequence
    4. manageTether(false)
    5. SetSpeedAndMove(2.0)
    6. SetSpeed(0.5)
    7. SetMode(InputMode.FREE)
    8. tree("dance freely with player")

    1. tree("dance freely with player")
    2. sequence
    3. SetSpeed(0.3)
    4. //match vertical position
    5. SetVerticalDestinationToPlayer
    6. //mirror horizontal position
    7. managePlayerScreenPositionAndMove

    BT would be more usefull to manage for example a complex boss with complex and varied behaviour instead.
     
    Last edited: May 5, 2017
  21. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne

    I don't get those errors when double clicking on a method. What do you mean when the script is being parsed? When you make modification to the script and refreshing the Editor? What version of Unity are you using? Do you have a way to repro this consistently?

    I can't repro that. I deleted the duplicated task both from C# and the BT script and both case the BT script becomes valid (Status turns from 'Error' to 'Ready'). Though, I noticed that the line number was not highlighted to indicate the error, also the error message did not indicated the classes containing the duplicated tasks. These will be fixed in the next release.

    If you edit the BT script using an external text editor instead of the Pro Editor, beware that the file will not be loaded if you've previously made modifications using the Pro Editor. Hit the revert button to discard the changes and reload the BT script from file. I noticed another, bug about that, the BT script was not recompiled when undoing changes (ctrl+z). That will also be fixed in the next release.

    You can do it in one move: drag the "LaunchForce" task and drop onto the fallback node (when the fallback node is circled). The task will be added as last child.

    Both these actions are performed using the right click, try to stand still with the mouse if you want to pop up the context menu. Maybe I could put a timer here to filter out rushing hands.
     
    laurentlavigne likes this.
  22. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Thank you. You could leave a review on the Asset Store if you like it.

    In terms of performance and memory usage, the overhead of the framework is insignificant. Most of the heavy work is done by task implementations (like raycasting, physics, object instantiations, ...), which are more likely to be the bottlenecks if there are performance issues. If you are concerned about CG allocation (can be a performance issue on Mobile devices), the Panda core produces 0 GC allocation after initialization. However, it is always possible to implement tasks that allocates memory, so you would need to optimized the tasks yourself.

    I feel like I've miss something there... =/
     
    Last edited: May 6, 2017
    zenGarden likes this.
  23. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Your BT script is fine.

    I doubt that's the problem, but check "Tick on:" on the PandaBehaviour component in the Inspector. If you've set it to "Fixed Update" and configured fixedDeltaTime with a large value, that can tick the BT at very low frequency.

    Otherwise, you can put a Debug.log in the task implementation and check when it is ticked:
    Code (CSharp):
    1. Debug.Log( string.Format("{0:000.000} {1}", Time.realtimeSinceStartup, message));

    Code (CSharp):
    1. tree ("ChasePlayer")
    2.     repeat
    3.         sequence
    4.             IsPlayerNear
    5.             SetDestination_Player
    6.             MoveTo_Destination
    Or by "slow", you mean that the AI is not continuously chasing the player, but goes where the player has been a short time ago?
    If that the case, try something like this:
    Code (CSharp):
    1. tree ("ChasePlayer")
    2.     while
    3.         sequence
    4.             IsPlayerNear
    5.             not IsPlayerWithinReach // stop chasing if within attack range
    6.         race
    7.             repeat SetDestination_Player
    8.             MoveTo_Destination
    If that does what you want to do, you might want to consider storing a Transform instead of a Vector3 when executing SetDestination_Player and modify MoveTo_Destination accordingly.
     
  24. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    I'll do.

    How can we manage collisions enter and exit events if we use BT ? Because they are events and not task.
     
  25. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @zenGarden
    You can create a task that completes immidiately by succeeding when colliding and failling otherwise. This type of task can be implemented using a boolean:
    Code (CSharp):
    1. using UnityEngine;
    2. using Panda;
    3.  
    4. public class BTCollision : MonoBehaviour
    5. {
    6.     [Task]
    7.     bool IsColliding;
    8.  
    9.     [Task]
    10.     bool IsTriggered;
    11.  
    12.     void OnCollisionEnter(Collision other)
    13.     {
    14.         IsColliding = true;
    15.     }
    16.  
    17.     void OnCollisionExit(Collision other)
    18.     {
    19.         IsColliding = false;
    20.     }
    21.  
    22.     void OnTriggerEnter(Collider other)
    23.     {
    24.         IsTriggered = true;
    25.     }
    26.  
    27.     void OnTriggerExit(Collider other)
    28.     {
    29.         IsTriggered = false;
    30.     }
    31.  
    32. }
    33.  
     
    Last edited: May 6, 2017
    Steve_WLF and zenGarden like this.
  26. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    I'm always surprised by how you just solve things. Thanks. :cool:
     
    ericbegue likes this.
  27. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    using 5.6F3, parsing = whenever the script changes and the inspector processes the change.
    better using the left drag for moving, consistend with finder, unity move component etc...
     
  28. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    I've tested also with Unity 5.6.0f3 and still no errors when double clicking on a method when the script has been changed and refreshed. I tested with the Play Tag example. I am suspecting that this occurs on Mac OS ( you are using this OS, right?). If that the case, unfortunately I don't have a Mac OS to debug on. Could test with the Play Tag example as well so we can confirm that it's not specific to your project but to the OS?

    Actually, the left click is used for drag&drop nodes (as expected by standard). But the right drag is different: it's used to select a place to insert a new node. The problem is the right click (when not moving the mouse) is used to popup the contextual menu.
     
  29. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    got it, I never use that, better have shift+LMB for that feature or you end up with too many function fighting each others in the event loop plus shift+ is more standard way to say insert. Anyway my point is the editor in the pro version is not user friendly and I end up using the text editor. Don't know if you want to fix that because that'd take a lot of work to get right.

    Shifting gear, I'm running into an odd one: the shooting tree in this script, the 3 randomized Wait functions don't wait at all, the debug t- is t-N seconds all the time, no numbers scrolling up. What's odd is that the wait in the die sequence works fine. Also the repeat i= doesn't change.
    Repeat root is on as per default.
    I wonder if the parallel node keeps firing up tree("Shooting") every frame... according to documentation each execution is independant and waits for completion.
    panda wait acting weird.PNG
     
    Last edited: May 16, 2017
  30. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    I also think it's best to use an external editor to create BT script. Though the Pro editor can be useful to fine tune the tree, like moving a node around or changing a node parameter. Anyway, any feedback to improve is welcome. I will put a timer to distinct the insertion from poping up the context menu.

    About your tree, the parallel node fails because the die tree fails, then the root tree fails and repeat again on each frame, which makes the wait task being reset continuously and always display the same value.

    Keep in mind that the parallel node fails when any of its children fails. Maybe you want to mute the 'die' tree.
     
  31. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Yes that was it but now dying waits on the timer of Firing...
    How does "root repeat" execute the root each frame? And parallel? It seems that when one node is busy, the other nodes have to wait before the next frame compute, which is odd for something called parallel.

    panda why parallel be acting like dis.PNG
     
    Last edited: May 17, 2017
  32. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    If the tree completes each frame, it is repeated each frame (if 'repeat root' is checked).

    Unless you are using multi-threading, true parallelism does not exist. The parallel node ticks all its children within the same frame. But within that frame they are ticked sequentially. That's how the parallellism is achieved (time slicing). It's like the Update function, all the GameObjects are updated during the same frame, but they are effectively updated one after the other. Yet, from the player point of view, a lot of thing's going on at the same time (in parallel),

    The parallel node fails as soon as one child fails. So, if one child fails, it does not tick the following children. Maybe it would make more sens to tick all the children whenever they fail or not in order to stress the parallelism, but that would do unnecessary processing: the parallel has failed anyway, so why ticking the following children?
     
    Last edited: May 17, 2017
  33. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    So when it fails it force exits from all its children? What about ongoing execution where a child has a wait in it?
    In this example, why is the tree ("Die") waiting until tree ("Fire") is done executing which happens after the randomly selected Wait is complete?
    I end up needing to add a "repeat" in from of "mute node", when in my understanding that should be required.
     
  34. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Yes, when a child fails, the parallel node fails, so all the children are no more executed.

    The child is no more executed, it does not matter which node the child contains (wait is not a special node).
    The tree "Die" does not wait anything; it has not been ticked by its parent node. The parallel node has failed because "Fire" has failed. When a node fails, all its children down the hierarchy are no more ticked.

    Maybe you are expecting that all the children of the parallel node should be ticked on the same frame, even if one of them fails. Instead, when a child fail, the parallel node stops right there and does not tick the following children. Consider this as a lazy evaluation, like in C# when you do:
    Code (CSharp):
    1. if( false && someExpression )
    2. {
    3.     // do something
    4. }
    someExpresison is never evaluated.
    It is analogous to:
    Code (CSharp):
    1. parallel
    2.     Fail
    3.     SomeNode
    SomeNode is never ticked.

    It you put a "repeat mute" in front of the "Die", that's a proper way to keep the parallel node from failing when "Die" fails and the same time keep the "Die" tree being repeated.

    According to the documentation, what is your understanding of how the parallel node works. Maybe I need to clarify something?
     
    Last edited: May 17, 2017
  35. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Ok now I get that part, "race" would be a better choice to avoid muting everything?
    How do you explain that in this code Die only gets called after Shooting has done waiting then shoot its bullets?
    Code (CSharp):
    1. tree("Root")
    2.     parallel
    3.         mute tree("Shooting")
    4.         mute tree("Die")
    5.  
    6. tree("Shooting")
    7.     sequence
    8.         random
    9.             Wait(4.0)
    10.             Wait(6.0)
    11.             Wait(8.0)
    12.         repeat(4)
    13.             sequence
    14.                 SetTargetToPlayer
    15.                 AimCanonAtTarget
    16.                 Fire
    17.                 Wait(0.5)
    18.  
    19. tree("Die")
    20.     sequence
    21.         not IsParentAlive
    22.         Wait(0.5)
    23.         Explode
    Why does "die" need to be repeated in the code above? (I removed it to expose what I don't understand = how repeat root and parallel work in conjunction with wait, or any node that doesn't succeed until a condition is met)

    The fail part is clear, maybe an example would help to understand how to shape the code when we want this or that behavior, and linking to race to clear things up.
     
  36. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I think parallel in combination with repeat mute is appropriate for your behavior. Why do you want to avoid muting? You would also need muting with the race node.

    Both "Die" and "Shooting" are executed at the same time becuause the parallel node. But "Die" completes earlier in failure and the mute succeeds. Then the parallel node keep ticking only "Shooting" until it completes. When it does, parallel completes and the root completes. Then on the next frame, the root restarted (repeat root checked) and the whole story repeats.

    Small examples to illustrate how the nodes work would be something to add indeed.
     
  37. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Taking the shortest path, I don't add stuff until I understand why I need to.

    Why is Parallel stuck on "Shooting" until it completes? According to the doc it's supposed to tick each child each frame.
     
  38. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    The parallel does tick each child each frame. But if a child has completed, it is no more ticked.

    For instance:
    Code (CSharp):
    1. tree("EatPasta")
    2.     sequence
    3.         parallel
    4.             CookPasta
    5.             MakeSauce
    6.         DumpPastaOnPlate
    7.         DumpSauceOnPlate
    8.         Eat
    If you are done with making the sauce but the pasta is still cooking, you do nothing more with the sauce and just wait until the pasta is done.

    But thinking about it, I don't think parallel is the best choice, instead a fallback would be more appropriate:
    Code (CSharp):
    1. tree("Root")
    2.     fallback
    3.         tree("Shooting")
    4.         tree("Die")
    5.  
    6. tree("Shooting")
    7.     while IsParentAlive
    8.         sequence
    9.             ...
    10.  
    11. tree("Die")
    12.     sequence
    13.         not IsParentAlive
    14.         Wait(0.5)
    15.         Explode

    ... or do you want to keep shooting while dying?
     
    Last edited: May 18, 2017
  39. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I think I'm trying to understand who has what responsibility and why in the case of parallel a node running holds back a node failing from being ticked again.
    In the pasta example tree ("Eat Pasta") is called by a repeating node, yes?
    So Panda evaluates the tree:
    Code (CSharp):
    1. tree("EatPasta")
    2.     sequence
    3.         parallel
    4.             CookPasta
    5.             MakeSauce
    Every tick until both CookPasta and MakeSauce succeed.
    If CookPasta takes 5 minutes and MakeSauce fails because say I'm still waiting for someone to bring the ingredients from the shop, it doesn't make sense to me that MakeSauce will have to wait 5 minutes before being re-evaluated, in other word if the ingredients arrive 1 minute in I'll have to wait 4 minutes before making the Sauce.
    Let me illustrate:
     
  40. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I'm not sure what you mean.

    Not necessarily, but we can pretend that we are cooking and eating non-stop. The logic stays the same, it only repeats.

    Except if:
    - A node has succeeded (completed node under parallel won't be ticked).
    - A node has failed (in that case, the whole tree fails).

    Then, you are describing another behaviour tree:
    Code (CSharp):
    1. tree("EatPasta")
    2.     sequence
    3.         parallel
    4.             tree("CookPasta")
    5.             tree("MakeSauce")
    6.         DumpPastaOnPlate
    7.         DumpSauceOnPlate
    8.         Eat
    9.        
    10. tree("CookPasta")
    11.     sequence
    12.         WaitForPasta
    13.         PreparePasta
    14.    
    15. tree("MakeSauce")
    16.     sequence
    17.         WaitForSauceIngredients
    18.         PrepareSauce
    Note that, not having the ingredients will not result in failure, we just wait. And if you don't have the ingredients for the sauce yet, you will keep going with cooking the pasta 5 min. After that 5 min, "CookPasta" will succeeds and won't be ticked anymore. Meanwhile the "MakeSauce" has been ticked in parallel on WaitForSauceIngredients. When the sauce ingredients arrive, we don't wait anymore and go on with PrepareSauce and the parallel node eventually succeeds (since all children have succeeded). Then we go on with the rest of the sequence: DumpPastaOnPlate, DumpSauceOnPlate and Eat .

    Now, I'm hungry...
     
    Last edited: May 19, 2017
  41. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Gotcha! Now what was the logic in implementing a parallel node that blocks ticking on the other nodes while a node is running? You mentioned lazy eval, is that related?
     
  42. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    The lazy eval is a general principle. You can avoid unnecessary computation when the result is known for sure when evaluating the first terms. For instance:

    0*3*7

    You know that 0 times any number is 0. So, there is no need waste processing time computing 3*7. The same principle applies to boolean logic in programming (false && whatever = false, true || whatever = true).

    In Panda BT, you know that a parallel node fails if one child fails. So it is not necessary to evaluate the nodes after the failing child. You can exploit this to optimize your tree by placing the more cpu expensive nodes at the end of the parallel node so that they won't be evaluated when a previous node fails.
     
    Last edited: May 20, 2017
  43. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    That's cool to know the little optimizations. I still don't know why running nodes do the same though...

    Anyway let's talk about multi editing. I wanted to add that so I added [CanEditMultipleObjects] to BehaviourTreeEditor.cs and PandaBehaviourEditor.cs, it's allowing multi edit but this doesn't get serialized in every member of the selection so I started digging in the doc and if you wanted to add that, you'd need to use SerializedProperty instead of doing everything by hand. The added benefit is that it'll work with prefab overrides and does the Undo for you.
    https://docs.unity3d.com/ScriptReference/Editor.html
    So instead of
    Code (CSharp):
    1.             EditorGUI.BeginChangeCheck();
    2.  
    3.             EditorGUILayout.BeginHorizontal();
    4.             bt.tickOn = (BehaviourTree.UpdateOrder)EditorGUILayout.EnumPopup("Tick On:", bt.tickOn);
    5.             bt.autoReset = GUILayout.Toggle(bt.autoReset, "Repeat Root");
    6.             EditorGUILayout.EndHorizontal();
    7.  
    8.             DisplayStatus();
    9.  
    10.             var newSize = EditorGUILayout.IntField("Count", size);
    11.  
    12.             if (EditorGUI.EndChangeCheck())
    13.             {
    something like this:
    Code (CSharp):
    1.         serializedObject.Update ();
    2.  
    3.             EditorGUILayout.BeginHorizontal();
    4.             bt.tickOn = (BehaviourTree.UpdateOrder)EditorGUILayout.EnumPopup("Tick On:", bt.tickOn);
    5.             bt.autoReset = EditorGUILayout.Toggle(bt.autoReset, "Repeat Root");
    6.             EditorGUILayout.EndHorizontal();
    7.  
    8.             DisplayStatus();
    9.  
    10.             var newSize = EditorGUILayout.IntField("Count", size);
    11.  
    12.             if (serializedObject.ApplyModifiedProperties())
    13.             {
    14.                 if( size != newSize)
    15.                 {
    16.                     size = newSize;
    17.  
    18.                     if (size < 0) size = 0;
    19.  
    20.                     // Resize the TextAsset array
    21.                     TextAsset[] old = new TextAsset[bt.scripts.Length];
    22.                     bt.scripts.CopyTo(old, 0);
    23.                     bt.scripts = new TextAsset[size];
    24.  
    25.                     for (int i = 0; i < size; ++i)
    26.                         bt.scripts[i] = i < old.Length ? old[i] : null;
    27.  
    28.                     bt.Apply();
    29.                     bt.Compile();
    30.                     Clear_guiBTScripts();
    31.  
    32.                     InitSourceInfos();
    33.  
    34.                     Refresh();
    35.                 }
    36.             }
    37.  
    38.             EditorGUILayout.EndVertical();
    39.  
    40.             if (serializedObject.isEditingMultipleObjects) {
    41.                 EditorGUILayout.HelpBox ("Cannot edit BT scripts in multi edit mode", MessageType.Info);
    42.                 return;
    43.             }
    (I omitted a lot, since that's pro stuff)
     
  44. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I'm not sure whether we are talking about the same thing. For the parallel node, the lazy evaluation occurs only in the case of a child node has failed.
    Here is a complete list of cases when the parallel node do not tick a child:
    1. The parallel node itself is not running.
    2. When a child is completed, it is not ticked while the other children are still running (The parallel node succeeds only when *all* the children have succeeded).
    3. A child fail: all the following children are not ticked (lazy evaluation).
    Thank you for figuring this out. I'm not very familiar with multi-editing, so that come as a great help! I will also dig into this and implement it. It seems you've done some progression, please PM me your code, so I would integrate them into sources.
     
  45. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    This should be in the documentation. And 2, "completed" or "failed".

    Me neither, it's possible this serializedObject is new in 5.6, I'll PM you once I'm done.

    Bug: if you copy paste a panda and it's missing one reflected method and then copy paste the component with the missing method, the error still there.
     
  46. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    I am fairly new to AI development and Panda BT looks quite friendly. Below is my high level decision-making concept and I am wondering if Panda BT is suitable.

    I'm imagining an AI tick to cover something along the lines of:
    1. Evaluation -- The AI entity assess nearby entities (friends, enemies, prey, predators), personal stats (health, hunger, stamina, hydration, etc), personal goals (get X number of timber before winter), and so on. These weights are the building blocks for calculating the urgency of a Panda BT Tree. Evaluation use AnimationCurves to accrue the final weights used in Prioritization.
    2. Prioritization -- Every possible Tree the AI could choose is weighted. Tree weights are calculated based on relevant Evaluation weights. Then, these potential Trees are sorted from highest to lowest priority.
    3. Enact -- The AI entity tries to do the Tree with the highest priority. If that fails, try the next Tree of highest priority.

    One other question:
    How could I go about making these Trees stateful? If a Tree is interrupted for an arbitrary duration, can the AI return to it's current 'spot' within the Tree? For example: a surprise attack interrupts a lumberjack at work (profession Tree). The woodcutter fights off the aggressors (combat Tree), and then returns back to work (profession Tree). When the lumberjack returns to work, he enters the profession Tree exactly where he left off.
     
  47. Xylph

    Xylph

    Joined:
    Dec 11, 2013
    Posts:
    30
    I'm not sure if this was already asked. Is there a support for coroutine?

    I'm trying to use your plugin to drive game states (I hate Mecanim FSM). Later, I'm planning to use it to control the character and AI.

    It's a bit awkward to keep states inside a function that keeps getting reset (stateless). For example, if I have to fade an object out of the game, it'll require me to declare a float fadeTimer in the instance scope.

    Here's a code sample which only completes a task if the animation completes:

    Code (CSharp):
    1. [Task]
    2.     void PlayAnimation(string stateName, bool completeAnimation, bool resetAnimationOnRepeat)
    3.     {
    4.         if (Task.current.isStarting)
    5.         {
    6.             if (resetAnimationOnRepeat) animator.Play(stateName, 0, 0);
    7.             else animator.Play(stateName);
    8.         }
    9.  
    10.         if (completeAnimation)
    11.         {
    12.             AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
    13.             if (!stateInfo.IsName(stateName)) return;
    14.             if (stateInfo.normalizedTime >= 1.0f) Task.current.Succeed();
    15.         }
    16.         else Task.current.Succeed();
    17.     }
    If this was a coroutine which only gets invoked upon the entry of the leaf node, I can easily do this:

    Code (CSharp):
    1. [Task]
    2.     IEnumerator PlayAnimationCoroutine(string stateName, bool completeAnimation, bool resetAnimationOnRepeat)
    3.     {
    4.         if (resetAnimationOnRepeat) animator.Play(stateName, 0, 0);
    5.         else animator.Play(stateName);
    6.  
    7.         if (completeAnimation)
    8.         {
    9.             // Wait for animation to finish
    10.             yield return null;
    11.             yield return new WaitWhile(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1.0f);
    12.         }
    13.  
    14.         Task.current.Succeed();
    15.     }
    I understand that overusing Coroutine is bad (especially if you're using it to drive game logic). However, for visual cues like animation, color fading, etc that requires multiple frames are easier and shorter to code in a coroutine.

    Does the plugin somewhat support coroutines or is there a smart workaround? I'm frustrated with declaring a highly-scoped field/variable just for one small task.

    Note: I just came across your plugin so I might not now some tricks.

    Thanks.
     
  48. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Keep float startFadeTime inside Task.current.item, it's the Panda way ;)
    http://www.pandabehaviour.com/?page_id=23#Properties

    coroutine are implicit in Panda. If a task doesn't have a succeed or fail state, it keeps repeating so if you have 2 coroutines and want to yiel run each one sequentially, use the structure keyword "sequence" like this:
    Code (CSharp):
    1. sequence
    2.     Coroutine1
    3.     Coroutine2
    This will run Coroutine2 once Coroutine1 succeeds.
     
  49. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    What you want to do is called utility AI and it's been discussed a few posts back, top right of this page there is a search field (it's new I think) so run a search on utility and read the answer from eric october 2016.

    As for stateful tree, I don't think there is a way to resume an action where you left it off. That's more GOAP-y, also doable but then panda becomes more of an action sequencer and you call the trees by hand from c# code.
     
  50. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @Xylph
    Panda BT is an alternative to coroutine. So, anything a coroutine can do can be done with a BT script.

    Panda BT does not support couroutines as tasks implementation because couroutines are internally handled by Unity and run exclusively on Update (BT script let the user choose when the tree is ticked).

    Thought you still have the option to start coroutines from BT task. This way the animation won't be interrupted when the task completes or is no more ticked.