Nodes

Nodes are the fundamental building blocks of behavior trees in ClosureBT.

Overview

Every node in ClosureBT has three possible statuses during execution:

  • RunningThe node is currently executing and hasn't finished yet.
  • SuccessThe node completed successfully.
  • FailureThe node failed to complete.

Core Concepts: Reactive Pattern & Invalidation

Two important core features that build on top of nodes are the Reactive Pattern and Invalidation checks. These are essential tools for building responsive systems that react to changing conditions.

The Reactive Pattern allows nodes to automatically detect when conditions change and reset their execution, making it easy to build behaviors that respond immediately to state changes without complex manual state management.

Creating Nodes

Nodes are created using static methods from ClosureBT. The simplest way to create a node is with a name and a setup action:

// Create from an existing node
Node MySequence() => Selector("Foo", () =>
{
    Wait(1);
    // ...
});

// A custom leaf node
Node Baz() => Leaf("Baz", () => 
{
    OnBaseTick(() =>
    {
        return SomeCalculation() ? Status.Success : Status.Running;
    });
});
// Create from an existing node
Node MySequence() => Selector("Foo", () =>
{
    Wait(1);
    // ...
});

// A custom leaf node
Node Baz() => Leaf("Baz", () => 
{
    OnBaseTick(() =>
    {
        return SomeCalculation() ? Status.Success : Status.Running;
    });
});

Resetting Nodes

You can reset a node to its initial state at any time:

// Reset immediately (synchronous)
AI.ResetImmediately();

// Reset gracefully (may take multiple ticks for async cleanup)
AI.ResetGracefully();

// Common use: clean up when GameObject is destroyed
private void OnDestroy() => AI.ResetImmediately();
// Reset immediately (synchronous)
AI.ResetImmediately();

// Reset gracefully (may take multiple ticks for async cleanup)
AI.ResetGracefully();

// Common use: clean up when GameObject is destroyed
private void OnDestroy() => AI.ResetImmediately();

Composite Nodes

Composite nodes are special nodes that contain child nodes. They define how their children are executed:

Node Root() => Selector("Root", () =>
{
    // Children added implicitly in order they appear
    Sequence("Patrol", () =>
    {
        MoveAroundPoints(() => PatrolPoints, () =>
        {
            OnEnter(() => Debug.Log("Began Patrolling"));
            OnExit(() => Debug.Log("Stopped Patrolling")
        });

        Wait(2f);
    });

    Attack();
});
Node Root() => Selector("Root", () =>
{
    // Children added implicitly in order they appear
    Sequence("Patrol", () =>
    {
        MoveAroundPoints(() => PatrolPoints, () =>
        {
            OnEnter(() => Debug.Log("Began Patrolling"));
            OnExit(() => Debug.Log("Stopped Patrolling")
        });

        Wait(2f);
    });

    Attack();
});

The main composite types are Sequence and Selector.

Parameters and Return Values

Nodes can have parameters and return values by passing them around as Func<T>

Node Patrol(Func<List<T>> points, out Func<GameObject> getTargetSeen)
{
    VariableType<GameObject> targetSeen; // Variables must be made inside a node!
    targetSeen = () => targetSeen.Value;

    return Leaf("Patrol", () =>
    {
        targetSeen = new();

        OnBaseTick(() =>
        {
            // Patrol logic here
            // If target seen, set _targetSeen and return Success

            if (Sight.HasTargetInSight(out var target))
                targetSeen.Value = target;

            return targetSeen.Value ? Status.Success : Status.Running;
        });
    });
}
Node Patrol(Func<List<T>> points, out Func<GameObject> getTargetSeen)
{
    VariableType<GameObject> targetSeen; // Variables must be made inside a node!
    targetSeen = () => targetSeen.Value;

    return Leaf("Patrol", () =>
    {
        targetSeen = new();

        OnBaseTick(() =>
        {
            // Patrol logic here
            // If target seen, set _targetSeen and return Success

            if (Sight.HasTargetInSight(out var target))
                targetSeen.Value = target;

            return targetSeen.Value ? Status.Success : Status.Running;
        });
    });
}

Now it can be used like this

Sequence(() =>
{
    Patrol(() => PatrolPoints, out var seenTarget);
    Attack(seenTarget);
});
Sequence(() =>
{
    Patrol(() => PatrolPoints, out var seenTarget);
    Attack(seenTarget);
});

Next: Sequence

Understand Sequence nodes that run children in order.

Read about Sequence →