[AI SCRIPTING] Panda BT: Behaviour Tree scripting

Hi guys,

The built-in tasks (implemented on the PandaBehaviour component) are now documented:

http://www.pandabehaviour.com/?page_id=23#Built-in_Tasks

Couple minor typos thereā€¦ you have ā€œposeā€ instead of ā€œpauseā€ for DebugBreak, and for the last batch of Input entries it should be ā€œindefinitelyā€ rather than ā€œundefinitely.ā€

Definitely some interesting Input options though. I think what Iā€™m liking the most about all of this is the ease of chaining activities not tied to frame-based processing. (I realize that is no great insight!)

@MV10 , thank you for spotting the typos. Thatā€™s fixed now.

If there is only one thing to say about this tool. This is it!

A serious problem constantly present while developing games is the lack of simply describing the game logic in a time independent way. Everything is fine until you start to write some code to handle long running activities, and it get worst when theses activities include some conditions and branching, even if theses activities are quiet easy to describe in plain english (or any human language). The usual approach to solve it, is to use some states to keep track of what the AI is currently doing, then changes the state on the go and use some coroutines, some time tracking variables or countersā€¦ Then you look back at your code, and you canā€™t decipher the high-level logic anymore, itā€™s diluted all over your code. I have reached the conclusion that C# or any similar programming languages does not provide an appropriate mechanism to describe this type of activities and that Behaviour Trees are just the perfect tool for that.

Letā€™s say you want to do something simple as:

ā€œI want my NPC to carry this precious object to some destination, but if there are some enemies on the way, secure the object and then eliminate the enemies. Once the threat is eliminated, go on with carrying the object to its destination.ā€

This is a simple mission to understand.

Now, how would you implement that in C# only? It will take you days! I donā€™t even dare to sketch a solution for solving that.
But with a Behaviour Tree you can almost make a literal translation of this two sentences:

tree "root"
    fallback
        tree "DeliverPreciousObject"
        tree "EliminateThreat"
   
tree "DeliverPreciousObject"
    fallback
        while not IsThereThreatOnTheWay
            sequence
                MoveTo_PreciousObjectLocation
                PickUp_Object
                MoveTo_PreciousObjectDeliveryLocation
        tree "SecurePreciousObject"
   
tree "SecurePreciousObject"
        sequence
            MoveTo_PreciousObjectSafeLocation
            Drop_Object

tree "EliminateThreat"
    while IsThereThreatOnTheWay
        sequence
            SetTarget_NearestEnemy
            Attack_Target

You have your high-level logic readable right there. And you know that the C# implementation of the tasks should be trivial.

1 Like

Yeah my wife and I are knocking out a simple story-driven RPG in our spare time and this kind of abstraction is precisely how weā€™ll be using this. I actually came to it trying to decide whether it was suited to dialogue trees (it isnā€™t really, and so Iā€™m wrapping up a custom mini-language for that).

@MV10 , great that you are planning to use it for your game!

For a dialogue system, my first though was that BT was not appropriate and that you need more a Decision Tree for that. But after giving it some thoughts, it could come as handy if you define some proper tasks. Letā€™s try out an example, letā€™s say you want to design a dialogue for a shop in your RPG game:

tree "Dialogue"
    sequence
        Say "Welcome! What can I do for you?"
        Option 1 "I'm fine... just looking around."
        Option 2 "What are you selling?"
        Option 3 "This is a hold-up! All your gold and fast!!"

        WaitPlayerChoice

        fallback
            sequence Choice 1
                Say "Ok, take your time. I'm here if you need me."
     
            sequence Choice 2
                tree "Shop"
     
            sequence Choice 3
                Say "Help! Guard! Heeelp!"
                AddGold(100)
                AlertNearestGuard

