Quick Start

Get up and running with ClosureBT in just a few minutes with this hands-on introduction.

Conceptual Learning

If you are new to behavior trees, it's recommended to read Behavior Trees for AI: How They Work first. These docs focus more on ClosureBT usage rather than behavior tree fundamentals.

Getting Started

For the best experience with ClosureBT, start by importing the static AI class:

using static ClosureBT.BT;
using static ClosureBT.BT;

This gives you direct access to all ClosureBT nodes and functions without the AI. prefix.

Quick Overview

Here's the basic setup for using ClosureBT

using static ClosureBT.BT;

public class YourNPC : MonoBehaviour
{
    public Node AI; // This will expose a "Open Node Graph" button in the inspector

    private void Awake() => AI = Sequence("NPC AI", () =>
    {
        Do(() => Debug.Log("Hello World"));
        Wait(1);
    });

    private void Update() => AI.Tick();
    private void OnDestroy() => AI.ResetImmediately();
}
using static ClosureBT.BT;

public class YourNPC : MonoBehaviour
{
    public Node AI; // This will expose a "Open Node Graph" button in the inspector

    private void Awake() => AI = Sequence("NPC AI", () =>
    {
        Do(() => Debug.Log("Hello World"));
        Wait(1);
    });

    private void Update() => AI.Tick();
    private void OnDestroy() => AI.ResetImmediately();
}

Cleanup Tip

You should probably call ResetImmediately() on your nodes in OnDestroy to ensure proper cleanup.

Sequence - The AND Logic

Sequences are like A() && B() && C() in normal code. They run children in order and succeed only if all children succeed.

Node Chase(Action lifecycle = null) => Sequence("Chase", () =>
{
    Condition("Target Too Far", () => Vector3.Distance(transform.position, target.position) > 5f);
    Leaf("Move Towards", () =>
    {
        OnEnter(() => Debug.Log("Begun to chase target!"));

        OnBaseTick(() =>
        {
            var distance = Vector3.Distance(transform.position, target.position);

            if (distance > 3f)
            {
                transform.position = Vector3.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
                return Status.Running;
            }
            else
                return Status.Success;
        });
    });

    // The second parameter allows us to attach lifecycle
    Wait(1f, () =>
    {
        OnSuccess(() => Debug.Log("Okay we're close enough now!"));
    });

    lifecycle?.Invoke();
});
Node Chase(Action lifecycle = null) => Sequence("Chase", () =>
{
    Condition("Target Too Far", () => Vector3.Distance(transform.position, target.position) > 5f);
    Leaf("Move Towards", () =>
    {
        OnEnter(() => Debug.Log("Begun to chase target!"));

        OnBaseTick(() =>
        {
            var distance = Vector3.Distance(transform.position, target.position);

            if (distance > 3f)
            {
                transform.position = Vector3.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
                return Status.Running;
            }
            else
                return Status.Success;
        });
    });

    // The second parameter allows us to attach lifecycle
    Wait(1f, () =>
    {
        OnSuccess(() => Debug.Log("Okay we're close enough now!"));
    });

    lifecycle?.Invoke();
});

Key Takeaways

  • Lifecycle methods like OnEnter and OnExit attach functionality to nodes
  • Most functions have overloads where the first parameter is the node name
  • Use a lifecycle parameter in custom nodes to add more functionality easily

Learn more about lifecycle methods →

Selector - The OR Logic

Selectors are like A() || B() || C() in normal code. They try children sequentially until one succeeds.

Node SomeAbstractChoices() => Selector("Choices!!!", () =>
{
    D.Condition(() => Foo == true);
    Sequence(() =>
    {
        Wait(1, () => OnSuccess(() => Debug.Log("Cool!")));
        Chase(() =>
        {
            OnSuccess(() => Debug.Log("We reached the target"));
            OnFailure(() => Debug.Log("We gave up chasing the target"));
        });
    });

    D.Condition(() => Bar == true);
    Wait("Pretend to do something", 1);

    // A final node that will run if Foo and Bar are both false
    Wait("Final", 1);
});
Node SomeAbstractChoices() => Selector("Choices!!!", () =>
{
    D.Condition(() => Foo == true);
    Sequence(() =>
    {
        Wait(1, () => OnSuccess(() => Debug.Log("Cool!")));
        Chase(() =>
        {
            OnSuccess(() => Debug.Log("We reached the target"));
            OnFailure(() => Debug.Log("We gave up chasing the target"));
        });
    });

    D.Condition(() => Bar == true);
    Wait("Pretend to do something", 1);

    // A final node that will run if Foo and Bar are both false
    Wait("Final", 1);
});

In terms of standard code, this might look like:

void SomeAbstractChoices()
{
    if (Foo == true)
    {
        if (Wait(...) && Chase())
            return Status.Success;
    }
    else if (Bar == true)
    {
        if (Wait(...))
            return Status.Success;
    }
    else
    {
        if (Wait(...))
            return Status.Success;
    }

    return Status.Failure;
}
void SomeAbstractChoices()
{
    if (Foo == true)
    {
        if (Wait(...) && Chase())
            return Status.Success;
    }
    else if (Bar == true)
    {
        if (Wait(...))
            return Status.Success;
    }
    else
    {
        if (Wait(...))
            return Status.Success;
    }

    return Status.Failure;
}

The real convenience comes from lifecycle methods that allow us to add non instaneous entry and exit methods.

Why Lifecycle Methods Matter

Lifecycle methods like OnEnter, OnExit, OnTick, OnSuccess, and OnFailure make it incredibly easy to initialize and cleanup state without cluttering your behavior tree logic. You can:

  • Attach setup code to any node with OnEnter
  • Ensure cleanup always happens with OnExit
  • React to specific outcomes with OnSuccess and OnFailure
  • Do all this without modifying the core node structure
  • Supports async operations

Next: Nodes

Learn about nodes, the fundamental building blocks of behavior trees.

Read about Nodes →