[AI SCRIPTING] Panda BT: Behaviour Tree scripting

I downloaded it late last night after skimming the docs but haven’t had time to play with it. A couple quick questions.

Are Task method names required to be unique on the GameObject with the BT script, or is there a way to specifically reference a Task method on a specific class when more than one attached script uses the same method for different purposes? To make up an example off the top of my head, maybe there is a Maneuver script and a Targeting script and they each have a PointAtEnemy method. Is there a way to differentiate?

I noticed your Task SetColor example passes constants to the method. Can variables be used? I suppose they’re scoped to whatever the target Task method can see? Given that success/fail is the return BT needs, does out/ref work?

Looks pretty cool though. Very glad you referenced other articles, the Simpson article in particular really shows off what they can do! I’m tempted to sidetrack myself into writing a node-based editor.

Thank @MV10 you, I hope you’ll give it a try soon!

A task in a BT script is bound to a C# method with the same signature (defined by the method name and the types of its parameters). Unfortunately, the same method implemented in different MonoBehaviours is not supported; the engine will raise an exception not knowing which one to choose. However, it’s possible to have overloaded tasks, that is, tasks with the same names but different parameters. In this case, it does not matter whether the tasks are implemented in the same MonoBehaviour or not.

I wanted to keep the language as simple as possible with no ambition to replace C#. Instead, the BT script are complementary to C#, so you can still do a lot on the C# side. If you want to have a variable, you can do it this way with the SetColor example:

using UnityEngine;
using Panda;

public class ColorTasks : MonoBehaviour
{
    public Color myColor;
    public Color[] myColorArray;

    [Task]
    void SetColor_myColor()
    {
        this.GetComponent<Renderer>().material.color = myColor;
        Task.current.Succeed();
    }

    [Task]
    void SetColor_myColor(int colorIndex)
    {
        this.GetComponent<Renderer>().material.color = myColorArray[colorIndex];
        Task.current.Succeed();
    }
}

This way you can set your colors from the Inspector as usual.
And here is a corresponding BT script:

tree "Root"
    sequence
        SetColor_myColor
        Wait 1.0
        tree "CycleColors"

tree "CycleColors"
    sequence
        SetColor_myColor 0
        Wait 1.0
        SetColor_myColor 1
        Wait 1.0
        SetColor_myColor 2
        Wait 1.0

Thank you!

Theses articles are very inspirational, they were in fact great resources for developing Panda. If you want to implement your own node-based editor, I would advice to have a look at the Asset Store first, there are already some solutions there.

I wanted to avoid writing yet another node-based editor, because as a programmer, I feel more efficient by typing code instead of dragging-and-dropping nodes around, and I hope there are other people like me :). And I believe that editing Behaviour Tree is closer to programming, that was in fact my motivation to create this package.

Yeah, I would love typing in a text file instead of using visual editors any day but my designers love the other way (drag and drop things visually). They kinda like the minimalistic approach of your BT but where asking me if they can add/remove stuff visually instead of going through text files. lol

I see. Is that so painful for designers to reach for text editor and fire it up? :slight_smile: Though, I understand it can be a bit scary for designers about typing raw text and that there is something reassuring about using an interface. I had a programmer workflow in mind while designing this tool. But I think there should be no technical difficulties to integrate this tool with a more graphical editor. Anyhow, the tasks still need a C# sharp implementation, and afak there is no node-based editor for C#.

Overall, I believe editing a Behaviour Tree is really close to programming, so it would be better to approach the workflow with programmer mindset. However, you don’t need to be a highly skilled programmer to write a Behaviour Tree. In fact, I believe it requires less programming skills to write an AI using this tool, rather than in plain C# only. All you need to know is how a BT is constructed, how the structural nodes works (which is also required with a node-based editor) and how to implement methods in C#. It requires no in depth knowledge about OOP, no inheritance, no encapsulation, etc… . So a beginner in programming could put stuff together rather easily.

I’m not completely closed to the idea to integrate a visual editor. This tool is very young. Just give it some time.

Edit:
I would like to add some advantages about using text file, that would be hard or inefficient to realized with a node-based editor.

  • Copy/Paste part of tree.
  • Indent a whole block of text to manage node parenting, and redefine the hierarchy.
  • Write comments anywhere to document your AI directly in script.
  • Mass renaming (useful during refactoring).

It also makes electronic communication easier. You can easily instant-message you BT script to a colleague to show something interesting. Or post your BT on a forum to request for help. Or share your AI on a wiki page.

Gave it a quick test and unfortunately didn’t like what I saw. Just running the first demo scene with the cube changing colors, 1kB of memory is allocated per frame. Duplicate the cube and the memory allocations are also doubled. The code is in a DLL so can’t try to fix it myself either and it also happens with a build, not just in editor.