tree "Shop"
    sequence
        Say "Trinkets, odds and ends, that sort of thing."
        Option 1 "Health potion: 10 G"
        Option 2 "Mana potion: 5 G"
        Option 3 "City map: 50 G"

        WaitPlayerChoice

        fallback
            sequence Choice 1
                RemoveGold(10) AddItem("HPotion")
            sequence Choice 2
                RemoveGold(5) AddItem("MPotion")
            sequence Choice 3
                RemoveGold(50) AddItem("CityMap")
            sequence
                Say "Sorry. You don't have enough gold!"
                Fail
     
        Say "Here you go. Thank you!"

The nice thing with that system is you can easily integrate the consequences of the dialogue choice quiet easily. Like the inventory management, or some triggers, like calling a guard.

1 Like

Interesting. Iā€™ll revisit this, thanks.

Hi guys,

There is a VS extension that would help to edit your BT scripts by displaying indentations guides:

https://visualstudiogallery.msdn.microsoft.com/e792686d-542b-474a-8c55-630980e72c30

This particularly helps if youā€™re are editing a deep tree.

Hi @ericbegue

Iā€™m really liking your BT, but Iā€™ve found a bug!

Sometimes deselecting a unit, and then reselecting that unit can cause Unity to pause for between 30secs and 2 mins, and cause this error:

This is with only this code:

         public void BehaviourAction()
         {
             if (!isInitialised)
             {
                 Initialise();
             }

             pandaBehavior.Tick();
         }

         [Task]
         private bool HasTargets()
         {
             return controller.enemies.Count > 0 ? true : false;
         }

         [Task]
         private bool CanWin()
         {
             return true;
         }

         [Task]
         private void Attack()
         {
             Task.current.Succeed();
         }

         [Task]
         private bool CanMove()
         {
             return true;
         }

         [Task]
         private bool SafeDistance()
         {
             return false;
         }

         [Task]
         private void Flee()
         {
             controller.transporter.Flee(controller.enemies[0].unitObject.transform.position, 500);
             Task.current.Succeed();
         }
tree "Root"
     fallback
         tree "Idle"
         tree "Attack"
         tree "Flee"
     
tree "Idle"
     sequence
         not HasTargets


tree "Attack"
     while CanWin
             sequence
                 Attack

tree "Flee"
     sequence
         CanMove
         not SafeDistance
         Flee

So very simplistic, but it still freezes Unity for an unnatural amount of time whenever I try to select the unit in the hierarchy. This is especially strange since Iā€™m using a high end PC.

EDIT: Iā€™ve found the culprit. When you have several components attached to a GameObject, and Panda Behavior is one of them, by selecting ā€˜Move Upā€™, or ā€˜Move Downā€™, it begins to slow down. Once youā€™ve done this about 3 or 4 times (trying to move Panda Behaviour to the top), Unity really begins to chug. This chugging will then continue whenever you try to select that unit, sometimes throwing the StackOverFlow exception. Would you be able to take a look when you get a chance? Right now my only workaround is deleting every other component from a GameObject, adding Panda Behaviour, and then adding all the rest (as itā€™s always best to have Panda Behaviour at the top so itā€™s easy to click on and read whatā€™s happening).

EDIT AGAIN: Iā€™ve found that by minimising panda behavior before moving it up or down the component hierarchy, it doesnā€™t trigger the slowdown.

Thanks!

@Nigey , thanks for your bug report. Iā€™ll take a look to see whatā€™s exactly is going on there and try to fix it.

Side note: I also often need to reorganized components attached on a GameObject, Itā€™s annoying to use the move up/down menu. It would be great if that was posible with a simple drag&drop. It seems a good candidate for a Unity feature request:).

1 Like

Lol, I thought the exact same thing.

@Nigey , I could reproduce the bug youā€™ve mentioned. It is indeed very annoying. The bug does occurs only with the current version (1.2.2) and does not occurred with the up coming version (1.2.3). I am suspecting it is related to the bug @Kiwasi discovered (see this post), this bug was making the BT being recompiled when an object is selected in the Hierarchy, and probably other undiscovered side effects. This will be fixed in the release of version 1.2.3.

