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
    I've check your project, and indeed the BT scripts are ticked in the Editor. Some of your scripts are ticked on LateUpdate, which is apparently also called in the Editor. That's not specified in the [ExecuteInEditMode] documentation, only Update is mentioned there. I will fix this by also placing a tick guard in LateUpdate, as well as in FixedUpdate (just in case).

    I understand you want to reuse code, that's a good thing (less is more). But how do you reuse a [task] that access Task.current.Succeed() or Fail(). Since Task.current is only available within BT script context, it makes the method useless when used in isolation.

    If there are code to share between the BT scripts and another system, I would instead place this shared code inside a library, then both the [task] methods and the other system would make calls to this same library.
     
  2. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I skip all Task.current when it's not called from panda. It's not super elegant but it's clean enough and it works in the context of input loop calling functions.
    I could do libraries yes but then I need to think harder than just skipping what is null, and thinking hard I don't like doing.
     
  3. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Note that Task.current is the same for MoveTowardsTarget and RealtimeWait. RealtimeWait calls Task.current.Succeed() after 0.1 seconds. Therefore, MoveTowardsTarget will succeeds after 0.1 seconds even if the agent did not reach the target. When MoveTowardsTarget succeeds, everything is done and the top sequence succeeds as well.

    I'm not sure why you have the RealtimeWait within the MoveTowardsTarget task. Is it to somehow slow down the movement of the agent? What do you want to achieve?
     
  4. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    So Wait/RealtimeWait will cause the method that they are located it to succeed? That explains it. Thanks for clearing it up. I had placed it inside the method because I was trying to cut down on the clutter in my behavior tree, but it look's like I won't be able to use it.
     
  5. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    When trying Panda, I saw that it generated some garbage, so I've just bought the pro version in order to fix it.

    First, there's some garbage generated in order to display the debug information, here is an example:
    Code (CSharp):
    1. public void Wait(float duration)
    2. {
    3.     [...]
    4.  
    5.     if (Task.isInspected)
    6.     {
    7.         task.debugInfo = string.Format("t-{0:0.000}", tta);     // Generates garbage
    8.     }
    9.  
    10.     [...]
    11. }
    12.  
    Unfortunately I cannot share my way of fixing it as I'm using a quite extensive replacement of the StringBuilder class in order to have concatenations and numeric to string transformation garbage-free.
    But, in that particular case, it is probably possible to use a simpler version as it always has the same length.


    Now the serious things, there's garbage generated in 3 cases:
    - executing a task with a parameter and a return value
    - executing a task that is a property
    - executing a task that is a field

    Here is the fix for the property:
    Code (CSharp):
    1. static bool Bind( BTTask task, TaskImplementation implementation)
    2. {
    3.     [...]
    4.  
    5.     if (property != null)
    6.     {
    7.         System.Func<bool> TheActionToExecute = (System.Func<bool>)System.Delegate.CreateDelegate(typeof(System.Func<bool>), taskImplementer, property.GetGetMethod(true));
    8.         taskAction = () => Task.current.Complete(TheActionToExecute());
    9.  
    10. //        taskAction = () => Task.current.Complete((bool)property.GetValue(taskImplementer, null));
    11.     }
    12.  
    13.     [...]
    14. }
    15.  

    I also have a fix for the field, but unfortunately it cannot work for mobiles as it involves generating methods at runtime.
    Anyway, it's just a matter of transforming the fields into properties, which is possible in most cases.


    For the method problem, it's the same solution than for the property (after all, a property is a method), but it's more tricky because the signature of the method is not fixed.
    So, it takes some efforts in order to cast the method correctly.
    For now I have just done it for the basic methods, which takes only one parameter of type 'int', 'float' or 'string', and it works, here is the fix:
    Code (CSharp):
    1. static bool Bind( BTTask task, TaskImplementation implementation)
    2. {
    3.     [...]
    4.  
    5.     if (method.ReturnType == typeof(bool))
    6.     {
    7.         if (parameters.Length > 0)
    8.         {
    9.             if ((1 == parameters.Length) && (typeof(int) == parameters[0].GetType()))
    10.             {
    11.                 System.Func<int, bool> TheActionToExecute = (System.Func<int, bool>)System.Delegate.CreateDelegate(typeof(System.Func<int, bool>), taskImplementer, method);
    12.                 taskAction = () => Task.current.Complete(TheActionToExecute((int)parameters[0]));
    13.             }
    14.             else if ((1 == parameters.Length) && (typeof(float) == parameters[0].GetType()))
    15.             {
    16.                 System.Func<float, bool> TheActionToExecute = (System.Func<float, bool>)System.Delegate.CreateDelegate(typeof(System.Func<float, bool>), taskImplementer, method);
    17.                 taskAction = () => Task.current.Complete(TheActionToExecute((float)parameters[0]));
    18.             }
    19.             else if ((1 == parameters.Length) && (typeof(string) == parameters[0].GetType()))
    20.             {
    21.                 System.Func<string, bool> TheActionToExecute = (System.Func<string, bool>)System.Delegate.CreateDelegate(typeof(System.Func<string, bool>), taskImplementer, method);
    22.                 taskAction = () => Task.current.Complete(TheActionToExecute((string)parameters[0]));
    23.             }
    24.             else
    25.             {
    26.                 taskAction = () => Task.current.Complete((bool)method.Invoke(taskImplementer, parameters));
    27.             }
    28.         }
    29.     }
    30.  
    31.     [...]
    32. }
    33.  


    And to finish with, I'd like to say that you did a good job with this asset.
    I needed a BT that could access my own data and with the code source so that I could optimize it if necessary.
    And it's very easy to enter in your code (I've fixed these issues in less than 3 hours, including reading the documentation and playing with the examples).

    I still have a few questions:
    - is it possible to run PandaBT on several threads simultaneously?
    - how does it scale with a lot of nodes (I saw in the profiler that there are a lot of nested calls)?
    - how are the performances with a lot of BT?
     
    laurentlavigne likes this.
  6. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    OK, after a small battle, C# lost and I finally managed to have garbage-free methods, whatever the parameters (well, not exactly, it works up to 9 parameters, but it's easy to improve it).

    The idea was to transform your 'parameters' (of type object[]) into a typed list of parameters.
    As I'm used to use generics everywhere, I used them again, here is the result:
    Code (CSharp):
    1. static bool Bind( BTTask task, TaskImplementation implementation)
    2. {
    3.     [...]
    4.  
    5.     if (method.ReturnType == typeof(bool))
    6.     {
    7.         taskAction = GetTaskAction(taskImplementer, method, parameters);
    8.     }
    9.  
    10.     [...]
    11. }
    12.  
    13. public static System.Action GetTaskAction(object taskImplementer, MethodInfo method, object[] parameters)
    14. {
    15.     if (0 == parameters.Length)         // Basic case
    16.         return () => Task.current.Complete((System.Delegate.CreateDelegate(typeof(System.Func<bool>), taskImplementer, method) as System.Func<bool>)());
    17.     else if (parameters.Length > 9)     // Unsupported cases
    18.         return () => Task.current.Complete((bool)method.Invoke(taskImplementer, parameters));
    19.  
    20.     // Create the parameters arrays
    21.     System.Type[] AllTypes = new System.Type[parameters.Length];
    22.     object[] AllParameters = new object[parameters.Length + 2];
    23.     AllParameters[0] = taskImplementer;
    24.     AllParameters[1] = method;
    25.     for (int i=0; i<AllTypes.Length; i++)
    26.     {
    27.         AllTypes[i] = parameters[i].GetType();
    28.         AllParameters[i+2] = parameters[i];
    29.     }
    30.  
    31.     // Find the correct generic method, build it and call it
    32.     MethodInfo[] AllMethods = typeof(BTRuntimeBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static);
    33.     for (int i=0; i<AllMethods.Length; i++)
    34.     {
    35.         MethodInfo CurrentMethod = AllMethods[i];
    36.         if (!CurrentMethod.IsGenericMethod)
    37.             continue;
    38.  
    39.         if ("GetTaskAction" != CurrentMethod.Name)
    40.             continue;
    41.  
    42.         ParameterInfo[] ParametersOfCurrentMethod = CurrentMethod.GetParameters();
    43.         if (AllParameters.Length != ParametersOfCurrentMethod.Length)
    44.             continue;
    45.  
    46.         // We found the correct generic method, we can now build it
    47.         MethodInfo TheGenericMethod = CurrentMethod.MakeGenericMethod(AllTypes);
    48.  
    49.         // Now we can call the generic method that will get the action task
    50.         return (System.Action)TheGenericMethod.Invoke(null, AllParameters);
    51.     }
    52.  
    53.     // Should never happen
    54.     UnityEngine.Debug.LogError("Impossible to get the action task for '" + method.Name + "'");
    55.     return null;
    56. }
    57.  
    58. // Adds the missing declaration in this old version of Mono
    59. public delegate Tret Func<T1, T2, T3, T4, T5, Tret>(T1 arg1,  T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    60. public delegate Tret Func<T1, T2, T3, T4, T5, T6, Tret>(T1 arg1,  T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    61. public delegate Tret Func<T1, T2, T3, T4, T5, T6, T7, Tret>(T1 arg1,  T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    62. public delegate Tret Func<T1, T2, T3, T4, T5, T6, T7, T8, Tret>(T1 arg1,  T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    63. public delegate Tret Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, Tret>(T1 arg1,  T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9);
    64.  
    65. // It's best to generate these methods automatically as they are all the same except for the number of parameters
    66. private static System.Action GetTaskAction<T1>(object taskImplementer, MethodInfo method, T1 Param1)
    67. {
    68.     return () => Task.current.Complete(((System.Func<T1, bool>)(System.Func<T1, bool>)System.Delegate.CreateDelegate(typeof(System.Func<T1, bool>), taskImplementer, method))(Param1));
    69. }
    70.  
    71. private static System.Action GetTaskAction<T1, T2>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2)
    72. {
    73.     return () => Task.current.Complete(((System.Func<T1, T2, bool>)(System.Func<T1, T2, bool>)System.Delegate.CreateDelegate(typeof(System.Func<T1, T2, bool>), taskImplementer, method))(Param1, Param2));
    74. }
    75.  
    76. private static System.Action GetTaskAction<T1, T2, T3>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3)
    77. {
    78.     return () => Task.current.Complete(((System.Func<T1, T2, T3, bool>)(System.Func<T1, T2, T3, bool>)System.Delegate.CreateDelegate(typeof(System.Func<T1, T2, T3, bool>), taskImplementer, method))(Param1, Param2, Param3));
    79. }
    80.  
    81. private static System.Action GetTaskAction<T1, T2, T3, T4>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4)
    82. {
    83.     return () => Task.current.Complete(((System.Func<T1, T2, T3, T4, bool>)(System.Func<T1, T2, T3, T4, bool>)System.Delegate.CreateDelegate(typeof(System.Func<T1, T2, T3, T4, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4));
    84. }
    85.  
    86. private static System.Action GetTaskAction<T1, T2, T3, T4, T5>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4, T5 Param5)
    87. {
    88.     return () => Task.current.Complete(((Func<T1, T2, T3, T4, T5, bool>)(Func<T1, T2, T3, T4, T5, bool>)System.Delegate.CreateDelegate(typeof(Func<T1, T2, T3, T4, T5, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4, Param5));
    89. }
    90.  
    91. private static System.Action GetTaskAction<T1, T2, T3, T4, T5, T6>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4, T5 Param5, T6 Param6)
    92. {
    93.     return () => Task.current.Complete(((Func<T1, T2, T3, T4, T5, T6, bool>)(Func<T1, T2, T3, T4, T5, T6, bool>)System.Delegate.CreateDelegate(typeof(Func<T1, T2, T3, T4, T5, T6, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4, Param5, Param6));
    94. }
    95.  
    96. private static System.Action GetTaskAction<T1, T2, T3, T4, T5, T6, T7>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4, T5 Param5, T6 Param6, T7 Param7)
    97. {
    98.     return () => Task.current.Complete(((Func<T1, T2, T3, T4, T5, T6, T7, bool>)(Func<T1, T2, T3, T4, T5, T6, T7, bool>)System.Delegate.CreateDelegate(typeof(Func<T1, T2, T3, T4, T5, T6, T7, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4, Param5, Param6, Param7));
    99. }
    100.  
    101. private static System.Action GetTaskAction<T1, T2, T3, T4, T5, T6, T7, T8>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4, T5 Param5, T6 Param6, T7 Param7, T8 Param8)
    102. {
    103.     return () => Task.current.Complete(((Func<T1, T2, T3, T4, T5, T6, T7, T8, bool>)(Func<T1, T2, T3, T4, T5, T6, T7, T8, bool>)System.Delegate.CreateDelegate(typeof(Func<T1, T2, T3, T4, T5, T6, T7, T8, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8));
    104. }
    105.  
    106. private static System.Action GetTaskAction<T1, T2, T3, T4, T5, T6, T7, T8, T9>(object taskImplementer, MethodInfo method, T1 Param1, T2 Param2, T3 Param3, T4 Param4, T5 Param5, T6 Param6, T7 Param7, T8 Param8, T9 Param9)
    107. {
    108.     return () => Task.current.Complete(((Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, bool>)(Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, bool>)System.Delegate.CreateDelegate(typeof(Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, bool>), taskImplementer, method))(Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9));
    109. }
    Note that this is only garbage-free for 0 to 9 parameters (included).
    Over that it reverts to your code, which means that it will work in all cases (which is the most important thing after all).

    Also, as I have just tried Panda for half a day, I just tried your examples. They seems to work fine (except the shooter which has some trouble with bullets collisions, but I think it's unrelated to BT).
    So, it would be a good idea to try my modification a bit more seriously, even if I'm confident they should work fine.


    On a general note, using the type 'object' often leads to garbage collection because of boxing/unboxing.
    That's why it's usually better to use generics.
    Unfortunately, it's sometimes not very easy, and sometimes it is needed to have one generic method for each number of parameters you need (as in this case).
    This problem fades away because it is possible (and advised) to create a small program that generates the generic methods automatically, it's just a matter of generating 50 methods, for 50 parameters.
    And if someone, some day, uses more than that, then it will cost him a little garbage, but if he's using more than 50 parameters for one method I doubt that he even knows what garbage is ;)
     
    laurentlavigne likes this.
  7. ericbegue

    ericbegue

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

    The GC allocation due to string.Format happens only when the Inspector is displaying the running BT script. That's what the "if (Task.isInspected)" line is for. When the inspector is closed, or completly absent (as it is in a build for instance), there are no GC allocations after the BT script initialization.

    I am curious though, how do you avoid GC alloc with StringBuilder and string concatenations. Is there a way to allocate a fixed length string once, then reuse for doing formatting?

    There are of course GC allocation when the scripts are compiled and build. A lot of stuff is going there. Optimizations are welcome here, but they have to be significant; I always try to balance optimization with code readability. How much memory is saved with your optimization?

    Unity is not thread safe. What would be the use cases for having multi-threaded behaviour trees running concurrently?

    The running overhead should be insignificant. Most of the heavy lifting work are done by the tasks, not by the framework. But those are really good questions. Maybe I should create some stress test examples, so we can have some numbers.
     
    laurentlavigne likes this.
  8. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    I have found some garbage generated after the initialization (seen in your shooter example, and I'm not speaking about the garbage generated by the task, but by Panda core), as stated in my first post:
    - executing a task with a parameter and a return value
    - executing a task that is a property
    - executing a task that is a field
    These 3 cases generates 17 bytes of GC each (using Unity 2017-1 on a 64 bits Windows 7).
    I have provided a fix for the first 2, and the third one isn't fixable on mobile, only on PC, but anybody can use properties instead, so it's not a problem.


    In short, StringBuilder have 2 problems, they generate garbage in two places:
    - when transforming an integer into a string (by concatenation or formatting)
    - when the 'ToString()' method is called

    For the first problem, it's just a matter of transforming the integer, float, date, etc. manually. Of course, I say 'just', but it's a lot of code, and a lot of bugs before it works well... especially the date...

    The second problem is trickier, you need to access the underlying code of StringBuilder.cs, in order to get the string used internally, and return it.
    But this string will not have the correct length because StringBuilder uses a larger than necessary string internally in order to avoid allocating too often, so you need to place a '\0' at the correct position.
    And even with that, sometimes it doens't work well, so you also need to access the inner String.cs structure in order to 'cheat' and make it believe it is actually shorter than it really is.

    All in all, when you've done that, you have string concatenation and float/int/date to string formatting totally garbage-free.
    But in your case, as you have fixed-size displays for the time, it's much easier, you can use a small part of all the things I described.


    And as you said, it is for debug only, so the optimization isn't crucial there, it's just that I've spent a lot of time fighting garbage, and it's now a spinal reflex, when I see garbage I eliminate it.


    It's the 17 bytes of garbage generated when calling some tasks that are bothering me.
    It can grow up fast, and it's additive, which means that the GC will be called more often.
    The garbage generated by the debugging display isn't a problem as it won't be in the releases.


    But C# is thread-safe, if the data and tasks used aren't using anything from Unity, then it would be possible to run several BT simultaneously provided they do not use shared data.
    Of course it would be a little tricky, but to compute the behaviour of a horde of enemies it could be useful.
    Each enemy could use the shared data of the world, but in a read-only way, so no multi-threading problem, and their BT would change their internal states.
    After a few hundred enemies have been processed, all the internal states can be put back into Unity in the main thread.

    That's also a good way of decoupling the game from Unity:
    Each frame:
    - the relevant data is taken from Unity using the main thread, and put into pure C# objects
    - the pure C# objects are processed in parallel if possible
    - the modified and relevant data are put back in Unity (positions, rotations, destructions, creations, etc.) using the main thread

    I read somewhere that PandaBT could be decoupled from Unity, so that means you've restricted the accesses to Unity in a few classes.
    That means it could be possible to do some multithreading, except for the 'Task.Current', I just saw that and it bother me a little.
    I understand how useful it is, and how it allows tasks to be even more simple. In order to avoid it I presume tasks would have to have a parameter dedicated to receive the current task, which wouldn't be very readable, and would add a lot of code mostly unused.
    I'll think about it to see if I can come up with something.


    I'm very interested in stress tests.
    In fact, it's one of the first thing I wanted to do after having written a few BT to familiarize myself with it.
    Behavior Designer indicates they can run 4000 BT in about 1.4ms, that gives you a reference.

    The thing is, I'm pretty sure that the gameObjects will be the limiting factor.
    In order to check BT performances properly, it should be applied on non-gameObject, but on standard C# objects, with minimal overhead.

    Anyway, I know the performances cannot be miraculous, as the tasks are using their own data, which means the cache misses will be numerous as soon as we're out of the reach of Panda and in game code.
    But from what I saw of PandaBT code, I think it should be quite good, I hope you can beat Behavior Designer (I presume they used empty tasks), that would give you an edge to add on the AssetStore presentation page.
     
    laurentlavigne likes this.
  9. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    I'm getting a puzzling error in edit mode (although it does not prevent me from playing the simulation, nor does it cause the simulation to pause when running). The error text reads: "NullReferenceException: Object reference not set to an instance of an object UniversalCommands.get_IsAlive () (at Assets/PBTest/UniversalCommands.cs:726)" Before I continue I should probably explain how "characters" ares set up in my project: The is the abstract class AbstractBase,which contains information like movement speed, max health, is the entity alive, jump height, what kind of shot to fire, etc. All the different class for the player, enemy grunts, and bosses inherit from this class. Then there's the UniversalCommands class, and its child, the BossCommands subclass, which handles the actual "thinking" as well as defining the methods that the BT script will use. Here's the code in question:

    Code (CSharp):
    1. //this is what I have in UniversalCommands
    2.     protected AbstractBase baseEntity;
    3.     void Start () {
    4.         baseEntity = GetComponent<AbstractBase> ();
    5.     }
    6. //...
    7.     [Task]
    8.     public bool IsAlive {
    9.         get {
    10.             return baseEntity.Alive;//this is line 726
    11.         }
    12.     }
    13. //then in the class BossCommands I have this excerpt:
    14.     AbstractBoss boss;
    15.      void Start () {
    16.         boss = GetComponent<AbstractBoss> ();
    17.         baseEntity = GetComponent<AbstractBase> ();
    18.     }
    19.  
    After determining the offending game object by a process of elimination, I have made several observations:
    It has a ComplexBoss (the inheritance tree for that class got like this: AbstractBase -> AbstractBoss -> ComplexBoss) script component and a BossCommands component.
    Another gameobject in a different scene also has the same script classes attached to them, but does not cause any problems.
    Changing any field in the inspector will cause another instance of the error to pop up in the console. Selecting something the the same scene and moving it around can fill the console with dozens of instances the error.

    The full text of the error is as follows:
    Code (CSharp):
    1. NullReferenceException: Object reference not set to an instance of an object
    2. UniversalCommands.get_IsAlive () (at Assets/PBTest/UniversalCommands.cs:726)
    3. System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)
    4. Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
    5. System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:232)
    6. System.Reflection.MonoProperty.GetValue (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] index, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoProperty.cs:297)
    7. System.Reflection.MonoProperty.GetValue (System.Object obj, System.Object[] index) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoProperty.cs:283)
    8. Panda.BTRuntimeBuilder+<Bind>c__AnonStorey0.<>m__6 ()
    9. Panda.BTTask.DoTick ()
    10. Panda.BTNode.Tick ()
    11. Panda.BTWhile.DoTick ()
    12. Panda.BTNode.Tick ()
    13. Panda.BTFallback.DoTick ()
    14. Panda.BTNode.Tick ()
    15. Panda.BTTree.DoTick ()
    16. Panda.BTNode.Tick ()
    17. Panda.BTProgram.Tick ()
    18. Panda.BehaviourTree.Tick ()
    19. Panda.BehaviourTree.LateUpdate ()
    It's not a critical error, but I fear that it'll cause problems later on.
     
  10. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    I'd say that's because 'Start()' isn't called in the editor (monobehaviour are usually inactive in the editor), but the BT is probably executed, at least in part, in the editor.
    I haven't tried it, but adding 'ExecuteInEditMode' may help:
    https://docs.unity3d.com/ScriptReference/ExecuteInEditMode.html

    There's a way to be sure, add a log at the start of the 'Start()' and 'IsAlive()' methods.
     
    laurentlavigne likes this.
  11. Burglebutt

    Burglebutt

    Joined:
    Jul 24, 2014
    Posts:
    51
    Hi I am thinking of buying the pro version but I have a question for you. Would I be able to use this for a turn based game? Thank you
     
  12. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @Gladyon
    Thank you for reporting this. I have not spoted this before, I guess it's because I've run my benchmark only with [task] methods and not with fields nor properties. Good catch!

    It's annoying that there's no way to get the value of an object without unboxing it first. So it seems the solution is to generate all method signatures with all combinations of types and number of parameters... that's seems to be a bit brutal. I'm not sure if that's the best solution yet I don't know any better. I will research on this problem with the hope that there is a better alternative. You seems to be well experienced about optimizing for GC alloc, let me know if you found something on your side or if you've already been down that path.
     
    laurentlavigne likes this.
  13. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    I have implemented the fixes I've posted, it's true that having 10 different methods in order to match the number of parameters isn't very pretty, but as I generate that code I don't really care.
    But it's true that on mobile it may take some memory, but honestly, we're speaking about a few methods probably less than 100, and most surely less than 1000. So, even if each method 100 octets (which I doubt as they are very small), that's only 100Ko of memory, which shouldn't be a problem even on mobiles.

    About the fields, I have implemented something to remove the garbage, but it involves creating a DynamicMethod at runtime in order to create the corresponding property.
    Unfortunately generating IL code isn't uspported on mobiles, so it wouldn't be a good idea to put that in an asset.
    I don't think there's another way, except of course generating C# code in the pre-build, and telling Panda to use the properties generated in pre-build.
    That adds a step in the build process, but it should be possible to automate it.
    That way, Panda would never use a field, but only methods and properties, which can both be made garbage-less.
     
    laurentlavigne likes this.
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Hi @lordhamburger
    By default the BT Scripts are ticked on Update. But you can tick the script whenever you want. So, you can implement logics that span over several turns. For instance, you can easily implement a unit that uses a hit and run tactic.
     
  15. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    It tried it, it gets rid of the error, but the AI scripts executes when ever I change a game object's value, such as its position or a public script value in the inspector. This means that every time I try to move or rotate something, the objects with the ComplexBoss script attached will move a bit towards the player (because that's what the bt behavior is set to do).
     
  16. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    Yes, that's a problem.
    That means that in your case you explicitely do not want to execute the BT in the editor.

    Panda seems to be designed to be executed in edit mode (there's the 'ExecuteInEditMode' attribute in the 'BehaviourTree' class).


    What you can do, is to add a test in your Tasks (or at least some of them) to check if you're in edit mode, and to do nothing if your are.
    So, you should remove the 'ExecuteInEditMode' attribute in your own code, and add that code to the tasks that need it:
    Code (CSharp):
    1. if (Application.isEditor)
    2. {
    3.     return;     // Or 'return false;' or anything else you need to do in your specific case
    4. }
    5.  
    That way, your boss shouldn't move. in the editor, unless you move it yourself of course.
     
    laurentlavigne likes this.
  17. BTCallahan

    BTCallahan

    Joined:
    Jun 6, 2015
    Posts:
    71
    I tried that, but due to the way my code is set up, whenever I hit play all characters that inherited from AbstractBase would end dieing immediately. However I used a slight modified version of your code to fix the problem, so thank you.
    Code (CSharp):
    1. return baseEntity == null ? false : baseEntity.Alive;
     
  18. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @BTCallahan and @Gladyon
    The BT scripts are not supposed to be ticked in the editor while not in play mode. That's a bug. It will be fixed in the next release. If you don't want to wait until it is published on the Asset Store, pm me and I will send you a patched version of the package with the fix.
     
  19. penny_debyl

    penny_debyl

    Joined:
    May 5, 2016
    Posts:
    1
    Hi Eric, I think I've tried every single BT package that was free in the Asset Store and your's is by far the best and easiest to implement. I'm an educator and like providing plenty of demos and workshops for students with free assets to get them started. You can find my stuff at holistic3d.com/udemy and youtube.com/c/holistic3d. I'm currently working on an AI course for Udemy to come out next week and I was hoping to get your permission to include the free version of Panda BT with the project files I give out.
     
    laurentlavigne likes this.
  20. ericbegue

    ericbegue

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

    Thank you for your interest in Panda BT!

    I would like to give you the permission to do so, but I think distributing the asset via any channel other than the Asset Store goes against the Unity's EULA. It would be safer not to include the package with your project files. But I will send a request to Unity to clarify whether this is possible or not.
     
  21. OnyDeus

    OnyDeus

    Joined:
    Aug 5, 2014
    Posts:
    1
    Hi ericbegue, Thank you for creating Panda BT. It's great; I understand the waterfall nature of the scripting language better than node based solutions. I have two things I want to clarify before I leave a review. 1st is the inclusion of Enum types in the latest release. There is no documentation on how these work for free version folks, nor did I see much in this forum. I'm hoping for cleaner state 'while' checks than bools. The 2nd is the ability to access variables to use for my method calls. For example I have several SetGameObject_Active methods for a Sequence that toggles things around, or several waitForDynamicVar methods. I feel like I have to make method variants regularly. Would a [Variable] tag be something that can be implemented? (with bool types fulfilling both [task] as well) I don't care for blackboards as a separate variable pool but I feel like I'm missing something? Either way I am still enjoying Panda; thank you in advance for your response!
     
    Last edited: Oct 16, 2017
  22. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @OnyDeus
    To use enums from BT scripts, just pass the enum value prefixed with the enum type name. For instance, if you have the following enum:
    Code (CSharp):
    1. enum Stamina{
    2.     Low,
    3.     Medium
    4.     High
    5. }
    and a [Task] method to set the stamina:
    Code (CSharp):
    1. [Task]
    2. void SetStamina(Stamina value){
    3.     this.stamina = value;
    4.     Task.current.Succeed();
    5. }
    Then you use the enum value in a BT script, like this:
    Code (CSharp):
    1. tree "Rest"
    2.     sequence
    3.         Wait(15.0)
    4.         setStamina( Stamina.High )
    5.        
    If the enum is within a namespace, prefix the value with the namespace name as well. Panda needs to know the complete name in order to locate the enum value.


    Adding support for variables in the BT language would complexify the system a lot. The goal of Panda BT is not to be a full fledged programming language, though I would love to see this happen, I currently don't have time and resources to realize that. But don't forget that you can still do a lot with C#. If you want to have a way to create and use dynamic variables from BT script, you could do something like this:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. using Panda;
    6. public class BTVars : MonoBehaviour {
    7.  
    8.     Dictionary<string, float> vars = new Dictionary<string, float>();
    9.  
    10.     [Task]
    11.     void SetVar(string name, float value)
    12.     {
    13.         vars[name] = value;
    14.         Task.current.Succeed();
    15.     }
    16.  
    17.     [Task]
    18.     void IncreaseVar(string name, float step)
    19.     {
    20.         vars[name] += step;
    21.         Task.current.Succeed();
    22.     }
    23.  
    24.     [Task]
    25.     void DecreaseVar(string name, float step)
    26.     {
    27.         vars[name] -= step;
    28.         Task.current.Succeed();
    29.     }
    30.  
    31.     [Task]
    32.     void isVarGreaterThan(string name, float value)
    33.     {
    34.         Task.current.Complete( vars[name] > value );
    35.     }
    36.  
    37.     [Task]
    38.     void isVarLessThan(string name, float value)
    39.     {
    40.         Task.current.Complete(vars[name] < value);
    41.     }
    42.  
    43.     [Task]
    44.     void isVarBetween(string name, float min, float max)
    45.     {
    46.         var v = vars[name];
    47.         Task.current.Complete( min < v && v < max);
    48.     }
    49.  
    50. }
    51.  
    You can adapt script to implement a blackboard system by declaring the "vars" static, so that it can be shared over different BT agents.

    Hope that helps.
     
    Last edited: Oct 28, 2017
    BitsAtPlayDev and laurentlavigne like this.
  23. galaxymeow

    galaxymeow

    Joined:
    Jul 5, 2017
    Posts:
    1
    Hi

    Can you advise on setting the BT script(s) on a behaviour component at runtime? Just once during my init code; not intending to change it after that.

    Compile is functionally fine (as below), but in an ideal world I would like to pass it a reference to a BT script (text asset) and see that in the editor window as normal while in play mode. When compiling from the raw text, it obviously just shows something like "(compiled)" in the editor window during play mode.

    Code (CSharp):
    1. behaviour.Compile(myTextAsset.text);
     
  24. ericbegue

    ericbegue

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

    If you want to link the textAssets at run time, you need to assign them to the "scripts" property (it's a array of TextAssets) of the PandaBehaviour component, then call the Apply() method to apply the changes, as follow:

    Code (CSharp):
    1. var pandaBehaviour = this.gameObject.AddComponent<PandaBehaviour>(); // or GetComponent
    2. pandaBehaviour.scripts = textAssets;
    3. pandaBehaviour.Apply();
    You should see the textAssets on the PandaBehaviour component in the Inspector exactly as you would have done it manually.
     
    laurentlavigne likes this.
  25. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
  26. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    Thanks for the video. It's a nice intro to AI planning. I was really curious about the language they created to define the actions, but this part was not shown here.
    I'm getting more interested in GOAP, and wondering whether it could be a layer on top of BT or has to be a complete distinct system.
     
  27. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Woohoo Eric is considering a GOAP layer, it must Christmas :D
    You can extend the BT language to include goals and preconditions, results and actions, what's inside is the same old panda goodness.
    A GOAPBT script could look like this:
    Code (CSharp):
    1. tree("Root")
    2.     //Root behaviour for all unit: Be alive or die.
    3.     GOAP
    4.         goals
    5.             IsAlive (true)
    6.             InventoryFull (ITEM.WOODMILL)
    7.         actions
    8.             // survival
    9.             GOAPtree ("flee")
    10.             GOAPtree ("defend self")
    11.             GOAPtree ("get weapon")
    12.             GOAPtree ("walk to weapon")
    13.             // gathering
    14.             GOAPtree ("gather wood")
    15.             GOAPtree ("get axe")
    16.             GOAPtree ("add wood to wood mill")
    17.             GOAPtree ("walk to tree")
    18.             GOAPtree ("drive to tree")
    19.             GOAPtree ("walk to fuel")
    20.             GOAPtree ("gather fuel")
    21.             GOAPtree ("walk to car")
    22.  
    23. GOAPtree ("walk to tree")
    24.     cost
    25.         20 * DistanceTo(ITEM.TREE) + InventoryWeight * 0.2
    26.     precondition
    27.         IsWearing (ITEM.SHOES)
    28.     result
    29.         IsAt(ITEM.TREE)
    30.     action
    31.         sequence
    32.             SetDestinationToNearby (ITEM.TREE)
    33.             GotoDestination
    34.  
    35. GOAPtree ("drive to tree")
    36.     cost
    37.         20 * DistanceTo(ITEM.TREE) + InventoryWeight * 0.2
    38.     precondition
    39.         sequence
    40.             VehicleNearby (VEHICLE.CAR)
    41.             NearbyVehicleHasFuel
    42.     result
    43.         IsAt(ITEM.TREE) true
    44.     action
    45.         sequence
    46.             BoardNearby (VEHICLE.CAR)
    47.             SetDestinationToNearby (ITEM.TREE)
    48.             GotoDestination
    49.  
    50. GOAPtree ("walk to car")
    51.     cost
    52.         20 * DistanceToClosest (VEHICLE.CAR) + InventoryWeight * 0.2
    53.     precondition
    54.         IsWearing (ITEM.SHOES)
    55.     result
    56.         Has(VEHICLE.CAR) true
    57.     action
    58.         sequence
    59.             SetDestinationToNearby (VEHICLE.CAR)
    60.             GotoDestination
    61.             Get (VEHICLE.CAR)
    62.  
    63. GOAPtree ("refuel")
    64.     cost
    65.         0.1
    66.     precondition
    67.         Has(VEHICLE.CAR)
    68.         InventoryContains(RESOURCE.FUEL)
    69.     result
    70.         VehicleHasFuel true
    71.     action
    72.         FuelUpNearbyVehicle
    73.  
    74. GOAPtree ("walk to fuel")
    75.     cost
    76.         20 * DistanceTo(RESOURCE.FUEL) + InventoryWeight * 0.2
    77.     precondition
    78.         IsWearing (ITEM.SHOES)
    79.     result
    80.         IsAt(RESOURCE.FUEL) true
    81.     action
    82.         sequence
    83.             SetDestinationToNearby (RESOURCE.FUEL)
    84.             GotoDestination
    85.  
    86. GOAPtree ("gather fuel")
    87.     cost
    88.         0.2
    89.     precondition
    90.         sequence
    91.             IsAt (RESOURCE.FUEL)
    92.             HasSpaceInInventory
    93.     result
    94.         InventoryContains(RESOURCE.FUEL) true
    95.     action
    96.         Grab(RESOURCE.FUEL)
    97.      
    98.  
    99. GOAPtree ("gather wood")
    100.     cost
    101.         1.5
    102.     precondition
    103.         sequence
    104.             HasToolInHand
    105.             IsWearingProperAttire
    106.             IsAt(ITEM.TREE)
    107.             HasSpaceInInventory
    108.     result
    109.         InventoryContains(RESOURCE.WOOD) true
    110.     action
    111.         sequence
    112.             Attack (ITEM.TREE)
    113.             Grab(RESOURCE.LOG)
    114. etc...
    115.  
    this bit
    Code (CSharp):
    1.     result
    2.         InventoryContains(RESOURCE.WOOD) true
    Is not very Panda, the idea is that conditions turn true or false as a result for the planer to build a graph
     
    elias_t likes this.
  28. ericbegue

    ericbegue

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

    I think it would be best to have the GOAP layer separated from the BT layer. What I am thinking of, is to have the GOAP action definitions also in simple text files, but not in BT scripts. Yet the action would be implemented as BT tree. That is the GOAP layer would plan the actions given the actions definition and the current state of the world, then the actual actions will be executed with BT trees.

    The definition of the actions should consist of the following part.
    * prerequisites: the conditions required to execute the actions, expressed as a logical expression
    * cost: a value, or an expression computing the cost of the action
    * action: the action itself, which tree should be run for performing the action
    * outcome: the state of the world after the actions has been executed, with could be expressed as several assignment expressions.

    Since the prerequisite, cost and outcome can be complex expressions, I am considering to delegate this part to a 3rd party scripting language, maybe lua.

    Also, I need to figure out how tho handle the variables describing the world state. These variables have to match the game state when initiating the planning, then they would get updated during the planning, as a small simulation to compute the best action sequence to realize the goal.
     
    Last edited: Nov 20, 2017
    elias_t likes this.
  29. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Give me a example.
     
  30. EvandroLins

    EvandroLins

    Joined:
    Jul 8, 2012
    Posts:
    5
    Hi @ericbegue ,

    I'm testing the PandaBT free, and I have some questions:

    tree("AvoidPlayer")
    while not IsIt
    sequence
    IsPlayerNear [return void, but use Task.current.Complete]
    SetDestination_Random [return bool]
    IsDirectionSafe [return bool]
    MoveToDestination [return void, Task.current.Complete]

    I understand that the sequence goes from first to last child, and by the moment any child fails the sequence will be interrupted. but why some tasks return a bool, while others use Task.Complete?

    Thanks!
     
  31. ericbegue

    ericbegue

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

    In a MonoBehaviour, a task implementation can be one of the following symbols tagged with the [Task] attribute:
    - void method
    - bool method
    - bool property
    - bool variable

    When implementing a task with a void method, to complete the task, it is required to call either:
    - Task.current.Succeed()
    - Task.current.Fail()
    - Task.current.Complete( someBoolean )
    (The task is kept running until one of the above is called).

    Whereas with a boolean (method, variable or property), the task completes immediately. That is, it completes the first time it has been ticked; it is never running.

    Boolean tasks are a short way to write tasks that are not required to run over several frames. You could also, write these type of tasks using void method and calling Task.current.*, the BT script would work the same. It just a way to avoid too much typing.
    For instance:
    Code (CSharp):
    1. [Task]
    2. void IsEnemyVisible()
    3. {
    4.     if( someCondition )
    5.         Task.current.Succeed();
    6.     else
    7.         Task.current.Fail();
    8. }
    Is the same as:
    Code (CSharp):
    1. [Task]
    2. bool IsEnemyVisible;
     
  32. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Hi, I downloaded Panda BT tonight and read through the docs. I could not find a Youtube video on starting a project from scratch and implementing Panda, but there was a Woodcutter which explained enough.

    I am stuck on something probably simple. The Component Panda Behavior script is not seeing my AI script?
    1. I created a empty GameObject in the hierarchy.
    2. I added the component Panda Behavior (script)
    3. Im my project folder I created ThePanda BT script and called it 'AI.BT'
    4. I created a C# script and added it to the same GameObject as the Panda Behavior is on with this code.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Panda;
    5.  
    6. public class AI_Script : MonoBehaviour {
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.        
    11.     }
    12.  
    13.     [Task]
    14.     public void helloWorld()
    15.     {
    16.         print("I am Alive!");
    17.         Task.current.Succeed();
    18.     }
    19. }
    But now in the Inspector, how do I add the helloWorld() method to the Panda Behavior script?

    Thanks!
     

    Attached Files:

  33. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    I think I figured it out. I need to manually enter the stuff into the BT.txt file. It showed up and worked.
     
  34. ericbegue

    ericbegue

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

    Yes, that's how the task is supposed to be used from the BT script: just type the task name (and the parameters when applicable). The system will then search for the task implementation on any MonoBehaviours attached on the game object, looking for the [Task] attribute.

    For information, the pro edition comes with an in-Inspector drag&drop style Editor, where the available tasks are listed in a menu or a combo-box when inserting new nodes into the tree. It allows to edit and tweak the BT directly from the Inspector.
     
    Last edited: Nov 28, 2017
  35. mtdev01

    mtdev01

    Joined:
    May 17, 2017
    Posts:
    10
    Implemented PandaBT and love it. However, I have hit a snag and have a question. I have multiple NPCs that could have different jobs. So my thought was to implement a tree like so:
    Code (CSharp):
    1. ...
    2. tree("employed")
    3.     while not isUnEmployed
    4.         while not isWorking
    5.             fallback
    6.                 tree("work")
    7.  
    8. tree("work")
    9.     sequence
    10.         GoToWork
    11.         ArriveAtDestination
    12.         GetTask
    13.         GoToTask
    14.         ArriveAtDestination
    15.         DoWork
    16. ...
    And then have different scripts for different jobs (lumberjack, miner, etc.). Those scripts each have a task defined for
    GoToWork
    ArriveAtDestination
    GetTask
    GoToTask
    ArriveAtDestination
    DoWork
    The problem is that Panda complains that the Tasks are defined in multiple scripts. I tried removing the script component and swapping it out for the job script that fits the NPC's task, however, that fails as well (null reference exception).

    What is the "Panda way" to achieve this?

    Thank you
     
  36. ericbegue

    ericbegue

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

    Do you mean the same agent can change its job at runtime, and each job type has its own implementation of tasks in different MonoBehaviours?

    The problem is the PandaBehaviour check the components attached to the gameObject for tasks when they are added or removed. So if there are tasks missing or duplicated, it will raise an exception.

    The safest way to swap MonoBehaviours at runtime, would be to unlink the BT scripts from the PandaBehaviour component and then swap the MonoBehaviours implementing the job tasks, before recompiling the scripts:

    Code (CSharp):
    1. var scripts = pandaBehaviour.scripts;
    2. // Unlink the BT scripts by compiling a dummy tree
    3. pandaBehaviour.scripts = null
    4. pandaBehaviour.Compile("tree(\"root\")\n\tSucceed\n");
    5.  
    6. // Remove the MonoBehaviours implementing the old job
    7. // Add the MonoBehaviours implementing the new  job
    8.  
    9. // Reattach the BT scripts
    10. pandaBehaviour.scripts = scripts;
    11. pandaBehaviour.Apply();
     
    Last edited: Nov 29, 2017
  37. mtdev01

    mtdev01

    Joined:
    May 17, 2017
    Posts:
    10
    That is what I meant and this is probably just what I was looking for. Thank you!
     
  38. ericbegue

    ericbegue

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

    By the way.

    Code (CSharp):
    1. tree("employed")
    2.     while not isUnEmployed
    3.         while not isWorking
    4.             fallback
    5.                 tree("work")
    6.                
    That can also be written with only one while node:
    Code (CSharp):
    1. while
    2.     sequence
    3.         not isUnEmployed
    4.         not isWorking
    5.     tree("work")
    6.                
    The sequence acts like a logical AND. If later on, you'd need a logical OR use a fallback node.
     
    Last edited: Nov 29, 2017
  39. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @laurentlavigne
    Code (CSharp):
    1. GetAxe
    2.     condition: hasAxe = false
    3.     result: hasAxe = true
    4.     cost: 2.0
    5.  
    6. CollectBranches
    7.     condition: hasFireWood = false
    8.     result: hasFireWood = true
    9.     cost: 9.0
    10.  
    11. CutWood
    12.     condition: hasAxe = true
    13.     result: hasFireWood = true
    14.     cost: 1.0
    15.  
    16. MoveToShop
    17.     condition: knowsShopLocation = true
    18.     result: atShopLocation = true
    19.     cost: distanceToShopLocation/speed
    20.  
    21. BuyAxe
    22.     condition: atShopLocation && hasAxe = false && gold >= 50
    23.     result: hasAxe = true; gold = gold - 50;
    24.     cost: 1.0
    25.  
    26. MakeFire
    27.     condition: hasFire = false && hasFireWood
    28.     result: hasFire = true; hasFireWood = false;
    29.     cost: 2.0
    30.  
     
    Last edited: Nov 29, 2017
    elias_t likes this.
  40. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    This is Very clean.
    So this is the txt file in this planning language and GetAxe is also present as a tree ("GetAxe") in the panda txt?

     
  41. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    That's exactly the idea.
     
  42. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I love it.
     
  43. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Hey Eric, I love your behavior tree. It's exactly what I was looking for. There is one issue though; the Random structural node only statically assigns the weights (for example random(1,2,3)). I want it to be able to dynamically assign weights at runtime. For example, depending on the state of the code, the weights for the Random node would be random(var1,var2,var3). Any thoughts on this?
     
  44. zenGarden

    zenGarden

    Joined:
    Mar 30, 2013
    Posts:
    4,538
    @ericbegue

    Goap could be written in a Class instead of text file ? Would it be better ?

    Code (CSharp):
    1.  
    2.  
    3.  
    4. Class BuyAxe{
    5.  
    6. float cost = 1;
    7.  
    8. bool condition(){
    9.  return ( atShopLocation && hasAxe == false && gold >= 50);
    10. }
    11.  
    12. void result(){
    13.  hasAxe = true; gold = gold - 50;
    14. }
    15.  
    16. }
     
    Last edited: Jan 20, 2018
  45. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    He usually answers in a day so he might be busy.
    Panda doesn't have a blackboard so if you need to store variable you create a variable inside your c# code and have a task to set it.
    For example
    Code (CSharp):
    1. float weight;
    2. [Task]
    3. void SetRandomWeight(float w)
    4. {
    5.    weight = w;
    6. }
    7.  
    8. [Task]
    9. void ConsumeRandomWeight()
    10. {
    11.     // do something with 'weight'
    12. }
     
  46. BinaryX

    BinaryX

    Joined:
    Aug 4, 2014
    Posts:
    55
    Hello,

    I love your asset. I've been playing around with it and I love its simplicity.
    There was one thing I couldn't figure out on my own how to do and the documentation and examples don't seem to have this type of thing and I'm guessing it's not possible to do.

    Is it possible to have variables as parameters in the tree so I can control the parameter from the inspector ?

    for example :

    Code (CSharp):
    1. [Task]
    2. public float Cooldown = 0;
    3.  
    4. ----------------------------------------------------
    5.  
    6. tree("Root")
    7.     while IsAlive
    8.         sequence
    9.             EnemyInRange
    10.             AttackEnemy
    11.             Wait Cooldown

    I want to upgrade this character and modify its attack speed ( reduce waiting time ).

    Thank you ^-^
     
  47. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    You can do something like
    Code (CSharp):
    1. [Task]
    2. public float cooldown = 1;
    3. [Task]
    4. public bool WaitForCooldown()
    5. {
    6.             var task = Task.current;
    7.             var info = task.item != null? (WaitFloatInfo)task.item: (WaitFloatInfo)(task.item = new WaitFloatInfo());
    8.  
    9.             if (task.isStarting)
    10.             {
    11.                 info.elapsedTime = -Time.deltaTime;
    12.             }
    13.  
    14.             info.elapsedTime += Time.deltaTime;
    15.            
    16.             if (Task.isInspected)
    17.             {
    18.                 float tta = Mathf.Clamp(cooldown - info.elapsedTime, 0.0f, float.PositiveInfinity);
    19.                 task.debugInfo = string.Format("t-{0:0.000}", tta);
    20.             }
    21.  
    22.             if (info.elapsedTime >= cooldown)
    23.             {
    24.                 task.debugInfo = "t-0.000";
    25.                 task.Succeed();
    26.             }
    27. }
     
    BinaryX likes this.
  48. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    bug: when panda can't bind to a component because that component class name != file name, panda doesn't show the panda script at all
     
  49. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    How can I invoke a tree using a variable? I wish to avoid a sequence of trees with each tree having "while someFlag" at the top. This doesn't work, but conveys the general idea:

    MonoBehaviour:
    Code (CSharp):
    1. public class Entity : MonoBehaviour
    2. {
    3.     string _job;
    4.  
    5.     [Task]
    6.     public string Job
    7.     {
    8.         get { return _job;  }
    9.     }
    10.  
    11.     [Task]
    12.     void Console(string txt)
    13.     {
    14.         Debug.Log(txt);
    15.     }
    16. }
    BT text:

    Code (CSharp):
    1. tree("Root")
    2.     parallel
    3.         repeat mute tree(Job)
    4.  
    5. tree("job1")
    6.     Console("doing job1")
    7.  
    8. tree("job2")
    9.     Console("doing job2")
    10.  
    11. tree("job3")
    12.     Console("doing job3")
     
    laurentlavigne likes this.
  50. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Is there documentation or examples of how PandaBehaviour.snapshot is supposed to work? I keep getting this error:
    Code (CSharp):
    1.  
    2. NullReferenceException: Object reference not set to an instance of an object
    3. Panda.BTProgram.set_snapshot (Panda.BTSnapshot value) (at Assets/PandaBehaviour/Core/Panda/BT/BTProgram.cs:305)
    4. Panda.BehaviourTree.set_snapshot (Panda.BTSnapshot value) (at Assets/PandaBehaviour/Core/PandaUnity/BehaviourTree.cs:418)
    5. Test2.FixedUpdate () (at Assets/Test2/scripts/Test2.cs:59)
    6.  
     
    laurentlavigne likes this.