Skip to content

Getting started with an existing behavior tree implementation

Stacey edited this page Sep 29, 2020 · 4 revisions

This guide provides information on how to setup an existing behavior tree to work with the Behavior Tree Visualizer tool.

Install the package

There are multiple ways that you can install a package through Unity's Package Manager tool. For this project, you can either download the latest releases .zip from the Releases tab, or you can point Package Manager directly towards this repository.

It's recommended that you point Package Manager to the repository, as you will automatically be notified of new releases that way. Here's how you can do it:

  1. Go to Window > Package Manager.
  2. Click on the + button and choose Add Package from git URL.
  3. Enter the following URL: https://github.com/Yecats/UnityBehaviorTreeVisualizer.git?path=/com.wug.behaviortreevisualizer

The package comes with three optional components that you can install. While none of these are needed for this guide, they may still be useful for you:

  1. Standard Nodes contains a set of standard behavior tree nodes that can be used for your game such as sequencer, selector, timer, etc. Read more here.
  2. NPC Behavior Tree - 2020.1 and NPC Behavior Tree - 2019.4 are sample projects that gives an example on how a simple behavior tree can be setup. In the project, an NPC randomly walks to waypoints and searches for items around it to collect. There are two versions of this project for Unity 2019.4 and 2020.1 due to changes in navigation baking - otherwise everything else is the same. Both samples also require the Standard Nodes component to be installed.

Refactor your project

Generating the graph requires a bit of refactoring so that the Behavior Tree Visualizer can detect your behavior trees. You must:

  1. Have your base class inherit from NodeBase.
  2. Adjust all existing behavior scripts to reference the required properties and method (referenced below).
  3. Update your MonoBehavior script to implement the IBehaviorTree interface and store a reference to your running behavior tree within the BehaviorTree property.

Steps 1 and 2: Inherit from NodeBase

Inheriting from NodeBase ensures that anything that the Behavior Tree Visualizer needs to run exists. It also contains the delegate that the tool looks for, which you will need to trigger on your Node class.

Let's take a look at what the class contains:

Type Name Description Required
List ChildNodes Contains all child nodes of the node. Do not add anything to the list, and it'll be ignored (and thus, treated as a leaf node). Decorators (Inverter, UntilFail, etc.) should only ever have one child and Composites (Sequencer, Selector, etc.) can have as many as they need. Yes
StatusReason LastNodeStatus Reference to the last status of the node, such as Success, Failure or Running. Update this each time the node is run. The tool watches for this value and uses it to set the icon and border highlight. Yes
string Name The friendly name of the title bar of the graph node. If not set, the tool will default to the file name. No
method OnNodeStatusChanged(NodeBase sender) Handles invoking the NodeStatusChangedEventHandler delegate. This should be called every time the LastNodeStatus or StatusReason properties change. Yes
string StatusReason Custom message that you want to display on the graph node. No

Here's an example on how the properties are reflected on a drawn node:

The sample projects and Standard Nodes scripts contain several examples of implementation. For ease, here is one of the scripts, SetNavigationActivityTo:

    public class SetNavigationActivityTo : Node
    {

        private NavigationActivity m_NewActivity;

        public SetNavigationActivityTo(NavigationActivity newActivity)
        {
            m_NewActivity = newActivity;

            //Set the Name property on construction
            Name = $"Set NavigationActivity to {m_NewActivity}";
        }
        protected override void OnReset() { }

        protected override NodeStatus OnRun()
        {
            if (GameManager.Instance == null || GameManager.Instance.NPC == null)
            {
                //Set the StatusReason so a message is displayed if this is the reason of the failure
                StatusReason = "GameManager or NPC is null";
                //Return NodeStatus of Failure, which is set on Node base class
                return NodeStatus.Failure;
            }

            GameManager.Instance.NPC.MyActivity = m_NewActivity;

            //Return NodeStatus of Success, which is set on Node base class
            return NodeStatus.Success;
        }
    }
}

Step 3: Implement IBehaviorTree

When scanning the scene, the Behavior Tree Visualizer tool looks for any references to IBehaviorTree and uses reflection to find the public NodeBase BehaviorTree { get; set; } property. This means that when you are executing your behavior tree, you should reference BehaviorTree and not your own variable. Here is a snippet of the implementation of the NonPlayerCharacter class, located in the sample project:

public class NonPlayerCharacter : MonoBehaviour, IBehaviorTree
{
    public NodeBase BehaviorTree { get; set; }
    private Coroutine m_BehaviorTreeRoutine;
    private YieldInstruction m_WaitTime = new WaitForSeconds(.1f);

    private void Start()
    {
        GenerateBehaviorTree();
            
        if (m_BehaviorTreeRoutine == null && BehaviorTree != null)
        {
            m_BehaviorTreeRoutine = StartCoroutine(RunBehaviorTree());
        }
    }

    private void GenerateBehaviorTree()
    {
        BehaviorTree = new Selector("Control NPC",
                            new Sequence("Pickup Item",
                                new IsNavigationActivityTypeOf(NavigationActivity.PickupItem),
                                new Selector("Look for or move to items",
                                    new Sequence("Look for items",
                                        new Inverter("Inverter",
                                            new AreItemsNearBy(5f)),
                                        new SetNavigationActivityTo(NavigationActivity.Waypoint)),
                                    new Sequence("Navigate to Item",
                                        new NavigateToDestination()))),
                            new Sequence("Move to Waypoint",
                                new IsNavigationActivityTypeOf(NavigationActivity.Waypoint),
                                new NavigateToDestination(),
                                new Timer(2f,
                                    new Idle()),
                                new SetNavigationActivityTo(NavigationActivity.PickupItem)));
    }

    private IEnumerator RunBehaviorTree()
    {
        while (enabled)
        {
            if (BehaviorTree == null)
            {
                $"{this.GetType().Name} is missing Behavior Tree. Did you set the BehaviorTree property?".BTDebugLog();
                continue;
            }

            (BehaviorTree as Node).Run();

            yield return m_WaitTime;
        }
    }