Oh I definitely wouldn’t want visual-only … but it was pretty striking to me how easy it was to follow the graphs in the linked articles rather than picking through a text file. Thanks for the response.

I’ve just run the test with 1000 copies of the cube. I don’t see the 1kB increase per frame.
With 1 cube the memory used by mono is ~8 MB.
With 1000 cube the memory used by mono is ~14 MB.
The program is running for about 10 mins now, with that amount of cubes, the memory varies but it stays within the range of 12 MB to 17 MB.

Note that even with a empty scene from an empty project there are memory variation of ~0.1MB per frame.

The sources will be available in the pro version.

Thank you for profiling. I am really concern with the performance of this tool. Any feedback on that part is welcome.

I don’t mean it leaks memory, I mean it allocates memory each frame (creates & deletes objects) which in turn makes the garbage collector run more often. It’s not that big a deal on desktop, but unity’s GC is bad and on mobile that causes slow frames. You can just use the profiler and see the allocations per frame, optimally those should be 0.

I see it here too. I will have a closer look at it to see what exactly is going on here and try to eliminate the allocations.
Good catch. Thank you for reporting it.

I was amazed at how quickly things could be done with behavior trees. I hit a few new types of bugs in my own project, mainly because I’d never used a tree before. But the underlying logic is pretty straight forward. And being able to watch execution in the inspector window is amazing.

https://BoredMormon@bitbucket.org/BoredMormon/pandabehaviourspaceinvaders.git

You will have to re import PandaBehaviour. I’m generally not comfortable with putting other people’s assets in my public repos.

Feel free to use the project however you like.

I like the ticks version, I used it in a few places where I needed to move my logic onto different frames. A version that takes unscaled time would also be useful.

I totally took my designer hat off for this exercise. There are a ton of things that would make the game better. But my aim was to learn Behavior Trees.

I found the culprit, and somebody else already did:
http://stackoverflow.com/questions/35003769/increased-allocations-when-invoking-parameterless-method-using-methodinfo-invoke

I have absolutely no idea why is that happening. It’s quiet annoying. Could it be a bug in the mono version Unity is using?

Edit:

I’ve submitted a bug report to Unity. I hope they can fixed it. Otherwise I would need to find a workaround…

For the most curious of you here is a script reproducing the bug.
MethodInfo.Invoke allocates 40B per call

using System.Reflection;
using UnityEngine;
using System.Collections;

public class InvokeGCAllocation : MonoBehaviour
{
    public bool withParameters = false;

    MethodInfo _someFunction;
    MethodInfo _someFunctionWithParameters;

    public void SomeFunction()
    {

    }

    object[] parameters = new object[2];
    public void SomeFunctionWithParameters(int a, int b)
    {

    }


    void Start ()
    {
        parameters[0] = 0;
        parameters[1] = 1;

        _someFunction = typeof(InvokeGCAllocation).GetMethod("SomeFunction");
        _someFunctionWithParameters = typeof(InvokeGCAllocation).GetMethod("SomeFunctionWithParameters");
    }

    void Update ()
    {
        if( withParameters )
            _someFunctionWithParameters.Invoke(this, parameters); // This one is fine, no memory allocated.
        else
            _someFunction.Invoke(this, null); // This one allocate 40B per call for the GC to clean!
    }
}

That’s why I love Behaviour Tree so much. And I love it even more hearing this kind of experience.

Thank you for your project. I’ll have a look at it.

It’s already there: RealtimeWait. I really need to document these built-in tasks:p. BTW, they are implemented in the Panda Behaviour component itself and you can have a look at it, the source is available.

For a game made that fast is quiet successful and your main goal of learning BT is reached.

1 Like

This is fixed. Now the core engine is optimized on this issue; it uses 0B of GC Allocations per frame.

However it is of course always possible to write tasks that are not optimized. If you are concern with optimized GC. Read the following:

What you often need to take care of is the custom debugInfo string that is displayed in the inspector to give information about how a task is progressing.

Because string.Format(…) allocates memory on each call, you want to avoid that as much as possible. If you use debugInfo, use it in combination with the new global bool Task.isInspected, which returns whether the current BT is displayed in the Inspector. So you can make sure that strings are formatted only when they are actually displayed in the Inspector.

For example:

if( Task.isInspected )
    Task.current.debugInfo = string.Format("t-{0:0.000}",  tta);

There is already a package review pending, so this optimization and Task.isInspected will be available in the following release.

2 Likes

Hi @Kiwasi ,

I had a look at your project. Overall, the way you’ve used Panda is simple and straight forward. I like this style.

Looking at the way you build the BT controlling the player firing, it seems (I’m not sure) there is confusion about how the while node works:

