Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

[AI SCRIPTING] Panda BT: Behaviour Tree scripting

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

  1. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Hello! I made a finite state machine, and now I'm trying to convert that into a Panda BT. So far, I don't know how to switch between trees without making a bunch of bools. I'm concerned that I will need a ridiculous amount of bools to control the game logic if I carry on like this. Here's an example:

    Code (CSharp):
    1.     #region//BT methods
    2.     [Task]
    3.     public void UnitIdle()
    4.     {
    5.         animator.SetFloat("WalkSpeed", 2f);
    6.         Task.current.Succeed();
    7.         if(job)
    8.             Task.current.Fail();
    9.     }
    10.     [Task]
    11.     public void ChangeClothes()
    12.     {
    13.         if (job && woodcutter && !hasRightGear)
    14.         {
    15.             ChangeGear(ItemDatabase.instance.FetchItemByID(2),
    16.           ItemDatabase.instance.FetchItemByID(0),
    17.           ItemDatabase.instance.FetchItemByID(6),
    18.           InteractablesManager.instance.chestOfDrawers,
    19.           InteractablesManager.instance.armorStands,
    20.           InteractablesManager.instance.toolRacks);
    21.             Task.current.Succeed();
    22.         }
    23.         else
    24.             Task.current.Fail();
    25.     }
    26.     [Task]
    27.     public void ChopDownTree()
    28.     {
    29.         //animator.SetFloat("WalkSpeed", 1f);
    30.         Task.current.Succeed();
    31.     }
    32.     [Task]
    33.     public void StoreWood()
    34.     {
    35.         //animator.SetFloat("WalkSpeed", 1f);
    36.         Task.current.Succeed();
    37.     }
    38.  
    39.     #endregion
    In the BT script, the unit starts in Idle. I wasn't sure how to go from Idle to ChangeClothes without adding a new bool 'job'. When job is true, the unit goes to the change clothes tree. However, the clothes the unit needs depends on the job. So I needed to create another bool 'woodcutter'. A method runs which goes through two FSMs: one to go through each clothing slot step by step, and the other to move to the a storage unit and collect the needed equipment if it is missing. Not sure how to make those FSMs into BTs.

    When all the clothing is obtained, the bool hasGear is set to true and the task fails allowing the unit to move onto the ChopDownTree tree.

    How could I improve this and make it less "booly"?

    The project can be found here if that helps: https://www.mediafire.com/?w76w8j0qjjc899u (note: you need to build a weapon rack, chest of drawers, and an armor stand for the code to work)
     
  2. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    I was waiting for Eric to answer but I think he's taking some time off.
    Taking a look at your project I think you're trying to recreate states that are required in an FSM to execute tasks sequentially. BT doesn't need those because sequences are implicit.
    The following code sample might shed some light on what this means:
    Code (CSharp):
    1.  
    2.  tree("Root")
    3.     //Root behaviour for all unit: Be alive or die.
    4.     fallback
    5.         tree("woodcutter")
    6.         tree("guard")
    7.         tree ("idle")
    8.  
    9.  
    10. tree ("woddcutter")
    11.     while
    12.         sequence
    13.             IsWoodcutter
    14.             NotHungry
    15.             BladderOK
    16.         sequence
    17.             SetDestinationToNearbyClothingRack
    18.             GotoDestination
    19.             ChangeClothing "bootswoodcutter"
    20.             ChangeClothing "pantswoodcutter"
    21.             ChangeClothing "shirtwoodcutter"
    22.             SetDestinationToNearbyFreeAxe
    23.             GotoDestination
    24.             GetAxe
    25.             repeat
    26.                 sequence
    27.                     SetDestinationToNearbyTree
    28.                     GotoDestination
    29.                     ChopTree
    30.                     GatherLog
    31.                     SetDestinationToNearbyDepot
    32.                     GotoDestination
    33.                     DropLog
    34.  
    35. tree ("guard")
    36.     while
    37.         sequence
    38.             IsGuard
    39.             NotHungry
    40.             BladderOK
    41.         sequence
    42.             SetDestinationToNearbyClothingRack
    43.             GotoDestination
    44.             ChangeClothing "bootsguard"
    45.             ChangeClothing "pantsguard"
    46.             ChangeClothing "shirtguard"
    47.             SetDestinationToNearbyFreeWeapon
    48.             GotoDestination
    49.             GetWeapon
    50.             while
    51.                 NoThreat
    52.                 repeat
    53.                     sequence
    54.                         SetDestinationToNextGuardWaypoint
    55.                         GotoDestination
    56.             while
    57.                 ThreatNearby
    58.                 sequence
    59.                     SetTargetToNearestThreat
    60.                     repeat
    61.                         fallback
    62.                             while
    63.                                 not TargetInRange
    64.                                 MoveToTarget
    65.                             Attack
    66.  
     
    devstudents likes this.
  3. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thank you @laurentlavigne! Yes I was using the BT like an FSM. I appreciate the BT text file. I need to start my AI from scratch so this will come in handy. I'll post the finished product here before I make a video to get some feedback from you guys.
     
    laurentlavigne likes this.
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    One last thing you can do is use enums instead of strings. It's cleaner and when you get mismatch your BT code will throw error at compile.
    SetDestinationToNearby (DESTINATION.CLOTHINGRACK)
    In the unit class:

    Code (CSharp):
    1.     public NavMeshAgent agent;
    2.     [Task]
    3.     public void GotoDestination()
    4.     {
    5.         agent.SetDestination(currentDestination.position);
    6.     }
    7.  
    8.     public Transform currentDestination;
    9.     [Task]
    10.     public void SetDestinationToNearby(DESTINATION destinationType)
    11.     {
    12.         var dts = Destination.destinations.FindAll (d => d.type == destinationType);
    13.         currentDestination = dts.Where(d=>d.transform != transform).OrderBy (d => (transform.position - d.transform.position).sqrMagnitude).First ().transform;
    14.     }
    //put this on every destination including the player. If you don't care about separating data and systems you can add a locking mechanism and even add a GetTransform() method to distribute the destination positions around the object.

    Code (CSharp):
    1. public enum DESTINATION {CLOTHINGRACK, FREEAXE, DEPOT, TREE. PLAYER, }
    2. public class Destination : MonoBehaviour {
    3.     public static List< Destination> destinations = new List<Destination>();
    4.     public DESTINATION type;
    5.  
    6.     void OnEnable()
    7.     {
    8.         destinations.Add (this);
    9.     }
    10.     void OnDisable()
    11.     {
    12.         destinations.Remove (this);
    13.     }
    14. }
    15.  
     
    Last edited: Mar 5, 2017
    devstudents likes this.
  5. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thanks for the tip @laurentlavigne! I normally have a lot of time to program, but my two part-time jobs have turned full-time this week! I'll be able to get stuck into it next week. Look forward implementing your suggestions and sharing the result here :)
     
  6. JymWythawhy

    JymWythawhy

    Joined:
    Jan 18, 2015
    Posts:
    25
    I'm having some trouble implementing a behavior, and its not working- I am probably doing something dumb!

    I have a spaceship that I want to approach a point in space. Once it gets close enough to stop, it should turn around and fire its thrusters so it comes to a stop near that point.

    Instead it gets close, turns around, slows down slightly, and then drops into the Idle behavior, even though it never should have met the "SpaceshipStoppedAtTargetPosition" condition in tree("Approach").

    Any ideas? (Also taking pointers in how I can better format or use this great plugin!)

    This is what I have for the BT:
    Code (CSharp):
    1. tree("Root")
    2.     sequence
    3.         tree("Approach")
    4.         tree("Idle")
    5.  
    6. tree("Approach")
    7.     fallback
    8.         while not SpaceshipStoppedAtTargetPosition // This should fire a Debug log if it is ever true, but it never is, but still dropps out of this while condition.
    9.             repeat
    10.                 sequence
    11.                     ApproachPosition
    12.                     tree("Brake")
    13.         Succeed
    14.  
    15. tree("Idle")
    16.     sequence
    17.         repeat
    18.             Succeed
    19.  
    20. tree("Reverse")
    21.     sequence
    22.         Reverse
    23.  
    24. tree("Brake")
    25.     sequence
    26.         while not SpaceshipStopped
    27.             repeat
    28.                 sequence
    29.                     tree("Reverse")
    30.                     Brake
    31.         Succeed
    And in my C# file, I have:

    Code (CSharp):
    1.     [Task]
    2.     protected bool AtTargetPosition()
    3.     {
    4.         return ((Vector2.Distance(transform.position, targetPosition) < 0.5));
    5.     }
    6.  
    7.     [Task]
    8.     protected bool SpaceshipStopped()
    9.     {
    10.        return (spaceship.Velocity.magnitude <= 0.01f || spaceship.stopped);
    11.     }
    12.  
    13.     [Task]
    14.     protected bool SpaceshipStoppedAtTargetPosition()
    15.     {
    16.         bool truth = SpaceshipStopped() && AtTargetPosition();
    17.         if (truth)
    18.         {
    19.             Debug.Log("---------------------------------------------------- " + truth); // This never fires!
    20.         }
    21.         else
    22.         {
    23.             Debug.Log("Frame: " + Time.frameCount + " truth: " + truth);
    24.         }
    25.         return SpaceshipStopped() && AtTargetPosition();
    26.     }
    27.  
    28.     /// <summary>
    29.     /// Approach a position in space. When it is past the braking distance, succeed
    30.     /// </summary>
    31.     [Task]
    32.     protected void ApproachPosition()
    33.     {
    34.         float targetDistance = Vector2.Distance(transform.position, targetPosition);
    35.         if (IsTheShipLessThanMinBrakeDistance())
    36.         {
    37.             Task.current.Succeed();
    38.         }
    39.         float steeringAngle = AngleToSeek(targetPosition);
    40.         Rotate(GetAngleTowardsTargetAngle(steeringAngle));
    41.  
    42.         if(Mathf.Abs(currentFacing - steeringAngle) <= AngleToThrust)
    43.         {
    44.             spaceship.Thrust();
    45.         }
    46.     }
    47.  
    48.     /// <summary>
    49.     /// Rotate so that the ship is facing the opposite of the velocity vector
    50.     /// </summary>
    51.     [Task]
    52.     protected void Reverse()
    53.     {
    54.         if (spaceship.Velocity.magnitude < 0.01 || spaceship.stopped)
    55.         {
    56.             Task.current.Succeed();
    57.             return;
    58.         }
    59.  
    60.         if (spaceship.Velocity.magnitude != 0)
    61.             Rotate(GetAngleTowardsTargetAngle(reverseHeading));
    62.        
    63.         if (Mathf.Abs(currentFacing - reverseHeading) < 0.01)
    64.         {
    65.             Task.current.Succeed();
    66.         }
    67.  
    68.     }
    69.  
    70.     /// <summary>
    71.     /// If the ship is moving and facing opposite of the heading, brakes the ship.
    72.     /// Succeeds when the ship stops,
    73.     /// fails if the ship is not facing backwards.
    74.     /// </summary>
    75.     [Task]
    76.     protected void Brake()
    77.     {
    78.         if (spaceship.Velocity.magnitude < 0.01 || spaceship.stopped)
    79.         {
    80.             Task.current.Succeed();
    81.         }
    82.  
    83.         if (Mathf.Abs(currentFacing - reverseHeading) <= 0.01f)
    84.         {
    85.             if (spaceship.Velocity.magnitude > 0.01)
    86.             {
    87.                 spaceship.Thrust();
    88.             }
    89.         }
    90.         else
    91.         {
    92.             Task.current.Fail();
    93.         }
    94.     }
     
  7. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    You're pretty close but you're getting tangled up in too much nesting plus you're mixing states like speed and angle. Instead, flatten everything and make it linear, one state one function, that's BT style and that's how you described the maneuver.

    Code (CSharp):
    1. tree("approach")
    2.     sequence
    3.         tree("move to flip distance")
    4.         tree("flip and burn")
    5.         tree("idle")
    6.  
    7. tree("move to flip distance")
    8.     fallback
    9.         while not IsWithinFlipDistance
    10.             Burn
    11.         Succeed
    12.  
    13. tree ("flip and burn")
    14.     sequence
    15.         Flip
    16.         Stabilize
    17.         while not IsStopped
    18.             Burn
    19.     Succeed
    Example of Flip task, notice how I'm not bothering with speed because I want to decouple the maneuver from ancillary conditions:
    Code (CSharp):
    1.     [Task]
    2.     bool Flip()
    3.     {
    4.         if (Mathf.Abs (currentHeading - reverseHeading) > .1f) {
    5.             RotateTowards (reverseHeading);
    6.             return false;
    7.         } else
    8.             return true;
    9.     }
    10.  
     
    ericbegue likes this.
  8. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @JymWythawhy

    Code (CSharp):
    1. tree("Approach")
    2.     fallback
    3.         while not SpaceshipStoppedAtTargetPosition // This should fire a Debug log if it is ever true, but it never is, but still dropps out of this while condition.
    4.             repeat
    5.                 sequence
    6.                     ApproachPosition
    7.                     tree("Brake")
    8.         Succeed
    If SpaceshipStoppedAtTargetPosition always succeed, then the only way to bail out the "Approach" tree is by failing ApproachPosition or the tree("Brake"). We can eliminate ApproachPosition, since it never calls Fail(). Therefore, there is a problem with "Brake".

    Code (CSharp):
    1.     [Task]
    2.     protected void Brake()
    3.     {
    4.         if (spaceship.Velocity.magnitude < 0.01 || spaceship.stopped)
    5.         {
    6.             Task.current.Succeed();
    7.         }
    8.         if (Mathf.Abs(currentFacing - reverseHeading) <= 0.01f)
    9.         {
    10.             if (spaceship.Velocity.magnitude > 0.01)
    11.             {
    12.                 spaceship.Thrust();
    13.             }
    14.         }
    15.         else
    16.         {
    17.             Task.current.Fail();
    18.         }
    19.     }
    I'm not sure exactly what's going on with currentFacing and reverseHeading, but I suggest to use Vector3.Dot, it yields -1 when the vectors are in opposite direction.

    I am suspecting that this condition is not doing what you think it should. It is evaluated to false and fails the task.
     
  9. JymWythawhy

    JymWythawhy

    Joined:
    Jan 18, 2015
    Posts:
    25
    Thank you both for your help!

    That makes a lot of sense- I misunderstood what would happen in the case of brake failing- I thought it would start the repeat over, but it looks like it breaks out of the repeat?
     
  10. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    Except for fallback, race, not and mute, most of nodes fail when any of their child fail. When in doubt, you can refer to the documentation. There is a table at the end of each node description that tells in which situation the node succeed, fail or run.

    For instance for the repeat node: http://www.pandabehaviour.com/?page_id=23#Repeat

    So, the repeat node never succeeds, it keeps running and repeating its child. It will only bails out on failure of its child.
     
    Last edited: Mar 10, 2017
  11. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    I can finally start on my BT! Today and all of tomorrow are BT days until next Monday! yay.

    I have an issue already. This is my BT script:


    ***Edit: this issue has been fixed. I think I fixed it by tabbing everything to the left and then tabbing them back into the correct position. I cut pasted some code into the file and thought the layout was fine. It still looks the same, but must have been off by single space or something. Let me know if I've interpreted this fix incorrectly****

    Code (CSharp):
    1.  tree("Root")
    2.     fallback
    3.         tree("woodcutter")
    4.         tree ("idle")
    5. tree ("woodcutter")
    6.     while
    7.         isWoodcutter
    8.         sequence
    9.             SetDestinationToNearbyClothingRack "pants"
    10.             GoToDestination
    11.             TakeClothingOrEquipmentAndEquip
    12.             SetDestinationToNearbyEquipmentRack "axe"
    13.             GoToDestination
    14.             TakeClothingOrEquipmentAndEquip
    15.             repeat
    16.                 sequence
    17.                     SetDestinationToNearbyTree
    18.                     GoToDestination
    19.                     ChopTree
    20.                     GatherLog
    21.                     SetDestinationToNearbyStorage "Wood"
    22.                     GoToDestination
    23.                     DropLog
    24.  
    25. tree("idle")
    26.     while
    27.         isIdle
    28.         StandAround
    29.  
    And I'm getting an error which says that the woodcutter tree has too many children -only one is expected. How do I fix this? I used the documentation to try and work out how to use the while node, not sure if I interpreted it correctly.

    **update**

    Ok, I looked through all the documentation and searched online. Couldn't find anything in the documentation about this, and there was a post in this forum on page 2 but not much info. I tried changing it so the repeat node became a different tree like so:

    Code (CSharp):
    1. tree("Root")
    2.    fallback
    3.        tree ("changeClothes")
    4.        tree ("chopDownTree")
    5.        tree ("idle")
    6.  
    7. tree ("changeClothes")
    8.     while
    9.         isWoodcutter
    10.         sequence
    11.             SetDestinationToNearbyClothingRack "pants"
    12.             GoToDestination
    13.             TakeClothingOrEquipmentAndEquip
    14.             SetDestinationToNearbyEquipmentRack "axe"
    15.             GoToDestination
    16.             TakeClothingOrEquipmentAndEquip
    17.  
    18. tree ("chopDownTree")
    19.     repeat
    20.         sequence
    21.             SetDestinationToNearbyTree
    22.             GoToDestination
    23.             ChopTree
    24.             GatherLog
    25.             SetDestinationToNearbyStorage "Wood"
    26.             GoToDestination
    27.             DropLog
    28.  
    29. tree("idle")
    30.     while
    31.         isIdle
    32.         StandAround
    33.  
    However, now it's saying that the tree node at line 1 has too many children. In the inspector, it has made the tree ("chopDownTree") it's own node and the tree ("idle") a child of that tree.
     
    Last edited: Mar 16, 2017
    laurentlavigne likes this.
  12. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman
    That's really weird, your BT script seems to be well formatted. It might be a bug in the parser. Could you send me your MonoBehaviour file so I can have a closer look at this problem.

    Edit:
    I'm not sure whether you've fixed this problem by cleaning up the tabs or you still have the problem afterwards. If it's definitely fixed, then yes, the script is sensible to indentation, so you need to take care of the spacing. If you work on Windows, I suggest to use notepad++ to edit your BT scripts. With this editor you have indentation guide and you can display spaces and tabulations, so you know exactly how your tree is formatted. Also, see this post for a useful plugin for Visual Studio with this regard.
     
    Last edited: Mar 16, 2017
    devstudents likes this.
  13. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thanks @ericbegue ! It's working fine now, so it must have just been a few spaces off somewhere. I'll tell viewers to download that extension as it's going to make working with BT scripts much easier :)
     
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman
    Great that your BT is working now! You've got the hang of making BT scripts.
    I have some suggestions:
    It seems to me that the "changeClothes" should be a subtree of "chopDownTree", since it is some sort of requirred preparation for gathering wood. Also I would do some renaming to make things more specific. Here is how I would refactor the script:

    Code (CSharp):
    1. tree("Root")
    2.    fallback
    3.        tree ("gatherWood")
    4.        tree ("idle")
    5.  
    6. tree ("gatherWood")
    7.     while
    8.         isWoodcutter
    9.         sequence
    10.             fallback // equip as wood cutter if not already.
    11.                 isEquippedAsWoodcutter
    12.                 tree ("equiqAsWoodcutter")
    13.             tree("chopTreeAndGatherWood")
    14.  
    15. tree ("equiqAsWoodcutter")
    16.     while
    17.         isWoodcutter
    18.         sequence
    19.             SetDestinationToNearbyClothingRack "pants"
    20.             GoToDestination
    21.             TakeClothingOrEquipmentAndEquip
    22.             SetDestinationToNearbyEquipmentRack "axe"
    23.             GoToDestination
    24.             TakeClothingOrEquipmentAndEquip
    25.  
    26. tree("chopTreeAndGatherWood")
    27.     While
    28.         isEquippedAsWoodcutter // Stop in case the equipment get broken
    29.         repeat
    30.             sequence
    31.                 SetDestinationToNearbyTree
    32.                 GoToDestination
    33.                 ChopTree
    34.                 GatherLog
    35.                 SetDestinationToNearbyStorage "Wood"
    36.                 GoToDestination
    37.                 DropLog
    38.  
    39. tree("idle")
    40.     while
    41.         isIdle
    42.         StandAround
    43.  
    Keep up the good work! I can't wait to see your tutorial video =).
     
    Last edited: Mar 17, 2017
    devstudents likes this.
  15. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    @ericbegue Thanks for the post! I didn't realize you could have trees within trees like that -I thought you could only do that in the Root tree. Here's my final BT (for now at least. I would like to implement a whole bunch of different things like breakable tools etc. in the future).

    Code (CSharp):
    1. tree("Root")
    2.    fallback
    3.         tree("woodcutter")
    4.         tree("idle")
    5. tree ("woodcutter")
    6.     while isWoodcutter
    7.         sequence
    8.             StandUp
    9.             fallback
    10.                 tree("checkPants")
    11.                 tree("checkHandRight")
    12.                 tree("harvestWood")
    13.  
    14. tree("checkPants")
    15.     sequence
    16.         CheckGear "brown_pants" 1
    17.         while not hasRightGear
    18.             sequence
    19.                 SetDestinationToNearbyEquipmentStorage "pants"
    20.                 GoToDestination
    21.                 FaceInteractable
    22.                 TakeClothingOrEquipmentAndEquip "Legs"
    23.  
    24. tree("checkHandRight")
    25.     sequence
    26.         CheckGear "axe" 3
    27.         while not hasRightGear
    28.             sequence
    29.                 SetDestinationToNearbyEquipmentStorage "axe"
    30.                 GoToDestination
    31.                 FaceInteractable
    32.                 TakeClothingOrEquipmentAndEquip "HandRight"
    33.  
    34. tree("harvestWood")
    35.     repeat
    36.         sequence
    37.             ResetClosestHavestObjectVariable
    38.             SetDestinationToNearestHarvestObject
    39.             GoToDestination
    40.             FaceInteractable
    41.             HarvestObject
    42.             WaitForTreeToFall
    43.             ProcessHarvestObject
    44.             TriggerPickUpAnimation
    45.             CollectHarvestedObject
    46.             SetDestinationToNearbyEquipmentStorage "wood"
    47.             GoToDestination
    48.             DropLog
    49.  
    50. tree("idle")
    51.     while
    52.         isIdle
    53.         fallback
    54.             sequence
    55.                 FindStool
    56.                 GoToDestination
    57.                 FaceInteractable
    58.                 SitOnStool
    59.             StandAround
    Could this be improved? It works as I expect it to, and I believe I understand why it works the way it does -so I'm going to go ahead and make/upload the video tomorrow. I'm going to be doing a lot of these (my aim is to have a town of about 30 units with a myriad of professions and activities), so if anything is not optimal I can always improve the next one :)
     
  16. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman

    Yes, you can have trees within trees with no problem. A tree is analogous to a function in a program, so you can use it similarly to organized you script in hierarchy and in small chunks. This way, you can avoid to have complicated indentation.

    Instead of a fallback for checking the equipment and harvesting, I would use a sequence. I see why you are using a fallback: the "check..." trees fail when the equipment are ok (because the while nodes fail), which is a kind of reversed logic. Also, the BT script restarts from the top when the equipment check succeed. Which, is not a big issue since the script ends up at the same point afterwards... but it could be better. Also, what would happen if the required equiments are not available? The check trees fails and the fallback will continue with "harvestWood".

    Instead I would use fallbacks to check the equipment, like this.

    Code (CSharp):
    1.  
    2. tree ("woodcutter")
    3.     while isWoodcutter
    4.         sequence
    5.             StandUp
    6.             sequence
    7.                 tree("checkPants")
    8.                 tree("checkHandRight")
    9.                 tree("harvestWood")
    10.  
    11.          
    12. tree("checkPants")
    13.     fallback
    14.         hasEquipment("pants")
    15.         sequence
    16.             SetDestinationToNearbyEquipmentStorage "pants"
    17.             GoToDestination
    18.             FaceInteractable
    19.             TakeClothingOrEquipmentAndEquip "Legs"
    20.  
    21.  tree("checkAxe")
    22.     fallback
    23.         hasEquipment("axe")
    24.         sequence
    25.             SetDestinationToNearbyEquipmentStorage "axe"
    26.             GoToDestination
    27.             FaceInteractable
    28.             TakeClothingOrEquipmentAndEquip "HandRight"
    29.  
    30.  
    So, if the unit is properly equipped, the check succeeds, otherwise it will try to equip. If the gears are available, the check succeeds, otherwise it fails and the sequence bails out without any attempt to harvest wood. When both checks succeed, the unit proceed with harvesting wood as expected.

    Other than that, your script is well structured. So, now you want to have 30 units with different behaviors in the village! You've glimpsed some perspective on this tool, ain't you?
     
    devstudents likes this.
  17. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thanks again. I implemented your changes. Here's the video:


    The project file (minus Panda BT) is in the description. I didn't get around to implementing @laurentlavigne advice to use enums instead of strings, but I'll work on that in the next one.

    I hope my explanations are accurate! I'm aiming to release another one by next Wednesday, so let me know if anything I said is off and I'll correct it in the next video.

    Working with this asset confirmed my initial impressions: this is an excellent asset! I'm looking forward to using it to create a thriving town of 30-40 residents. Should be fun! :)
     
    laurentlavigne likes this.
  18. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    Your tuto is so good and I had a good laugh when the guy stands up upset because no pants! When you reach 30 citizen I want to see how you solve interactable locks.

    Detail: The first parameter of a while node is also a node, not a bool: it means that you can have while sequence task1 task2 task3 node2 which will execute node2 only if the entire sequence succeeds.

    Hint: When you're at that stage where the game itself has multiple states and transitions, you can use Panda even for that.

     
    devstudents likes this.
  19. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thanks a lot for the feedback @laurentlavigne ! Ahhhh, ok. That makes sense about the while node. I'll correct that in the next video. I'm glad that was the only mistake you could detect! I was concerned about misrepresenting the asset :)

    What do you mean by interactable locks?
     
    laurentlavigne likes this.
  20. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    3 woodcutters and only one tree left, how you handle those types of behaviors. Do you lock the tree once a woodcutter assigned it to himself, do you allow the 3 woodcutters to spam one tree, do you keep a timer in the tree so there is delay after the woodcutter that's been chopping it down is done etc.... Sharing policy if you will.
     
    devstudents likes this.
  21. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Yeah this is going to take some serious work. At first, I'm going to make it that only one woodcutter can work on a tree at a time. I think I'm going to have two lists for each interactable object: one for the interactables that are not targeted by a unit, and one for interactables that are targeted. So as soon as a unit finds the closest tree and starts moving towards it, that tree will be removed from the available tree list and moved into the occupied tree list. This would work as long as units don't find a tree at exactly the same time. I'm not too sure how I'm going to avoid that potential bug at the moment, so it's going to take many hours of experimentation -_-
     
    laurentlavigne likes this.
  22. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    Finding a tree at the same time isn't possible in single threaded code so I wouldn't worry about that.

    Two lists is cool, it's clean. I usually do chosenTree.woodcutter=this and trees.Find(t=>t.woodcutter==null) but maybe there is a more elegant way that also deals with delay between tasks globally, like your "waitfortreefall"
     
    devstudents likes this.
  23. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Hey that's a relief! I'm not doing any multi-threading at the moment so it's good I don't have to worry about that then! I'll also need a way to tell if unit's are interacting with storage containers so units don't stack on top of each other there either. I'll probably need some kind of 'wait in line' task that will happen while a unit is interacting with a storage container and will put waiting units into a queue.
     
  24. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman
    Your tutorial is great! You've covered quiet a lot in 30 min. Here are my remarks:

    1 - Wording
    Excepted comments, everything in a BT script is a node. It runs, fails or succeeds. Then, there are 2 types of node:
    • Structural
      • tree: it essentially gives a name to its child node so you can call it from anywhere (similarly to a function).
      • sequence
      • fallback
      • repeat
      • ... etc
    • Task: a leaf of the tree (it does not have children). Tasks do the real job and are implemented as a C# sharp method in one of the MonoBehaviour attached to the GameObject.
    In some resources, the structural nodes are subdivided further into two categories: composite and decorator. Composite are nodes with children and decorators do some transformation to the node status. So, in Panda BT:
    • Composite: sequence, fallback, parallel, race, random
    • Decorators: not, mute
    I'm not sure in which category the while and repeat would fall...

    Strictly speaking, there are no booleans in the BT script. Though, in Panda BT you can tag a boolean variable in C# as a [Task]. The true of false value will map to succeed or fail respectively. That's just a shortcut to implement a task that completes immediately in success or failure. You could implement the same using a method.

    Sometime, (for instance here ), people make a distinction between a task (or action) and a condition. In Panda BT there are technically no such distinction: every leafs are tasks. But a task implemented using a bool can be considered as a condition.

    As @laurentlavigne mentioned, you can use any node as the first child of the while node, from a simple task, a sequence or fallback, or even a tree if your condition is really complex.

    2 - BT script

    About: https://youtu.be/2sYHmUdM_W8?t=580

    That's right, a fallback node can have as many children as you want (the minimum is one). It's the same about: sequence, parallel and race.

    About this tree:

    Code (CSharp):
    1. tree ("woodcutter")
    2.     while
    3.         isWoodcutter
    4.         sequence
    5.             StandUp
    6.             sequence
    7.                 tree("checkPants")
    8.                 tree("checkHandRight")
    9.                 tree("harvestWood")
    Unless you want to make some sort of grouping, the second sequence is unnecessary. You can simplify the script to:
    Code (CSharp):
    1. tree ("woodcutter")
    2.     while
    3.         isWoodcutter
    4.         sequence
    5.             StandUp
    6.             tree("checkPants")
    7.             tree("checkHandRight")
    8.             tree("harvestWood")
    About this task:
    Code (CSharp):
    1.     [Task]
    2.     public void StandAround()
    3.     {
    4.         if (InteractablesManager.instance.stools.Count > 0)
    5.         {
    6.             Task.current.Fail();
    7.         }
    8.     }
    Here, there is some logic embedded inside the task implementation. Testing the availability of chair should be done in the BT script. I know that would make the StandAround an empty function doing nothing, but that's not a problem (actually, that's what you want your AI to do: nothing). Though maybe you want to activate some idle animation there. Then I would implement the idle tree like this:
    Code (CSharp):
    1. tree("idle")
    2.     while isIdle
    3.         fallback
    4.             sequence
    5.                 FindStool
    6.                 GoToDestination
    7.                 FaceInteractable
    8.                 SitOnStool
    9.             while not FindStool
    10.                 StandAround
    This might seem insignificant. But it's a good example about having as less as logic as possible into the task definition, and have that logic implemented in the BT script instead. That makes the behaviour a bit more explicit ( You go sit on a chair if you find one, otherwise stand around until a chair becomes available).

    3 - Potential viewers:
    Once in while, there are people landing on the Panda BT website from this page. They are probably looking for behaviour tree tutorials, so I've added a link to your video.


    I had fun watching your video and I'm looking forward for the next one!
     
    Last edited: Mar 25, 2017
    laurentlavigne and devstudents like this.
  25. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Thanks for the fantastic feedback! I'll make sure I implement all of that in the next video. Thanks for sharing the video on that forum page too.

    Just a quick question: you say you are not sure whether while and repeat nodes fall into the decorator or composite subcategories of structural nodes..... does that mean that while and repeat nodes don't have children? In the video I interpret the sequence in the "harvestWood" tree to be the child of the repeat node. Is this incorrect?
     
    laurentlavigne likes this.
  26. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman
    Yes, that's correct: the while and the repeat node do have children (2 for while and 1 for repeat). I just don't know how to categorize them according to what I've read. They are not quiet composite, since they have a limit to the number of children and they are not really decorator, since they are not used to change the status of the children. I guess they are a category of their own, like:
    • conditional node: while
    • loop node: repeat
    If you want to talk about the different types of structural nodes and group them into some categories, I would classiffy them as follow:
    • identifier: tree
    • composite:
      • synchronous:
        • sequence
        • fallback
      • asynchronous
        • parallel
        • race
      • probabilistic: random
    • conditional: while
    • iterative: repeat
    • decorator
      • inverter: not
      • ignore: mute
     
    laurentlavigne and devstudents like this.
  27. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    Perfect, thank you. I'll use that classification of nodes.
     
  28. moosefroggo

    moosefroggo

    Joined:
    Jul 10, 2015
    Posts:
    2
    Hi mate. I downloaded your asset today and can't figure out some things. This is my first time using a BT. So I may be doing some dumb things.
    I am trying to set up an Enemy AI with the help of Panda Behaviour. But I'm running into a problem.
    Scenario is like this:
    The enemy will check distance from the player, if it is greater than 1, enemy will keep moving towards him. If it is less than 1, enemy would stop and from thereon, he would randomly select to either:
    Hit the player
    Defend
    Move back.
    Here's my script
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Panda;
    5.  
    6. public class EnemyPandaBehaviour : MonoBehaviour {
    7.     Transform Player;
    8.     Animator anim;
    9.     float distance;
    10.     // Use this for initialization
    11.     void Start () {
    12.         Player = GameObject.FindGameObjectWithTag ("Player").transform;
    13.         anim = GetComponent<Animator> ();
    14.     }
    15.  
    16.     // Update is called once per frame
    17.     void Update () {
    18.      
    19.     }
    20.     [Task]
    21.     void Move(){
    22.         float temp = 2f;
    23.         Debug.Log (tooClose().ToString());
    24.         transform.position -= new Vector3 (1 * Time.deltaTime, 0, 0);
    25.         anim.SetFloat ("Speed", 2f);
    26.     }
    27.  
    28.     [Task]
    29.     void CheckDistance(){
    30.         distance = Vector3.Distance (transform.position, Player.transform.position);
    31.         //Debug.Log (distance);
    32.     }
    33.     [Task]
    34.     bool tooClose(){
    35.         bool temp = distance < 1;
    36.         Debug.Log (temp);
    37.         return temp;
    38.     }
    39.     [Task]
    40.     void FallBack(){
    41.         return;
    42.     }
    43.     [Task]
    44.     void Attack(){
    45.     }
    46.     [Task]
    47.     void Defend(){
    48.     }
    49.     [Task]
    50.     void Stop(){
    51.         transform.position += Vector3.zero;
    52.     }
    53.  
    54. }
    55.  
    I haven't implemented some of the functions yet because I am still stuck on the first one
    Code (CSharp):
    1. tree("Root")
    2.     parallel
    3.         tree("Move")
    4.         tree("CheckDistance")
    5.  
    6.  
    7. tree("Move")
    8.     while
    9.         not tooClose()
    10.         Move()
    11.  
    12.  
    13.  
    14.  
    15. tree("CheckDistance")
    16.     sequence
    17.         CheckDistance()
    From what I understand, I am having trouble with using conditions. I've been trying around different things like, switching to different nodes like repeat, sequence. But still can't do what I want to, even after trying for hours. I've read documentation twice already, and watched the video posted above few posts. I hope you can help me out.
    Edit: I found out the issue was in while, I thought it worked like programming languages, but I read one of the earlier posts in this thread and fixed it, now I need to understand how to perform next tasks like hitting the player after the enemy is tooClose(). Here's the fixed code:
    Code (CSharp):
    1. tree("Root")
    2.     repeat
    3.         tree("Move")
    4.  
    5.  
    6. tree("Move")
    7.     while not tooClose()
    8.         repeat
    9.             Move()
    10.  
     
    Last edited: Mar 26, 2017
    laurentlavigne and devstudents like this.
  29. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    @holycrap55 I thought this would be a fun topic for the next video. Here's what I have so far:

    Code (CSharp):
    1. tree("Root")
    2.     fallback
    3.         tree("searchForPlayer")
    4.         tree("pursuePlayer")
    5.         tree("attackPlayer")
    6.  
    7. tree("searchForPlayer")
    8.     while
    9.         not isPlayerInSight
    10.         SearchForPlayer
    11.  
    12. tree("pursuePlayer")
    13.     while
    14.         not isPlayerInRange
    15.         repeat
    16.             sequence
    17.                 SetPlayerTarget
    18.                 WaitToRecalculate
    19.  
    20. tree("attackPlayer")
    21.     while
    22.         isPlayerInRange
    23.         Random
    24.             AttackPlayer
    25.             Defend
    The only bit I haven't tested so far is the attackPlayer tree. Everything else works pretty well. I thought the behaviour you want to achieve would be a good example of using the Random node.

    I will upload the video and the project tomorrow if you can wait till then. Otherwise keep following this thread because @laurentlavigne and @ericbegue are really helpful!!
     
    moosefroggo and laurentlavigne like this.
  30. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    Yes while is a sort of "if" with no repeat power. It's confusing at first and Eric was thinking of changing its name (probably a good idea seeing how many people get confused), unlike c# though it can interrupt the flow of tasks and that's super powerful. I think you got that already and @mattockman code.
    When you want to add line of sight and cover in your AI, have a look at the Shooter example that comes with the Panda BT package, it's good.

    @mattockman adding burglars and cops in your village? That's one hell of a sim you're making :)

     
    moosefroggo and devstudents like this.
  31. moosefroggo

    moosefroggo

    Joined:
    Jul 10, 2015
    Posts:
    2
    @mattockman @laurentlavigne Thanks a lot for your consideration, I really appreciate it.
    Um, one thing. The game I am working on is sort of a 2.5D fighter, so searching for player, and the visibility of a player is not an issue. We can just assume that our Player will be in sight all the time. Although from what I created last night, just for the sake of working, the enemy is chasing the player as soon as he just moves a little. I would try to make it more realistic by checking the current position > move towards current position > wait some time > repeat. However I am not sure that might be the "best" approach.
    @mattockman I'll be waiting for that video. Also, I think I might be able to use that snippet you posted by modifying some parts. I may have a bit of clarity now. I think I am getting the grasp of it little by little. Thanks again mate!
     
    laurentlavigne and devstudents like this.
  32. danjool

    danjool

    Joined:
    Apr 10, 2016
    Posts:
    3
    The docs for While state, "It repeats the condition..."

    Seems like a possible source of confusion in the While-Wars.

    Also, portions of the text on pandabehaviour.com are not selectable. Could be bad SEO.

    Cool asset, though. I'll likely end up buying the baller version this weekend. Plus review.

    I think a handful of 5-minute tuts on youtube would help sell the idea. Something to really get across the philosophy of thinking through encoding the tasks/trees in a way that takes advantage of BT's.
     
    laurentlavigne likes this.
  33. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    I think what you want to do here is pretty close to the "ChasePlayer" tree of the Play Tag demo, with an extra wait task:
    Code (CSharp):
    1. tree("ChasePlayer")
    2.     while IsIt
    3.         repeat
    4.             sequence
    5.                 SetDestination_Player
    6.                 MoveToDestination
    7.                 Wait(1.0)
    Code (CSharp):
    1. while
    2.     sequence
    3.         DebugLog("Tic")
    4.         Wait(0.5)
    5.         DebugLog("Tac")
    6.         Wait(0.5)
    7.     sequence
    8.         DebugLog("Zzzzz")
    9.         Wait(3.0)
    10.         DebugLog("Wake up!")
    The tic-tac sequence will be repeated 4 times. ( The action starts the first time the sequence succeeds, then it is repeated 3 times before the action completes).

    This is how the while node literally works; the condition is repeated. That's required to check whether the condition is still succeeding. It would be hard to describe this node without using the word 'repeat' or synonyms.

    I've noticed that too, it seems coming from the Wordpress theme I'm using. It is actually selectable. But the style renders the background as not selected... I'm not very satisfied with the current website. I wish I would have some time to re-design it.

    Thanks for buying and for your review, it helps a lot.

    I also think tutorial videos would help promoting BT, it seems to be such an underrated tool. I'm really glad @mattockman is making his videos.
     
    Last edited: Mar 28, 2017
    laurentlavigne and devstudents like this.
  34. danjool

    danjool

    Joined:
    Apr 10, 2016
    Posts:
    3
    Thanks for the tic tac example. I must admit I kept staring at it, and squinting, and finally I just pasted it into a panda behavior and stepped through it live! And now to go to sleep so the insight will dissolve away.

    Alt-tabbing restarts the whole panda behavior from the root, right? At least if you have it running in the editor.
     
  35. ASterkhov

    ASterkhov

    Joined:
    Apr 5, 2011
    Posts:
    2
    Hello! This asset is cool. Why? Why I found it so late? I try it and I love it. But I have a behaviour wich I dont know how do with Panda BT.
    I have a few characters, each can do some work: mine, chop tree, build, hunting and so on...
    And I wish to each character has his own priority with this jobs.
    for example character 1:
    Code (CSharp):
    1. tree("Root)"
    2.     fallback
    3.         tree("tryMine")
    4.         tree("tryChop")
    5.         tree("tryBuild")
    6.         tree("tryHunting")

    but charactre two can has different order:
    Code (CSharp):
    1. tree("Root)"
    2.     fallback
    3.         tree("tryBuild")
    4.         tree("tryChop")
    5.         tree("tryMine")
    6.         tree("tryHunting")
    job order generated for each character, and I dont know what order will be.
    I see only one way, dynamicaly generate behaviour tree and compile it in runtime.
    May be your know some tricks and advice me how do this. Thanks.
     
    laurentlavigne likes this.
  36. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    Sorry, It was not a meaningful example to illustrate the repetition of the condition node (surely I just wanted to go to sleep). Maybe this one will make more sens:
    Code (CSharp):
    1. tree("CrossStreetSafely")
    2.     while
    3.         sequence // Condition
    4.             LookLeft
    5.             CheckForNoVehicule
    6.             LookRight
    7.             CheckForNoVehicule
    8.         MoveForward// Action
    The tree should not restart when you tab out and in of the Editor. I don't see that behaviour on my side. Could you give more details about how that happen?

    I also wish I'd have discovered BT years ago. It would have simplify my work a lot. If you like, It would be great if you leave a review on the Asset Store. That helps a lot.

    It's possible to generate and compile BT scripts at runtime. That's always an option. But I advice to use it only if nothing else works.

    I'm am not sure how you define the priority list at runtime. But if it is the player that create some order queue, you could implement an order queue system in the BT. First define some tasks to handle the order queue:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using Panda;
    4.  
    5. public class OrderQueueBT : MonoBehaviour
    6. {
    7.     Queue<string> orderQueue = new Queue<string>();
    8.     string currentOrder;
    9.  
    10.     public void QueueOrder(string order) // Call this somewhere to queue order
    11.     {
    12.         orderQueue.Enqueue(order);
    13.     }
    14.  
    15.     [Task]
    16.     void DequeueOrder()
    17.     {
    18.         if( orderQueue.Count > 0)
    19.         {
    20.             currentOrder = orderQueue.Dequeue();
    21.             Task.current.Succeed();
    22.         }
    23.         else
    24.         {
    25.             currentOrder = null;
    26.             Task.current.Fail();
    27.         }
    28.     }
    29.  
    30.     [Task]
    31.     void IsCurrentOrder( string order )
    32.     {
    33.         Task.current.Complete(currentOrder == order);
    34.     }
    35. }
    36.  
    Then make the AI unit to execute the orders as follow:
    Code (CSharp):
    1. tree("ExecuteOrders")
    2.     repeat
    3.         sequence
    4.             DequeueOrder
    5.             fallback
    6.                 sequence IsCurrentOrder("mine")  tree("DoMining")
    7.                 sequence IsCurrentOrder("build") tree("DoBuilding")
    8.                 sequence IsCurrentOrder("chop")  tree("DoChopping")
    9.                 sequence IsCurrentOrder("hunt")  tree("DoHunting")
    10.                
     
    Last edited: Mar 28, 2017
  37. danjool

    danjool

    Joined:
    Apr 10, 2016
    Posts:
    3
    Code (CSharp):
    1. void Update(){
    2.     energy -= 0.2f*Time.deltaTime;
    3.     boredom += 0.4f*Time.deltaTime;
    4. }
    5.  
    6. tree("Root")
    7.     fallback
    8.         sequence
    9.             isBored [Task] bool isBored() { return ( boredom > 90 ); }
    10.             tree("readBook")
    11.         sequence
    12.             isTired [Task] bool isTired() { return ( energy < 10 ); }
    13.             tree("takeNap")
    14.  

    In this case, if a character is born into the world with low energy, low boredom, and so immediately jumps into takeNap mode. While napping, their boredom level crests 90, but readBook doesn't kick in because takeNap is already in progress. When takeNap Succeed()'s, then the tree will start over, isBored will allow readBook to kick in. That's what I would expect to happen, and what does happen if I don't alt-tab.

    If I do alt-tab while napping with high boredom, it appears the tree resets and the character begins to sleep walk towards the book!
     
  38. ASterkhov

    ASterkhov

    Joined:
    Apr 5, 2011
    Posts:
    2
    That's great idea. This is really what I need. So, I didn't even think in this direction. Big Thanks for advice, and of course I will leave a review on Asset Store.
     
  39. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    What if instead of an order queue, it's an order of favorite actions set by the personality of the character? So if a character is a bookworm he'll favor reading if he's got the mental stamina over playing badmington.
     
  40. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    It seems I know where you are going with this: you want a way to tick an arbitrary tree from C#. I am going to add an extra method on the PandaBehaviour component to retrieve a tree by name, then you can tick that tree from a task. So, your example would be possible to be implemented as follow:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using Panda;
    4.  
    5. public class PreferredActivitiesBT : MonoBehaviour
    6. {
    7.     public string[] preferredActivities =
    8.     {
    9.         "ReadBook",
    10.         "PlayBadmington",
    11.     };
    12.  
    13.     List<Panda.Tree> trees = new List<Panda.Tree>();
    14.  
    15.     void Start () {
    16.         var bt = GetComponent<PandaBehaviour>();
    17.         foreach( var activity in preferredActivities)
    18.             trees.Add(bt.GetTree(activity));
    19.     }
    20.  
    21.     [Task]
    22.     void DoPrefferedActivity(int i)
    23.     {
    24.         trees[i].Tick();
    25.     }
    26. }
    27.  
    Code (CSharp):
    1. tree("Root")
    2.     fallback
    3.         DoPrefferedActivity(0)
    4.         DoPrefferedActivity(1)
    I will implemented the GetTree method. It will be available in the next release.
     
    laurentlavigne and devstudents like this.
  41. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    Thank you in advance for the review! It comes as a great support.
     
  42. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    Haha, no I wasn't hinting at ticking trees from c# but what you showed looks really good so I'm glad you went with that.
    It also solves Utility and GOAP quite elegantly so thank you :)
     
    ericbegue likes this.
  43. devstudents

    devstudents

    Joined:
    Feb 18, 2014
    Posts:
    132
    @holycrap55 well, I got slightly carried away. I diverged quite a bit from the BT you are trying to create. Hopefully it is still useful to you:



    @laurentlavigne and @ericbegue any feedback on the project would be most appreciated. The link to the project is in the description of the video :)
     
  44. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,374
    How do we check multiple conditions? I've only just started messing with this.

    Code (CSharp):
    1. tree("Root")
    2.     repeat
    3.         mute
    4.             fallback
    5.                 tree "AttackTarget"
    6.                 tree "ChaseTarget"
    7.                 tree "Idle"
    8.  
    9. tree "AttackTarget"
    10.     while HasTarget
    11.         while IsNearTarget
    12.             sequence
    13.                 Attack
    14.  
    15. tree "ChaseTarget"
    16.     while HasTarget
    17.         repeat
    18.             sequence
    19.                 Chase
    20.  
    21. tree "Idle"
    22.     Succeed
    My problem is that IsNearTarget only gets checked once (when HasTarget = true), whereas HasTarget is checked constantly.

    So, when we have a target, it stops idling. AttackTarget fails at IsNearTarget, so ChaseTarget is selected, but when I'm near the target, it won't switch to AttackTarget.
     
  45. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @mattockman
    Great tutorial! You've mastered the BT scripting very well so I don't have much to say about this part except that the script are neat and well structured.

    Though, I have remarks about some tasks. It seems you are putting a lot of logics on the C# side, whereas these logics could be handle on the BT script. For instance, this task:
    Code (CSharp):
    1.     [Task]
    2.     public void SearchForPlayer()
    3.     {
    4.         if(attackTarget != null)
    5.             if (Vector3.Distance(transform.position, attackTarget.transform.position) >= 10)
    6.             {
    7.                 Task.current.Fail();
    8.             }
    9.             else if (Vector3.Distance(transform.position, attackTarget.transform.position) < 10)
    10.             {
    11.                 attackTarget.GetComponent<UnitTasks>().isTargetedByEnemy = true;
    12.                 attackTarget.GetComponent<UnitTasks>().attackTarget = gameObject;
    13.                 audioSource.clip = warCries;
    14.                 audioSource.Play();
    15.                 Task.current.Succeed();
    16.             }
    17.     }
    There is a lot going on there, and it seems to be more than just "searching for the player". The goblin would idle unless the player comes near by, then it acquires the player as a target and do a war cry. This SearchForPlayer method can be broken down to the following tree:
    Code (CSharp):
    1. tree("searchForPlayer")
    2.     while not HasAttackTarget
    3.         sequence
    4.             DistanceToPlayerLessThan(10.0)
    5.             SetTarget_player
    6.             ShoutWarCry
    This would make the logic readable directly from the BT script, and you would have some additional tasks that you can reuse for defining other trees.

    The top tree would look something like this:
    Code (CSharp):
    1. tree("Root")
    2.     fallback
    3.         tree("combat")
    4.         tree("searchForPlayer")
    5.         tree("idle")
    6.        
    About this, a video covering how all the different nodes work would be great. Your viewers could use it as a reference to understand how the nodes works so you don't have to explain them in the future tutorial which would put more focus on your on your actual implementation. I've made this demo with the same purpose which you could use in your video, but I think it would great if you could come up with some examples using your RPG demo to illustrate how the nodes work, which would be more concrete.

    Keep up the good work!
     
    Last edited: Apr 9, 2017
    devstudents likes this.
  46. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    You can use a sequence or a fallback with the while node to combine tasks to form a complex condition. The sequence node implements the AND logic, whereas the fallback not implements the OR logic.

    In your AI, you want to attack the target when it has been acquired and it is near, so you can do the following:
    Code (CSharp):
    1. tree "AttackTarget"
    2.     while
    3.         sequence // sequence implements AND
    4.             HasTarget
    5.             IsNearTarget
    6.         Attack
    You need to bail out of the "ChaseTarget" tree when you get close to the target, so you can include a "not IsNearTarget" as a condition for the "ChaseTarget" tree:
    Code (CSharp):
    1. tree "ChaseTarget"
    2.     while
    3.         sequence // we have a target AND it's not near by
    4.             HasTarget
    5.             not IsNearTarget
    6.         Chase
    Also, the "repeat mute" is unnecessary in the root tree if you have the "repeat root" option checked on the PandaBehaviour component.
     
    Last edited: Apr 14, 2017
    IggyZuk and Stardog like this.
  47. cephalo2

    cephalo2

    Joined:
    Feb 25, 2016
    Posts:
    218
    Hi,
    I'm having a bit of trouble with the repeat node. I need to repeat something an unknown number of times depending on a condition, but when you use repeat without specifying an amount of repetitions, it can only fail, or by using not, only succeed. I can probably do what I want to do inside of a task, but I'm trying to increase my fluency in the BT language.

    Let me give an example. This is the AI for a strategy game, where each city can build one unit, and it must build the best unit it can. Here is the tree with the irrelevant stuff cut out:
    Code (CSharp):
    1. tree "CityDeploy"
    2.     fallback
    3.         //First check immediate threats then
    4.         //possible gambit then
    5.         //basic security then
    6.         //support then
    7.         //backup requests then
    8.         //spearhead maintenance then
    9.         //supply
    10.  
    11.         //threats-----------------------------------
    12.         trySomthing
    13.         trySomthing
    14.         trySomthing
    15.         trySomthing
    16.         trySomthing
    17.         //possible gambits----------------------------
    18.         trySomthing
    19.         //basic security------------------------------
    20.         trySomthing
    21.         trySomthing
    22.         trySomthing
    23.         //count maintenance-----------------------
    24.         sequence
    25.             mute establishAllCounts //creates list of units to parse through
    26.             sequence
    27.                 not
    28.                     repeat
    29.                         sequence
    30.                             chooseMostNeededUnit //if this fails, whole section should end in failure
    31.                             not tree "DeployUnitForward" //if this fails, repeat. If this succeeds whole section should succeed
    32. //        //supply--------------------------------------
    33.         fallback //This should happen if above section fails
    34.             tree "DeploySupplyShip"
    35.             tree "DeploySupply"
    36.  
    The 'count maintenance' section of this tree doesn't work because failure on each child task has a different desired outcome, and as written, never gets to the following "supply" section because repeat can only fail and using the not only means that it can only succeed, preventing the lower section from ever being used.

    chooseMostNeededUnit will pick the best unit, but on subsequent calls will pick the next best and the next best in case that the DeployUnitForward tree fails, when it runs out of units to recommend it fails.

    Since each unit has different movement rules in a way similar to chess, DeployUnitForward can fail for various reasons while other units might succeed.

    Since the purpose of this whole tree is to choose one unit, if DeployUnitForward succeeds it should end the tree, but if chooseMostNeededUnit runs out of units before then, I want to 'fall back' to the "supply" section.

    I wonder if there is a proper BT way to do this before I rewrite my tasks to do it. I'm having a logic block.

    EDIT: I think what I'm learning here is that you can't make a decision inside a repeat block, except when the repeating should end. You must record a decision in your C# code, then have another child in the sequence after the repeat block that reads and returns that record.
     
    Last edited: Apr 12, 2017
  48. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @cephalo2
    That's right. A repeat node, without specifying the number of repetitions, can complete in failure only; it never succeeds. So, it's not possible to either fail or succeed based on some condition. Indeed you can implement a task that will keep track of the decision and use it instead to indicate success or failure.

    For instance, you can implement 'tryDeployingUnit' tasks that wrap a boolean, and use it to keep trying other unit or not, and to succeed the tree or not:
    Code (CSharp):
    1. tree("TryDeployingUnit")
    2.     sequence
    3.         tryDeployingUnit(true)
    4.         fallback
    5.             while tryDeployingUnit
    6.                 repeat
    7.                     sequence
    8.                         chooseMostNeededUnit // If we went through all units, this fail.
    9.                         fallback
    10.                             sequence
    11.                                 tree "DeployUnitForward"
    12.                                 tryDeployingUnit(false) // When deploying succeeds, stop.
    13.                             Succeed // We want to continue trying with next unit.
    14.             not tryDeployingUnit // Succeed the tree only when a unit has been deployed.
     
    Last edited: Apr 13, 2017
  49. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    2,057
    Why is there a fallback? The second child does nothing but confirm the only way to exit the while repeat.
     
  50. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,294
    @laurentlavigne
    The tree("DeployUnitForward") can fail. When that occurs, we want to avoid failing the parent repeat node, because we want to keep on trying other units.