Search Unity

[AI SCRIPTING] Panda BT: Behaviour Tree scripting

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

  1. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    You might be interested by DOTween (I'm not affiliated, just like this package a lot). It's a very good tweening engine for doing small animations such as color fading, changing position, ... using very few code. And it will work well in combination with Panda BT.
     
  2. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @Captian-Brink
    Behaviour Tree is reactive and does not handle planification. The behaviour is defined in advance and it is somewhat static. Yet you can define list of tasks ordered by priority using the fallback node, but the order does not change at runtime.

    If you really need to weight the trees and dynamically sort them by weight, you would need to build this system as a layer on top on Panda BT. For that, you can get the trees by name and tick them whenever you want from a custom task in C#. Have a look at this post for an usage example of GetTree.

    Using a fallback node and the right conditions, the AI will end up executing the right tree, which can be considered of some sort of resuming. Your lumberjack getting attacked and getting back to work after the fight can be written as follow:
    Code (CSharp):
    1. tree("root")
    2.     fallback
    3.         tree("fight")
    4.         tree("gatherWood")
    5.  
    6. tree("fight")
    7.     while isAttacked
    8.     // attack actions
    9.  
    10. tree("gatherWood")
    11.     while not isAttacked
    12.     // gathering actions
    13.  
    14.  
     
    Last edited: Jun 8, 2017
  3. HyenaGamesDev

    HyenaGamesDev

    Joined:
    Aug 4, 2013
    Posts:
    32
    I'm new to BT's in general and Panda. Trying to get my first Melee AI BT working. I want it so that a Unit with this AI will constantly scan for any enemy faction in range. If it's within attack range, attack. if it's not, but is within chase range, chase. if none are found within chase range, just wander. Below is what I have.. It's not working for some reason... I think that reason is the "ScanForTargets" is not getting called repeatedly. The tree is not looping as I expect. Can someone assist?

    Code (csharp):
    1.  
    2. tree ("Root")
    3.     sequence
    4.         IsMovementAllowed
    5.         ScanForTargets
    6.         SetRotationToTarget
    7.         fallback
    8.             tree ("AttackEnemy")
    9.             tree ("ChaseEnemy")
    10.             tree ("Wander")
    11.  
    12. tree ("AttackEnemy")
    13.     sequence
    14.         TargetInAttackRange
    15.         TargetIsEnemy
    16.         AttackTarget
    17.         Wait(1.0)
    18.  
    19. tree ("ChaseEnemy")
    20.     sequence
    21.         TargetInChaseRange
    22.         TargetIsEnemy        
    23.         MoveToDestination
    24.  
    25.  
    26. tree ("Wander")
    27.     while
    28.         sequence
    29.             ScanForTargets
    30.             not TargetInChaseRange
    31.             not TargetInAttackRange
    32.         sequence        
    33.             Wander
    34.  
    Attached is a picture showing the state of my tree when it's running. TargetInChaseRange and TargetInAttackRange are failing in the beginning because the player isn't near. However, when I then walk up to the enemy, they continue to fail. The "ScanForTargets" doesn't appear to be running again and again and catching when the player is nearby.

    PS - Wander is set to Fail because I was testing that out.. The same inaction happens whether Wander Succeeds or Fails.

    Thanks for helping me figure this out!
     

    Attached Files:

  4. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    The 'while' keeps repeating the condition node (the 1st child) as long as the action (the 2nd n child) node is running. Therefore the ScanForTargets, should be repeated as long as the Wander task is running. You could debug log from the task in C# to make sure it is executed.

    Your tree seems to be ok and should run as expected. Maybe you should double check the tasks implementations.
     
  5. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Hi @ericbegue

    I have one question about the behaviour tree. I want to be able to modify the behaviour text at runtime. So change the behaviour tree itself. Is that something which is possible? I can still sort it without that, it is just a way of implementing AI behaviour that I'm thinking of.

    Thanks!
     
  6. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Hi @Nigey,
    You can pass the modified script as a string to PandaBehaviour.compile(...) at runtime.
    I'm just curious, why do you need to do it?
     
  7. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    The unit's AI, is a sum of it's components (weapons, special abilities, ect). Components can be broken off as part of gameplay. With that a units AI can become more complex, or more simple, based upon it's attached components. It would be nice to the AI in line with that.
     
  8. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    Does the Panda BT behaviour execute the BT script when the editor isn't playing?
    The panda script moves the object when the editor isn't playing, which is odd, never happened before.
     
  9. ericbegue

    ericbegue

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

    That's not supposed to happen. But it could be a bug. Anyway to reproduce it?

    Also, you could check if you have some script with [ExecuteInEditMode] that ticks the PandaBehaviour component.
     
  10. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    Yes it's there, I must have added it.
     
  11. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    This has definitely been a big help, thank you for creating it. The interface is very Pythonish which make it easier for me to understand. I do have one question though, is it possible to to reset a sequence or fallback node? What I'm trying to do is is to set a sequence as uncompleted and reiterate through them again, after the child nodes of have all succeeded or failed. Here's an example:

    Code (CSharp):
    1.  
    2. while//this returns running
    3.     CheckThis
    4.     repeat//this returns running
    5.         fallback//this returns a success
    6.             while
    7.                 OtherCondition
    8.                 repeat(10)
    9.                     NodeOne
    10.             NodeTwo//if NodeTwo or NodeThree are successful, CheckThis will be set to false
    11.             NodeThree
    12.             NodeFinal//this always returns a success
    What happens is that the first repeat node gets stuck in the running state, but there is no way to break out of it.
     
    ericbegue likes this.
  12. hungrybelome

    hungrybelome

    Joined:
    Dec 31, 2014
    Posts:
    336
    Hi @ericbegue, first off, absolutely amazing asset. Totally saved me from the tyranny of visual node editors.

    That being said, is there a way to know from within a [Task] whether the method execution is a BT task execution or a direct call? I am trying to reuse my existing player controller methods so that my AI uses the same API as players. But when I directly call the method, I get an error thrown from the Task.current getter (so I can't just null check it), and I ofc don't want to try/catch wrap it.

    Thanks!
     
    ericbegue likes this.
  13. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @BTCallahan
    Great that you gave Panda BT a try!

    The repeat should stop when the CheckThis condition fails. If you want to add more conditions to the while node you can , for instance, use a sequence or fallback (to implement AND or OR logic). For example:
    Code (CSharp):
    1. while//this returns running
    2.     sequence
    3.         CheckThis
    4.         CheckThat // <-- another condition
    5.     repeat//this returns running
    6.         fallback//this returns a success
    7.             while
    8.                 ...
    I'm not sure if I'm answering your question... What are you trying to achieve? It's would be easier to illustrate the logic with a concrete example.
     
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Hi @arlevi09,

    I'm glad you like Panda BT! It would be great if you leave a review an the Asset Store.

    The purpose of a [Task] is to be executed inside the context of a running BT script. As you are experiencing, an exception is raised if Task.current is accessed outside of that context.

    I'm not sure why you would need to directly call tasks, without using a BT script. It would be easier to implement your existing player controller using a BT script (Behaviour Tree is not limited for implementing AI characters).

    If you really need to share code between BT script and another system. I suggest to take the problem by the other end. That is implement your api without using [task], then call the api from [task] methods.
     
    Last edited: Jul 23, 2017
  15. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    It's a bare bones pathfinding tree, suitable for a basic platformer, but not much else.
    Code (CSharp):
    1. tree("MoveInFacingDirection")
    2.     repeat
    3.         fallback
    4.             while
    5.                 sequence
    6.                     CheckIfCanMoveInCurrentDirection
    7.                     not CheckBottomlessPit
    8.                 repeat(10)
    9.                     sequence
    10.                         CheckIfCanMoveInCurrentDirection
    11.                         not CheckBottomlessPit
    12.                         MoveInDirection(0.05)
    13.                         Wait(0.05)
    14.             sequence
    15.                 CheckBottomlessPit
    16.                 CheckIfCanJumpOverCaps(2.0)
    17.                 Jump(2.0)
    18.             sequence
    19.                 CheckIfCanJumpOverCaps(1.0)
    20.                 Jump(1.0)
    21.             sequence
    22.                 IsDestroyableObjectBlockingMovment
    23.                 tree("FireWeapon")
    24.             ReverseDirection
    An explanation of the tasks that have been defined is as follows:
    CheckIfCanMoveInCurrentDirection uses a raycast to check if there a wall in front of the enemy game object, while CheckBottomlessPit uses another to determine if if there is a pitfall in front of the enemy (in this case, I define a pitfall as any drop the the enemy will not be able to jump back up from). CheckIfCanJumpOverCaps uses capsule casts to detect if there is room for the enemy to jump up on a ledge. The argument that it takes is the horizontal distance in meters between the current position and the point to check is. A value of one is used for checking an adjacent tile, a value of two is used for checking a tile two units over, etc. IsDestroyableObjectBlockingMovment is a raycast test that checks if there is an object on a layer 9 (the layer where I keep any destructible obstacle) in front of the enemy. ReverseDirection changes the facing direction (a vector3 is used since the final game uses sprites) and always returns true.

    The five children of the fallback node work like this: As long as there is nothing blocking the way forward, or there is no pitfall that the enemy will be unable to jump out of, it will move forward. If either of them fail, then the next node checks if there is a pit in front of the enemy, and if so, that it can be jumped over. If these checks succeed, the enemy jumps. Otherwise the third node checks if the ledge the enemy is on is is not too steep, executing a jump if successful. If that check fails, it checks if a destroyable object is blocking the way. If so, then it fires the weapon. Finally, if all these nodes return false, the ReverseDirection task is called.
     
  16. LeonHromyko

    LeonHromyko

    Joined:
    Feb 24, 2016
    Posts:
    4
    Is it bug or feature? Task Wait(2.0) in the following tree does not work.
    Code (CSharp):
    1. tree("Attack")
    2.     while
    3.         fallback
    4.             HasEnemy
    5.             AcquireEnemy
    6.         sequence
    7.             //FireWeaponToHand
    8.             repeat
    9.                 fallback
    10.                     while HasGrenade
    11.                         sequence
    12.                             GrenadeAttack
    13.                             Wait(2.0)
    14.                     //FireWeaponAttack
    15.             ClearEnemy
    If task Wait(2.0) is written before GrenadeAttack that work fine.
     
    Last edited: Jul 21, 2017
  17. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    My guess is you test this with only one grenade. While is evaluated every tick so as soon as it threw the last grenade it skips the entire while sub tree, never evaluating Wait.
     
    ericbegue likes this.
  18. LeonHromyko

    LeonHromyko

    Joined:
    Feb 24, 2016
    Posts:
    4
    I've already tested it with multiple grenades, and all of them were thrown without delay.
     
  19. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @BTCallahan
    Do you want to interrupt a sequence, do something else, then resume that sequence? This is not how a sequence works. Once it has completed (in sucess or failure), it will be restarted the next it is ticked.

    If you want such mechanism, you would need to implement it using some tasks to keep track of a list of action and storing the states on the C# side.
     
    Last edited: Jul 21, 2017
  20. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @LeonThundeR
    I thought the same as @laurentlavigne.

    Here is another guess. If HasGrenade does not fail when you're are throwing the last grenade or when there are grenades left, then it must be grenadeAttack that fails.

    Or, both HasEnemy and AcquireEnemy fail (which makes sens if the enemy got killed by the thrown grenade).
     
  21. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    I constantly run into trouble with that type of situation, how do you systematically handle that, Eric? a.k.a rewrite Leon's script.
     
  22. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Actually, I've been playing around with it I found that if I removed the Task.current.Succeed() the the end of the methods, then the node will restart. Also using race/parallel nodes really help out. I feel kind of embarrassed. Thanks anyway for your help.
     
  23. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    In that case, a 'sequence' is more appropriate instead of the first 'while'. By using a 'sequence', the condition is evaluated once, then the rest of the sequence is executed without being interrupted even if the condition would fail if re-evaluated. Similarly to the way you keep driving pass a green traffic light that turns red behind your car.
     
    Last edited: Jul 23, 2017
  24. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    @ericbegue thanks. The fun part about your BT language is when going back on old code from just 2 month ago and scratch my head, redo it in half the line count :)
     
    ericbegue likes this.
  25. LeonHromyko

    LeonHromyko

    Joined:
    Feb 24, 2016
    Posts:
    4
    @ericbegue
    My bad, now everything works fine, should pay more attention to tasks results: Succeed or Fail. Thanks for your help.
     
  26. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Quick question about the Wait and RealtimeWait methods: is it possible for them to be called in a .cs script? I've tried Task.Wait, and BTTask.Wait, but I can't find where they are defined.
     
  27. LeonHromyko

    LeonHromyko

    Joined:
    Feb 24, 2016
    Posts:
    4
    Code (CSharp):
    1. var pandaBT = GetComponent<PandaBehaviour>();
    2. pandaBT.Wait(2.0f);
     
    BTCallahan likes this.
  28. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Yes, it's possible to call them from a .cs script, but only from [Task] methods (as I said earlier, tasks are supposed to be called only within BT script context).

    This can be useful to define new tasks from existing one. Let's say you want to reuse the Wait task to define a task that plays a sound then wait until the sound ends:
    Code (CSharp):
    1. using UnityEngine;
    2. using Panda;
    3.  
    4. public class PlaySoundBT : MonoBehaviour {
    5.  
    6.     public AudioSource sound;
    7.     PandaBehaviour panda;
    8.  
    9.     void Awake()
    10.     {
    11.         panda = GetComponent<PandaBehaviour>();
    12.     }
    13.  
    14.     [Task]
    15.     void PlaySound()
    16.     {
    17.         // Play the sound when the task starts
    18.         if( Task.current.isStarting)
    19.           sound.Play();
    20.  
    21.         // Wait until the sound ends using the existing 'Wait' task
    22.         panda.Wait( sound.clip.length );
    23.     }
    24. }
    25.  
    Tip: For any task, just double click on it in the inspector. That will open your code editor right at the task definition.
     
    Last edited: Jul 30, 2017
    BTCallahan likes this.
  29. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    Sorry to bug you again (and thank you for your answer to my previous question), but I've run into a strange error message. The console tells me that:
    The method that it's pointing to is named LeapAttack which is defined as so:
    Code (CSharp):
    1.  
    2.      public void LeapAttack(float amount){
    3.         jumped = true;
    4.         _jumpVsFall = baseEntity.JumpCelling;
    5.  
    6.         _jumpFallMotion = FaceingDirection * baseEntity.GetLeapDistance * amount;
    7.        
    8.         Con.height = baseEntity.JumpHeight;
    9.         Con.Move (new Vector3 (0f, 0.1f, 0f));
    10.         Task.current.Succeed ();
    11.     }
    I tried putting in an if(task.isStarting) but the error still showed up. What really baffling about it is that the Jump method is almost identical yet it doesn't cause the error.
    Code (CSharp):
    1. [Task]
    2.     public void Jump(float amount){
    3.         jumped = true;
    4.         _jumpVsFall = baseEntity.JumpCelling;
    5.  
    6.         _jumpFallMotion = FaceingDirection * amount * (baseEntity.MoveSpeed / baseEntity.JumpCelling);
    7.  
    8.         Con.height = baseEntity.JumpHeight;
    9.         Con.Move (new Vector3 (0f, 0.1f, 0f));
    10.  
    11.         Task.current.Succeed ();
    12.     }
    I've been trying to figure out what's causing it to no avail.
     
  30. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @BTCallahan
    You can ask whatever questions you want, I'd be glad to answer them. That's what this forum is for.

    I think you typed a semicolon ";" in the BT script. The error message is not very clear, sorry for that. I'll better handle this error. The message suggest to implement a task method named ";", which is not possible in C#.

    Also, maybe it's just a copy-past problem, but LeapAttack is missing the [Task] attribute. Only methods with [Task] are recognized as tasks by the system.
     
    Last edited: Aug 8, 2017
  31. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    I checked, and you are completely right. Nine times out of ten, it's one of those problems that's caused by one or two characters being misplaced. Thanks for your help.
     
  32. cephalo2

    cephalo2

    Joined:
    Feb 25, 2016
    Posts:
    263
    I think I'm doing something illegal, and I want to verify that. Let me start with some snippets.

    Code (CSharp):
    1.  
    2. fallback
    3. ...
    4.         sequence
    5.             not
    6.                 fallback
    7.                     unitInRange(2,false,-1)//Friendly Airfield
    8.                     unitInRange(7,false,-1)//Friendly Carrier
    9.             mute chooseUnit(2)//Airplane
    10.             tree "DeployUnitInDefense"
    11.         sequence
    12.             not
    13.                 fallback
    14.                     unitInRange(2,false,-1)//Friendly Airfield
    15.                     unitInRange(7,false,-1)//Friendly Carrier
    16.             mute chooseUnit(7)//Carrier
    17.             tree "DeployUnitInDefense"
    18.         sequence
    19.             not unitInRange(3,false,-1)//Friendly Artillery
    20.             mute chooseUnit(3)//Artillery
    21.             tree "DeployUnitInDefense"
    22.         sequence
    23.             not
    24.                 fallback
    25.                     unitInRange(3,false,-1)//Friendly Artillery
    26.                     unitInRange(8,false,-1)//Friendly Battleship
    27.             mute chooseUnit(8)//Battleship
    28.             tree "DeployUnitInDefense"
    29. ...
    30. tree "DeployUnitInDefense"
    31.     sequence
    32.         hiliteMoves(true)
    33.         findSafeCityDefenceCell()
    34.         deployUnit
    35.  
    I noticed that when the tree "DeployUnitInDefense" fails, it can't be called anymore, as if it's marked as failed permanently for this root tick. None of the code inside the tree is executed after the first time. Is it illegal to use the same tree multiple times in the same parent tree?

    I guess I was trying to reuse code, but I suppose I could copy/paste the contents to avoid this problem. I just wanted to verify if my theory is correct.

    EDIT: I can confirm that copying and pasting the code works as expected.
     
    Last edited: Aug 10, 2017
  33. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @cephalo2
    Thank you for reporting this. Consider it a bug.

    Good catch! You are right that the tree is marked as failed during the whole root tick. That should not happen, the tree should be reset in the sequences that follow.

    This bug will be fixed in the next release.

    Note: the only thing not supported though, is that the same tree can not be run in parallel. That will raise an error. Because, it would make the tree to be in different state at the same time. That would be impossible (or more complicated) to visualize in the inspector.
     
    Last edited: Aug 10, 2017
  34. cephalo2

    cephalo2

    Joined:
    Feb 25, 2016
    Posts:
    263
    I've been using panda for a while now, and I think I have an idea I would like to propose. In a previous post I talked about how the 'repeat' node, without a specific repeat count, basically blocks all information coming out of it because it can only return false.

    I'm finding the workarounds to this less than ideal, but I think I have a useful solution. First, let me give an example of a workaround.
    Code (CSharp):
    1.  
    2. sequence
    3.      startObjectList
    4.      mute setRepeatResult(false)
    5.      mute
    6.           repeat
    7.                sequence
    8.                     getNextObject //<---returns false if none left
    9.                     not  trySomethingWithObject //<----sets repeat result
    10.      checkRepeatResult //<----returns true if set by trySomethingWithObject
    11.                  
    12.  
    By saving the value of the repeat body with a variable, I can dig it back out again eventually, but this solution really muddies up the script. BT trees are already pretty confusing, so it helps to keep things simple.

    Another workaround would be to do such looping inside of a Task, but as soon as you do that, you're probably writing very specialized code. BT trees work better when tasks are atomic and generalized so you can move them around easily.

    I think a better solution would be to extend the 'repeat' node to accept an optional second child, so the behavior is as follows.

    returns Running when child1 returns true or child2 returns false.

    returns False when child1 returns false.

    returns True when child2 returns true.

    What this gives you is a looping mechanism that allows you to both determine the number of iterations at run time, and also return a useful decision that can be acted upon. The current repeat node can't do that. Also, since the second child would be optional, it wouldn't break any existing scripts.

    Let me rewrite the above example with the proposed extension.
    Code (CSharp):
    1.  
    2. sequence
    3.      startObjectList
    4.      repeat
    5.           getNextObject //<---returns false if none left, ending loop
    6.           trySomethingWithObject //<----returns true if try is successful, ending loop
    7.  
    I think this looks much cleaner, and allows me to keep the Tasks simple by putting loop control in the script instead of code. Anyway there is my suggestion.
     
    ericbegue likes this.
  35. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    The fallback block almost never gets evaluated. Bug?


    Code (CSharp):
    1.     MusicToThreat oldChoice;
    2.     float musicTime;
    3.     [Task]
    4.     void EvaluateMusic()
    5.     {
    6.         if (oldChoice == null)
    7.             oldChoice = musics [0];
    8.         int threat = EvaluateThreat ();
    9.         var choice = new MusicToThreat {threat = 0 };
    10.         foreach (var m in musics)
    11.         {
    12.             if (threat > choice.threat && threat > m.threat)
    13.                  choice = m;
    14.         }
    15.         if (choice != oldChoice && (oldChoice.source.time < musicTime || oldChoice.source.time > oldChoice.source.clip.length*.95f)) //we consider a good fade point when we're almost at the end or already past the end
    16.         {
    17.             StopCoroutine (CrossFadeTo (null, 1));
    18.             StartCoroutine (CrossFadeTo (choice, 1));
    19.             oldChoice = choice;
    20.             Task.current.Succeed();
    21.         }
    22.         musicTime = oldChoice.source.time;
    23.     }
    24.  
    Code (CSharp):
    1.         repeat
    2.             mute
    3.                 parallel
    4.                     EvaluateMusic
    5.  
    6.                     fallback
    7.                         //It's Game over when the player is dead.
    8.                         sequence
    9.                             IsPlayerDead
    10.                             RealtimeWait(1.0)
    11.                             Freeze(true)
    12.                             RealtimeWait(1.0)
    13.                             FadeoutMusic
    14.                             Display("GAME OVER")
    15.                             RealtimeWait(1.0)
    16.                             Freeze(false)
    17.                             ReloadLevel
    18.                             ResetTree
    19.  
    20.                         //The level is completed when all enemies are dead.
    21.                         sequence
    22.                             IsLevelCompleted
    23.                             Display("STAGE COMPLETE")
    24.                             Wait(3.0)
    25.                             ReloadLevel
    26.                             ResetTree
     
  36. ericbegue

    ericbegue

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

    It seems you want to implement the same behaviour as the fallback node but with a dynamic list of tasks.
    That is:
    Code (CSharp):
    1. fallback
    2.     trySomeThingWithObject_0
    3.     trySomeThingWithObject_1
    4.     trySomeThingWithObject_2
    5.     ....
    6.     trySomeThingWithObject_n
    7.  
    But of course, you don't want to have the list statically defined in the script, instead have it defined dynamically at run time.
    Is this what you want to do?

    I think the issue is more general and not specific to the repeat node: the language does actually not support dynamic list of tasks. So, for now, the workaround is to write tasks to handle the list on the C# side.

    Maybe the GetTree method can be useful for what you want to do. Have a look a this post for more detail about this method.

    Supporting dynamic list would be a great addition to BT script. But that would require a lot of design and fundamental changes to the language. I'm thinking about it, but have no concrete plans for implementation yet.
     
    Last edited: Aug 13, 2017
  37. ericbegue

    ericbegue

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

    Difficult to say whether it is a bug or not. There are two cases when the fallback node won't be evaluated:
    - EvaluateMusic fails.
    - The parallel node is not ticked.

    It seems EvaluateMusic musics never fails. But maybe there is a call to Task.current.Fail in the methods used by EvaluateMusic?

    Could you make sure that the parallel get ticked when the fallback is not. You could put a DebugLog to check:
    Code (CSharp):
    1. parallel
    2.     DebugLog("parallel ticked")
    3.     EvaluateMusic
    4.     fallback
    5.         ...
    If you could produce the minimum test case that repro this issue, that would help to narrow down the search to spot what is causing the issue.
     
  38. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    Adding the DebugLog helped, it was working actually, some other error.
    But now it no longer evaluates the fallbacks each tick, I just added not isplayerdead and not islevelcompleted in front of evaluatemusic

    Code (CSharp):
    1.  
    2.         repeat
    3.             mute
    4.                 parallel
    5.                     sequence
    6.                         not IsPlayerDead
    7.                         not IsLevelCompleted
    8.                         EvaluateMusic
    9.  
    10.                     fallback
    11.                         //It's Game over when the player is dead.
    12.                         sequence
    13.                             IsPlayerDead
    14.                             RealtimeWait(1.0)
    15.                             Freeze(true)
    16.                             RealtimeWait(1.0)
    17.                             FadeoutMusic
    18.                             Display("GAME OVER")
    19.                             RealtimeWait(1.0)
    20.                             Freeze(false)
    21.                             ReloadLevel
    22.                             ResetTree
    23.  
    24.                         //The level is completed when all enemies are dead.
    25.                         sequence
    26.                             IsLevelCompleted
    27.                             Display("STAGE COMPLETE")
    28.                             Wait(3.0)
    29.                             ReloadLevel
    30.                             ResetTree
     
  39. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    suggestion: allowing mute X in one line, similar to sequence X
    because we need to use mute A LOT to "fix" the default behavior of repeat and parallel into something that's intuitive, and since we use it as a modifier, it makes code harder to read
     
  40. ericbegue

    ericbegue

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

    So that you could write scripts like this?
    Code (CSharp):
    1. mute sequence
    2.     task1
    3.     task2
    4.     task3
    This is a good idea, and will safe tabulations and increase the script readability.
     
  41. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    Exactly.
    Maybe also every signal modifier could act the same. EDIT: "not" already does, and since it's the only other one besides mute, I guess they all do.
     
    Last edited: Aug 16, 2017
  42. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    This bit of code seems to evaluate the sequence from the beginning every frame, why?
    More explanation:
    Once it evaluates EvaluateMusic and starts CrossFadeToNewMusic it should stay in the latter until the Time.time goes to timer.
    Instead it keeps evaluating music and crossfade keeps reseting timer.

    Code (CSharp):
    1.                     mute
    2.                         sequence
    3.                             not IsPlayerDead
    4.                             not IsLevelCompleted
    5.                             EvaluateMusic
    6.                             CrossFadeToNewMusic
    Code (CSharp):
    1.     MusicToThreat currentChoice;
    2.     float musicTime;
    3.     [Task]
    4.     void EvaluateMusic()
    5.     {
    6.         if (currentChoice == null)
    7.             currentChoice = musics [0];
    8.         int threat = EvaluateThreat ();
    9.         var newChoice = new MusicToThreat {threat = 0 };
    10.         foreach (var m in musics)
    11.         {
    12.             if (threat >= newChoice.threat && threat >= m.threat)
    13.                  newChoice = m;
    14.         }
    15.         //we consider a good fade point when we're almost at the end or already past the end
    16.         if (newChoice != currentChoice
    17.             && (
    18.                 (currentChoice.waitForEndClipBeforeTransition && (currentChoice.source.time < musicTime || currentChoice.source.time > currentChoice.source.clip.length*.95f))
    19.                 ||
    20.                 !currentChoice.waitForEndClipBeforeTransition)
    21.         )
    22.         {
    23.             transitionDestination = newChoice;
    24.             Task.current.Succeed();
    25.         }
    26.         musicTime = currentChoice.source.time;
    27.  
    28.         if (Task.isInspected)
    29.             Task.current.debugInfo = threat.ToString();
    30.     }
    31.  
    32.     MusicToThreat transitionDestination;
    33.     float transitionTimer, transitionDuration;
    34.  
    35.     [Task]
    36.     void CrossFadeToNewMusic()
    37.     {
    38.         if (Task.current.isStarting) {
    39.             transitionDuration = currentChoice.waitForEndClipBeforeTransition ? 3.0f : .5f;
    40.             transitionTimer = Time.time + transitionDuration;
    41.         }
    42.         if (Time.time < transitionTimer) {
    43.                 var t = (transitionTimer - Time.time) / transitionDuration;
    44.             foreach (var m in musics) {
    45.                 if (m == transitionDestination)
    46.                     m.source.volume = Mathf.Sqrt (1 - t);
    47.                 else
    48.                     m.source.volume *= Mathf.Sqrt (t);
    49.             }
    50.         } else {
    51.             // finished
    52.             foreach (var m in musics) {
    53.                 if (m == transitionDestination)
    54.                     m.source.volume = 1;
    55.                 else
    56.                     m.source.volume = 0;
    57.             }
    58.             currentChoice = transitionDestination;
    59.             Task.current.Succeed ();
    60.         }
    61.         if (Task.isInspected)
    62.             Task.current.debugInfo = "t"+(Time.time - transitionTimer).ToString();
    63.     }
    64.  
     
    Last edited: Aug 17, 2017
  43. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    Here might be the reasons why the sequence get restarted:
    - It has completed (failed or succeeded).
    - A parent up the hierarchy has completed and has restarted.

    Here is some debug logs to help finding out what's going on:
    Code (CSharp):
    1. fallback
    2.     sequence
    3.         DebugLog("sequence starts")
    4.         not IsPlayerDead
    5.         not IsLevelCompleted
    6.         EvaluateMusic
    7.         CrossFadeToNewMusic
    8.         DebugLog("sequence succeeds")
    9.     sequence
    10.         DebugLog("sequence fails")
    11.         Fail
    Also, instead of writing DebugLogs, with the pro edition, you can click on the line number in the Inspector to setup break points that will trigger on desired node status (start, run, succeed or fail).

    According to your posted code, the sequence should never fails. If you see that it starts but does not complete, then it means something fishy is going on up there, by the parents. If it starts and succeeds in the same frame, then you should review the implementation of EvaluateMusic and CrossFadeToNewMusic.
     
    Last edited: Aug 17, 2017
  44. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    EvaluateMusic can't just run? ie: no Task.current.Stuff()?
    I designed it this way so it doesn't fail and succeeds only when there is a tune available in which case sequence moves to crossfade
     
  45. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    I'm not sure what you mean. If EvaluateMusic just runs and never completes, the sequence would get stuck there, CrossFadeToNewMusic would be never be ticked and the sequence would never complete.

    Did you try the DebugLogs? What do you see in the console?
     
    Last edited: Aug 18, 2017
  46. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I'm glad to announce that Panda BT 1.4.2 (free and pro) has been published on the Asset Store.
    Here are the changes:

    Core:
    • GC alloc optimization: minimize usage of RegEx during BT script parsing.
    • Added built-in task WaitRandom(min, max)

    Fixes:
    • Fixed BT script not being recompiled on Undo.
    • Fixed 'multiple definitions' error message not displaying target class name.
    • Highlight tasks with multiple definitions in Inspector.
    • Fix same tree not being reset when used at different place in the BT script.

    Thanks to :
     
    Last edited: Aug 18, 2017
  47. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    All pandas are executing in edit mode, I found this in PandaBehaviour [ExecuteInEditMode]

    Also I am getting this error now, could be related to the GC optim.
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. Panda.Task.get_current () (at Assets/Plugins/PandaBehaviour/Core/Panda/BT/Task.cs:82)
    3. ShootingLaser.ShutDownLaser () (at Assets/scripts/ShootingLaser.cs:111)
    4. Hackable.Init () (at Assets/scripts/Hackable.cs:54)
    5. PodModuleKatamari.Hack () (at Assets/scripts/PodModuleKatamari.cs:82)
    6. PodModuleKatamari.Update () (at Assets/scripts/PodModuleKatamari.cs:187)
    7.  
    the code bit that it's complaining about is this check which used to work, and allowed me to use tasks in panda and c#

    Code (CSharp):
    1.         if (Task.current !=null)
    2.             Task.current.Succeed ();
    3.     }
    @ericbegue you need to add a check in this line
    var task = BTProgram.current.currentNode as BTTask;
    so it doesn't nullrefs, unless there is a preferred way of testing that a method is called as a task?
     
    Last edited: Aug 18, 2017
  48. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    You mean the BT scripts are running in edit mode (e.g. nodes turning blue in the Inspector)? The PandaBehaviours are executed in edit mode to get the scripts initialized and compiled for the Inspector display. Though, the BT scripts are not ticked in edit mode. So, except if you have some custom code ticking the PandaBehaviour component, there should not be any BT script running in edit mode.

    Good catch about the Task.current NullReferenceException! That will be fixed in the next release.

    I'm still not sure why you need to call [task] methods directly. They are supposed to be called from BT scripts. When a task is called outside of a BT script context, Task.current is not available, so it is not possible to indicate the task status (Succeed, Fail or Running). @arlevi09 wanted to do the same thing. Maybe I'm missing a use case, could you guys detail why do you need to do such thing?
     
    Last edited: Aug 19, 2017
  49. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,324
    There is BT ticking, you can access my project and put back [ExecuteInEditMode] that I removed.

    In the case where the user wants to keep method and class number down, and sometimes fire a gun with bt but some other time directly from an update loop listening to input, you want to make a task callable by both.
     
  50. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    I'm having some trouble getting a node to continue executing. Everything in the sequence works well except for the MoveTowardsTarget method. What happens is that it will never get to the Task.current.Succeed part. Even though it should still be in the "loop", that is to say that Vector3.Distance (moveDestination, transform.position) > boss.Con.radius is still equal to true, it will exit the node without ticking Task.current.Succeed. That not to say that it will never call panda.RealtimeWait, just that it will do so only three for four time before it exits the node.

    The method UnsafeToMove works by setting the private bool _safeToMove to false then returning true, and IsSafeToMove simply returns the state of _safeToMove. They aren't called anywhere else in the bt script, so II'm pretty sure that they aren't causing it to exit the node.
    Code (CSharp):
    1.             sequence
    2.                 UnsafeToMove
    3.                 fallback
    4.                     while
    5.                         not IsSafeToMove
    6.                         sequence
    7.                             RotateInRandomDirection(0)
    8.                             SetRandomAmountToMove(0,2,11)
    9.                     while
    10.                         IsSafeToMove
    11.                         MoveTowardsTarget(0)
    12.  
    Code (CSharp):
    1.  
    2.     [Task]
    3.     public void MoveTowardsTarget(int subComNumber){
    4.  
    5.         Debug.Log ("Moving towards target");
    6.  
    7.         if (Vector3.Distance (moveDestination, transform.position) > boss.Con.radius) {
    8.             boss.Con.SimpleMove (bossParts [subComNumber].transform.forward * boss.MoveSpeed);
    9.             Debug.Log ("Waiting");
    10.             panda.RealtimeWait (0.1f);
    11.  
    12.         } else {
    13.             Debug.Log ("Finished");
    14.             Task.current.Succeed ();
    15.         }
    16.     }
    17.