Source file unity-csharp/game-ai/behaviour-tree/BehaviourTree.cs from the GDnD code examples. Download raw file

// GDnD wiki example
// Demonstrates: a minimal behaviour tree framework (status, node, sequence, selector, inverter, condition and action leaves)
// Related pages: [[behaviour-trees]], [[ai-state-machine-pattern]], [[game-ai-agent-design]]
 
using System;
using System.Collections.Generic;
 
// Every node answers one question when ticked: "how did that go?"
public enum NodeState { Success, Failure, Running }
 
// Plain C# classes, not MonoBehaviours: the agent owns the tree and ticks it.
public abstract class BTNode
{
    public string Name { get; }
 
    protected BTNode(string name) => Name = name;
 
    public abstract NodeState Tick();
}
 
// AND: runs children in order and stops at the first one that does not succeed.
// Use it for "check this, then do that" steps.
public sealed class SequenceNode : BTNode
{
    private readonly List<BTNode> children;
 
    public SequenceNode(string name, params BTNode[] children) : base(name)
        => this.children = new List<BTNode>(children);
 
    public override NodeState Tick()
    {
        foreach (BTNode child in children)
        {
            NodeState result = child.Tick();
            if (result != NodeState.Success)
                return result;          // Failure or Running stops the sequence here
        }
        return NodeState.Success;
    }
}
 
// OR: tries children in priority order and stops at the first one that does not fail.
// Put the most urgent branch first.
public sealed class SelectorNode : BTNode
{
    private readonly List<BTNode> children;
 
    public SelectorNode(string name, params BTNode[] children) : base(name)
        => this.children = new List<BTNode>(children);
 
    public override NodeState Tick()
    {
        foreach (BTNode child in children)
        {
            NodeState result = child.Tick();
            if (result != NodeState.Failure)
                return result;          // Success or Running stops the selector here
        }
        return NodeState.Failure;
    }
}
 
// Decorator: flips Success and Failure; Running passes through unchanged.
public sealed class InverterNode : BTNode
{
    private readonly BTNode child;
 
    public InverterNode(string name, BTNode child) : base(name) => this.child = child;
 
    public override NodeState Tick()
    {
        NodeState result = child.Tick();
        if (result == NodeState.Success) return NodeState.Failure;
        if (result == NodeState.Failure) return NodeState.Success;
        return NodeState.Running;
    }
}
 
// Leaf: asks a yes/no question about the world. Never Running.
public sealed class ConditionNode : BTNode
{
    private readonly Func<bool> test;
 
    public ConditionNode(string name, Func<bool> test) : base(name) => this.test = test;
 
    public override NodeState Tick() => test() ? NodeState.Success : NodeState.Failure;
}
 
// Leaf: does one tick's worth of work and reports how it is going.
public sealed class ActionNode : BTNode
{
    private readonly Func<NodeState> act;
 
    public ActionNode(string name, Func<NodeState> act) : base(name) => this.act = act;
 
    public override NodeState Tick() => act();
}