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();
}