Articles

Behavior trees in Unity with Behave

6

NOTE: These tutorials were created for Behave 0.3, and Behave has changed considerably since then. You can use them as guidelines for concepts, but do not expect the code will work straight out with the latest libraries.

Behavior trees are a powerful tool for implementing how your world characters act, as well as react to their environment, and a good alternative to finite state machines or pure scripting. Thanks for AngryAnt’s Behave plugin we can now greatly simplify the work of implementing behavior trees in Unity, but I’m sure if you’re unfamiliar with behavior trees you probably saw the example and were left thinking that’s nice – but what do I do now?.

Where AngryAnt’s initial tutorial focused on giving you an initial view of the Behavior Tree workflow using his plugin, I’ll focus more on the actual code flow with a behavior tree and how to get things done with it. You’ll still want to check out the AIGameDev videos for a better background on the concepts. I’ll quickly skip over some of the Behave UI elements, since AngryAnt already explained those in his tutorial.

Note: while this tutorial is written in Boo, its follow-ups are written in C#. Once you’re done with it you can move to the ones on sequences and branching paths with selectors.

Setup

Through this tutorial I’ll use the Copper prefab provided with the Unity 3D, along with some of its scripts as a starting point. I’ve exported the enemy folder to an Unity package, which you can get from here. Import it into a new project.

The next step is to import AngryAnt’s Behave package. Go to his page to obtain it, and bear in mind that you will need to install Mono if you don’t already have it.

So now we have the Copper model ready to go. Create a large plane to have it resting on, and drop a Copper prefab on the scene. Move it so that it’s at (0, 0, 0) – nice and close to the camera. Don’t worry about the plane having boundaries, it’s not going anywhere.

(And don’t forget to remove its Enemy tag).

We’ll use a simplified version of EnemyPoliceGuy.js from the 3D tutorial. I’ve removed from it most of the old action related code. This includes the functions for Idling, Attacking, as well as the code linking to the level status. Finally, I noticed that the Copper’s target is hardcoded – it always looks for a single object with the Player tag. This makes sense if there’s going to be only one player, but we want this to be more general – perhaps we would like this to be a controller on an RTS, where there are many possible targets.

Finally, I’ve converted the code to Boo. Why? Because our object will need to implement an interface, and the two best choices in Unity seem to be C# and Boo. But I already know C# very well, so this gives me a chance to play more with another language I not that familiar with.

You can get the starting file from here.

Creating a behavior tree

Now it’s time to get real. The original EnemyPoliceGuy script had the following code in the Start function:


// Just attack for now
while (true)
{
// Don’t do anything when idle. And wait for player to be in range!
// This is the perfect time for the player to attack us
yield Idle();

// Prepare, turn to player and attack him
yield Attack();
}

The behavior for each is pretty self explanatory if you look at its source – Idle until the target gets within range, then goes into Attack mode. We’ll now begin building an equivalent behavior tree.

If you have properly imported the Behave package, you should now have a Behave menu object under Assets, much like this:

BH1 1 Behave Menu 400x250.Shkl

There will also be a new item under Assets > Create, which allows you to create a new Behave Library.

BH1 2 Create Library 400x250.Shkl

Click on it, and rename your new library from NewBehavelibrary0 to CopperLibrary. If you can’t see the library editing window, right-click it and select Behave > Edit Library. A two-panel window will appear.

Click on the + sign on the left to create a new collection, and name it CopperBasic; then click on a the + under the right panel to create a new tree, and name it SimpleTree. You’ll end up with this:

BH1 3 Create Tree 400x268.Shkl

Select SimpleTree, and click on Edit. Unity will display this:

BH1 4 Canvas 400x288.Shkl

What you’re looking at is, on the left, the possible components you can use to create your behavior tree; below them, an object inspector you can use once you’ve added a few components; and on the right, the empty canvas in which you’ll build the tree. For this tutorial we’ll only use Actions and Sequences.

Select the Sequence, click on “Add Component”. An unattached sequence will appear on the canvas. Then select Action, and add two as well. See how each of the unnamed actions has a nob on top, as does the sequence? These are to connect them to other components. You’ll notice there’s a nob on the top of the screen as well – this will be the start of your tree.