tree "Shoot"
    Sequence
        Wait 1.5
        While PlayerPressShoot
            PlayerShoot

And this is the definition of PlayerPressShoot:

    [Task]
    void PlayerPressShoot ()
    {
        if (Input.GetButton("Fire1"))
            Task.current.Succeed();
    }

The PlayerPressShoot task runs and succeeds when the player press the “Fire1” button, and it never fails.

The while node is used to run a task under an assumption, if the assumption fails the task is not ticked and the while node fails. You can think of it as a guard. However, the PlayerPressShoot task never fails, so guarding the PlayerShoot task is not necessary.

I guess you wanted to do something as follow:
First define a task that tells whether the player is pressing the fire button or not:

    [Task]
    void IsFireButtonPressed()
    {
        Task.current.Complete(Input.GetButton("Fire1"));
    }

This task completes immediately. it succeeds if the “Fire1” is pressed, and fails otherwise.

Then the behaviour tree would be:

tree "Shoot"
    Sequence
        IsFireButtonPressed
        PlayerShoot
        Wait 1.5

So, sequences can be used to implement conditionals. Here the sequence fails if the fire button is not pressed, leaving the remaining task non-executed. In other words, “PlayerShoot” and “Wait 1.5” are executed only if “IsFireButtonPressed” succeeds.

I really like the way you’ve implemented the GameController:

tree "Root"
    Sequence
        FreezeGame
        Wait 120
        StartGame
        Race
            GameWon
            GameLost
        FreezeGame
        Wait 120
        RestartLevel

Particularly, the way you’ve used the race node to decide how the game ends. I would never have thought to use the race node in this situation :).

I would like add a point about the character case in script. The language keywords (tree, sequence, fallback, …) are case insensitive. So, for example, it’s up to to you if you want to write sequence, Sequence, SEQUENCE or even SeQuenCE. However task name are case sensitive: the task name in a BT script has to be the exact same name as the C# method.

This property can be used for defining a convention to distinct between the language keywords and the tasks. I like to use only small case for the keywords and CamelCase for the task. When the task has some variable, I separate it with an underscore (for example: SetDestination_EnemyPosition). But convention is a matter of taste, so it’s up to you and your team to define one.

2 Likes

Apparently the while is just a carry over from my C# habits. You are right, a sequence would actually do the job just as well, and would be clearer.

I’m currently messing around with the trees in one of my more complex projects. I’ll let you know how it goes. So far it’s much easier to understand then any other AI scheme I’ve used.

1 Like

Actually, I was also a victim of my C# habits. In fact, my first implementation of the while node was as you would have expected, that is a conditional loop. Later on, I feel the need for a node that would run another node under an assumption; there are plenty of situations where you need to check that some conditions are satisfied for running a node. Then, I was looking for a pertinent name for such a node. “If” didn’t work, because it as this idea of a punctual branching in time and not much about long running tasks and simultaneity. Then I thought about “unless”, this word was a good candidate it links two ongoing actions with a condition, however it has that double negation any programmer is allergic to. Then, I thought, wait a minute, if you look at the literal meaning of the word while, that was exactly what it means. You have two simultaneous action where one is conditioned by the other and it makes perfect sens in everyday life: “I can nail that plank while you hold it”, “While I have money, I’ll spend it”, “You can’t drive while you drunk.” .Then I decided to not use the while node as a loop, there is already node that implements looping: the repeat node. And if I need to implement a conditional loop, then I just have to combine a while node and a repeat node. Everything just fitted.

About the sequence node and conditional, I actually had an if node implemented (Because, what kind of programming does not have an if?), then I dropped it when I realized it was a duplication of the sequence node.

Have fun with your more complex projects, I’m curious how it will go.

1 Like

HI~ ericbegue:
Can I change bt file content dynamic when bt is running?(that mean I need to create new bt format file or text memory dynamic to use when program is running,user can change code at run time)

Hi @Michael-Li ,

Thank you for you interest in this tool.

I already had similar requests. I will expose a method on the PandaBehaviour component to compile a BT from a string. It will be:

PandaBehaviour.Compile( string source )

It will be available in the coming releases. So, stay tune!

1 Like

cool! @ericbegue , thanks!

Sorry. It seems I’ve missed your post.

A graphical representation might be easier to understand and it helps even more when you are not familiar with Behaviour Tree.

A textual representation also has some advantages:

  • It’s suitable for representing hierarchy (think about the unity scene Hierarchy tab)
  • No need to do the layout manually. (With a node-based editor, you often find yourself dragging the nodes around until you get a nice layout.)
  • It’s very compact! Therefore, more information at a glance. ( A node-based editor often take a large portion of the screen.)

I’m sure you’ll get to use to reading BT scripts, I guess it’s a matter of habit. It should not be more difficult than reading C# code.

1 Like