I would like to somehow quickly distribute the fix to you guys, so you donā€™t have to wait for the fixed package being published on the Asset Store (the asset reviewing takes quiet some time). But I have to ensure first that I am not violating my agreements with Unity by doing so.

Thatā€™s exactly what I thought the issue was too. It seemed like itā€™d duplicate another version each time you moved the component. The delay got longer and longer each time you moved it. Iā€™ve seen a few other assets on the asset store with the same issue of recompiling each time itā€™s selected (A* Pathfinding, Iā€™m looking at you).

I didnā€™t know about the asset reviewing phase. I suppose that does make sense. Well Iā€™ve asked publishers for minor changes before via Email and theyā€™ve sent it to me. So Iā€™m guessing itā€™s not a problem. In case you find itā€™s okay to send the hotfix privately, my email is ā€˜nigel_clark_@outlook.comā€™. Thanks for fixing it quick :).

Oh! The BT scripts not appearing on the prefab seems to be caused by the same bug. So, this is a 1-stone-3-birds-fix (maybe more, who knows). Anyway, everything will better in the 1.2.3 release.

1 Like

Nice work. Iā€™m personally happy with simply waiting out the review process. Should be quicker for updates?

Either way none of this is time critical for me.

Iā€™ve got an answer from Unity, itā€™s fine I distribute the new package directly. So if you donā€™t want to wait for the Asset Store update, just PM me and Iā€™ll send the hotfixed package to you.

Panda BT Pro is now available on the Asset Store!

This version contains the sources and break point debugging.

So far, Iā€™m enjoying using Panda more than using the dialogue mini-language I wrote myself during the past week.

Disclaimer: I hate Python. :slight_smile: This is minor, but from time to time I stumble over PBTā€™s indent-based hierarchy. Looking at the language youā€™ve created, I canā€™t see a reason itā€™s important. I agree it looks nice, but I donā€™t understand why it must be enforced. Iā€™m guessing it makes your parsing easier? Any chance this requirement could be dropped? Or am I missing some case where lack of indentation would introduce ambiguity? (My trees are still pretty simple at this pointā€¦)

Another thought / suggestion / requestā€¦

In using PBT for dialogue experiments, I found myself calling the same tasks over and over. To use the example you posted the other day, Say(text) would be an example of this. It is obvious those repeated calls belong in a separate class. I also separated the UI handling, so methods like Say() and Option() go into a generic Dialogue class that sits in between the dialogue script in the behavior tree and the UI.

It quickly begins to feel wasteful to sprinkle copies of this Dialogue class on every single GameObject that needs to say something. Normally I would to implement it as a static class (there is almost no state-data and only one dialogue would ever be active at a given time) but this doesnā€™t work because, by definition it isnā€™t on the same GameObject, and of course, a static class canā€™t be a MonoBehaviour.

Iā€™m sure you see where Iā€™m going with this. I can define [Task] attributes on static methods but PBT canā€™t find them. Is there some Unity-reason for this? As far as I know, .NETā€™s GetCustomAttributes() should find [Task] anywhere in the project, right? As long as there are no naming ambiguities, would it be a problem to just call a named Task wherever it might be? I suppose this really only applies to static methods, but I tend to use them heavily for utility-style operations.

Yeah, I know what you mean about the Python indentation style. Sometimes I also have those ā€œaargghhhh! ffffffā€¦ā€-moments when somehow I end up with a mixed of spaces and tabs indentation in the same script rendering the hierarchy as ambiguous. I went for this style of indentation because it requires less typing than using brackets as in C/C++ or derived languages including C#. Also I believe is more readable, though it is sometime frustrating to have such a strict indentation. But, maybe I could support both stylesā€¦ I donā€™t know.

For example this BT script:

tree "DeliverPreciousObject"
    fallback
        while not IsThereThreatOnTheWay
            sequence
                MoveTo_PreciousObjectLocation
                PickUp_Object
                MoveTo_PreciousObjectDeliveryLocation
        tree "SecurePreciousObject"