Now do this:

  • Click on the nob at the top of the screen, and drag a line to the one at the top of the sequence icon to connect them.
  • Once you have done that, drag another one from the bar at the bottom of the sequence to the first action.
  • And then, from the sequence to the second action.
  • Finally, click on the first action and, using the inspector, rename it to WaitForTarget. Once you have done this, rename the second one Attack.

Your tree should look like this:

BH1 5 Sequence 400x288.Shkl

What does this mean? You have just created an action tree consisting solely of a series of actions. The actions in the sequence are executed from left to right, so this means that first it will execute the WaitForTarget action, and once that one completes successfully it will execute the Attack action.

You’ll see how this works out once we get to the code. For now, right-click the library and select Behave > Build Library Debug.

BH1 6 Build 400x250.Shkl

Here’s where I should mention, by the way, that I hate how Unity always selects all items on the Hierarchy window when I press Command-A, no matter where I press it. Very un-Mac-like.

BH1 7 Output

The first new file will be the library, the second a manifest its contents of your library. In our case:


Library class:
BLCopperLibrary

Trees:
CopperBasic_SimpleTree
Unknown

Actions:
Attack
WaitForTarget
Unknown

Decorators:
Unknown

The Trees, Actions and Decorators are enumerators for each possible option. We’ll see next how to use them.

Before we go any further: this is likely obvious, but it bears mentioning. Most of the code you’ll create in Unity is compiled the moment you save it, when Unity reimports it. With the behavior library, we’re building it explicitly ourselves in this step. This means that if you reopen the library and change a couple things, you shouldn’t expect Unity to pick up on the changes right after you save it – they’ll only be reflected after you rebuild it.

Expanding our enemy controller

We have a behavior tree created – now we need to use it. Download EnemyPoliceGuy.boo and import it to your project, then assign it to the Copper object you have on the scene. Press Play, and you’ll see the robot playing its idle animation, but doing nothing else.

We will now add a bit of code. Bear in mind that like in Python, indentation is important in Boo, and should be consistent as well – that is, always spaces or always tabs.

Add the following to the member variable declarations, right below the lastPunchTime declaration.


enemyBehaviorTree as Tree
currentTarget as GameObject

Then at the very end of the Start method:


enemyTree = BLCopperLibrary.Instance.Tree(cast(int, BLCopperLibrary.Trees.CopperBasic_SimpleTree));

What this does is create an instance of the CopperBasic_SimpleTree that we defined earlier. These are defined by enumerators, but we’ll need to cast them to integers which are what the Behave methods expect.

But we have simply created a tree instance, we should tell it to do something. We first evaluate if the behavior tree is running, and if it isn’t’ – perhaps it hasn’t started, or a cycle just ended- we will reset it.


def FixedUpdate ():
if enemyBehaviorTree.Tick(self, null) != BehaveResult.Running:
Debug.Log( “Resetting” );
enemyBehaviorTree.Reset( self, null );

We’ll then need the add a methods for Behave to call on every tick:


def TickAction(action as int, sender as Tree, data as Object):
castAction = cast(BLCopperLibrary.Actions, action)
Debug.Log(“Action: “ + castAction)
if castAction BLCopperLibrary.Actions.WaitForTarget:
pass
elif castAction BLCopperLibrary.Actions.Attack:
pass
else:
Debug.LogError( “Unknown action ID: “ + action );
return BehaveResult.Failure;

(Just noticed that Boo doesn’t have a switch / case statement. Curious.)

The TickAction method will get called every time there is a tick, with an integer identifying the action executed, the tree that is executing it, and any relevant data that is passed to the tree originally as a parameter (more on this on another post). As you can see, we cast the integer to an Action enumerator, evaluate it, but to nothing for now – we simply return that the action failed.

Just so that the tree can actually reset we should define the following function:


def ResetAction(action as int, sender as Tree, data as Object ):
pass

Save the script so that it is compiled, and play the scene to see how your copper acts.

Now, this does nothing. It runs, it gets called, and it returns that the action failed. You can also see on the debug log we only get called with WaitForTarget – why is this?

BH1 8 Debug Log 1

The reason only WaitForTarget is being called is because we defined things above as a sequence. Since the first call to WaitForTarget is returning BehaveResult.Failure, it never moves to the second action but instead finishes executing, only to be reset in FixedUpdate. This is a very useful property of behavior trees, particularly when dealing with alternate paths via selectors, but we will explore that in another tutorial. In this specific case, it allows us to avoid attacking until we actually see a target (perhaps we should have named it LocateTarget) – we wouldn’t want the robot to flail around wildly, would we? A more complex behavior could allow for it to roam within an area, if we present alternative actions via a selector.

Let’s convert the original Idle method


def PlayIdleSound():
if idleSound:
if audio.clip != idleSound:
audio.Stop()
audio.clip = idleSound
audio.loop = true
audio.Play()

yield WaitForSeconds(idleTime)

Then add the following methods (part of LookForTarget comes from the original script as well):


// Attempts to lock on a target
def LookForTarget():
PlayIdleSound()
currentTarget = null
targets = GameObject.FindGameObjectsWithTag(“Enemy”)
for target in targets:
offset = transform.position – target.transform.position
if offset.magnitude < attackDistance:
Debug.Log(“Will attack “ + target.transform.GetInstanceID())
currentTarget = target
break
if currentTarget == null:
animation.Stop(“attackrun”)
animation.Play(“idle”)

def Attack():
animation.Stop(“idle”)
animation.Play(“attackrun”)

What LookForTarget does is to iterate through all game objects with the Enemy tag (precisely the reason why we wanted it removed from the Copper robot), and look if any one of them is within attacking distance. If so, the method returns the first one it finds. If also changes to playing a “robot idle” animation if we haven’t found a target.

A better approach would be to detect objets that are in range via a trigger collider, but I for this demo I wanted to modify the code as little as possible. We will deal with that in another tutorial.

The Attack method simply stops playing the idle animation, and then makes the model look like it set of in an attack run. I’m avoiding adding any further code to focus simply on the behavior-related code.

Finally, let’s update the TickAction method:


def TickAction(action as int, sender as Tree, data as Object):
castAction = cast(BLCopperLibrary.Actions, action)
Debug.Log(“Action: “ + castAction)
if castAction BLCopperLibrary.Actions.WaitForTarget:
LookForTarget()
if currentTarget != null:
return BehaveResult.Success
elif castAction BLCopperLibrary.Actions.Attack:
Attack()
return BehaveResult.Success
else:
Debug.LogError( “Unknown action ID: “ + action );

return BehaveResult.Failure;

As you can see, we have modified it so that it returns Behave.Success if either we were able to successfully locate a target, or if we could attack a previously acquired target – otherwise it’ll report the action has having ended in failure.

Now add a capsule to the scene, and add the Enemy tag to it. Place it away from the robot, on (-15, 1.5, 18).

BH1 9 Capsule 400x251.Shkl

Play the scene. The flow will be the same as before, and Attack won’t be executed. Have we accomplished nothing?

Let’s try this – while playing the scene, drag the capsule close to the robot. When it gets close enough, you’ll see the robot break into a run, and the following appear on your debug log:

BH1 10 Debug Log 2

Ta dá! Once the WaitForTarget action from the sequence returns that it was successful, Behave will then attempt to execute the second action. Otherwise it’ll keep our robot in an idle, targeting pattern. Now pull back the capsule beyond the robot’s detection range, and he’ll return to his normal idle state.

You have just implemented your first behavior sequence using behavior trees.

Conclusion

You can see how using the Behave plugin to abstract the all handling of behavior flow allows us to focus solely on the task that each behavior needs to perform. While the code in this version of our example is not that far away from the one in the original tutorial, you’ll see when we go further into complex behaviors how uncomplicated it is to link them together into a sensible flow.

You can obtain the final version of the project here.

  1. Frank
    Frank04-06-2011

    I am a student from China & download the final project of this tutorial, got some errors after using Unity3.3 & lasest version of Behave as list out. please help.

    Assets/Scripts/Enemies/EnemyPoliceGuy.boo(58,27): BCE0018: The name ‘BLCopperLibrary.Action’ does not denote a valid type (‘not found’). Did you mean ‘Behave.Assets.Action’?

    By the way, can we have the tutorial on C# version.

    • Ricardo J. Méndez
      Ricardo J. Méndez04-07-2011

      Hello Frank,

      As I mention above, this tutorial is very old – over 2 years old by now. While its concepts are current, the Behave API is not, and that’s why you’re getting those errors. I recommend contacting AngryAnt, as I believe he has some tutorials for the current version of Behave.

  2. jhon
    jhon10-15-2012

    Hello Ricardo,
    I like this tool verry much, but before I using behave in my project I think I need a demo for learning with it, thanks.

Leave a Reply