Would become in a bracket style indentation:

tree "DeliverPreciousObject"
{
    fallback
    {
        while{ not{IsThereThreatOnTheWay} }
        {
            sequence
            {
                MoveTo_PreciousObjectLocation
                PickUp_Object
                MoveTo_PreciousObjectDeliveryLocation
            }
        }
        tree "SecurePreciousObject"
    }
}

Somehow, I prefer the python style. But maybe itā€™s just a matter of taste. I would like to have more feedback on this. Or maybe there is another way to specify hierarchies (Disclaimer: I am definitely not going towards an xml style, donā€™t even suggest it, it would be a plain and definitive ā€œNo!!!ā€)?

Iā€™ve made a small project trying to implement this Panda BT dialog system. And so far Iā€™m pretty satisfied of the result. I have the intention to place this solution on the Asset Store as a Panda BT extension. But since you are the catalyst of this idea, I will send the project to you in PM. Basically, any character in you game that speaks, therefore could trigger a dialogue has to have a specific MonoBehaviour attached to it ( thereā€™s no other way around this). However, that does not mean that this MonoBehaviour has to handle all the UI, this could be delegated to a unique object in the scene. So the ā€œSayā€ task can have a ā€œstatic smellā€.

In fact, my first Panda implementations did not require the [Task] attribute. But I thought maybe itā€™s a good idea to have a way to specify that you have the intention to use a method from a BT script, this would avoid some ambiguous situations where a method with the same name is already defined but has nothing to do with a BT script. Thatā€™s why there is this [Task] attribute: you know that this method is to be used from a BT script.

Also, it is simple and straight forward that the task implementations should be implemented on the same GameObject the PandaBehaviour component is attached to. This also introduces some flexibility. For instance, you can have the same BT script attached to different GameObjects but with different different task implementations. Such that you have the same high level logic but different effective implementation, it is a mechanism somewhat similar to polymorphism in OOP.

I agree it is technically possible to not have the restriction of having the task implementation attached to a specific game object. But you would need to introduce some task implementation searching or priority, which would introduce more complications. I would like to keep Panda BT as simple and straight forward as possible. But I am not closed to suggestions and ideas that would come in handy in practice. Like having a way to specify a global-default-tasks. But again that would introduce extra configurations. Which are not necessary, since it so easy to create a new MonoBehaviours and attached to GameObjects.

Panda BT is a really young project and there is always place for improvements. Iā€™m really glad to have such constructive critics and suggestions. Please keep them coming up!

Thanks, Iā€™d be interested in checking out what you came up with.

In terms of Tasks which arenā€™t associated with a GameObject, I wasnā€™t hoping for anything complicated. If there is ambiguity, Iā€™d be ok with it just throwing an error. With Unity serializing and deserializing things all over the place, no constructors on MonoBehaviours, etc, Iā€™m just not a big fan of composing game elements and relationships through the editor, I prefer a more data- and code-driven approach.

After considerations and weightings, having an in-inspector BT editor would make the workflow much simpler and more streamlined than editing the BT script with an external text editor (though I will keep this as an option). An in-inspector editor would eliminate all the indention hassles (:wink: @MV10 ) and it could open the option to have auto-completion and error checking directly within the Inspector.

The Unity scene Hierarchy tab could be a good model to start with, itā€™s rather easy to organize a hierarchy using it (obviously). So I am going toward this approach to organize the node hierarchy of a behaviour tree.

@tatoforever I think this new feature would match pretty well what you are suggesting. (?)

Thought, this is not going to be implemented soon. I need to give it more thoughts. Also, I need to refactor the PandaBehaviour inspector a lot, since it was originality not build to be an editor.

This in-inspector editor is a good reason to increment the minor version number, so this is going to be a feature of Panda BT version 1.3.x!

Stay tuned!

1 Like