GroveGames.BehaviourTree
1.1.1
dotnet add package GroveGames.BehaviourTree --version 1.1.1
NuGet\Install-Package GroveGames.BehaviourTree -Version 1.1.1
<PackageReference Include="GroveGames.BehaviourTree" Version="1.1.1" />
<PackageVersion Include="GroveGames.BehaviourTree" Version="1.1.1" />
<PackageReference Include="GroveGames.BehaviourTree" />
paket add GroveGames.BehaviourTree --version 1.1.1
#r "nuget: GroveGames.BehaviourTree, 1.1.1"
#:package GroveGames.BehaviourTree@1.1.1
#addin nuget:?package=GroveGames.BehaviourTree&version=1.1.1
#tool nuget:?package=GroveGames.BehaviourTree&version=1.1.1
GroveGames Behaviour Tree
A modular, allocation-conscious behaviour tree framework for AI development in C#, with first-class Unity and Godot integrations. Build complex AI behaviours by composing Composite, Decorator, and custom action nodes with a fluent API.
Table of Contents
- Features
- Installation
- Getting Started
- Unity Integration
- Godot Integration
- Customization
- Contributing
- License
Features
- Fluent builder API — compose trees with chainable extension methods:
Selector,Sequence,Parallel,Conditional,Cooldown,Delayer,Repeater,Inverter,Succeeder,Failer,Reset,Abort,SuccessOnce. - Type-safe blackboard — strongly typed
BlackboardKey<T>keys catch type mismatches at compile time; storing value types (int,float,Vector3, …) never boxes. - Reusable subtrees — extract branches into
ChildTreeclasses and attach them anywhere. - Unity integration —
BehaviourTreeMonobase component plus an editor Visual Debugger window with live node states. - Godot integration —
GodotBehaviourTreenode plus a debugger panel in the Godot editor. - AOT friendly — no reflection or runtime code generation in the core; works with IL2CPP and NativeAOT. Targets
netstandard2.1andnet10.0.
Installation
.NET
dotnet add package GroveGames.BehaviourTree
Unity
Add the package via Package Manager → Add package from git URL:
https://github.com/grovegs/BehaviourTree.git?path=src/GroveGames.BehaviourTree.Unity/Packages/com.grovegames.behaviourtree
Alternatively, download the .tgz from the latest release and add it as a local tarball package.
The core library uses modern C# features, so add a csc.rsp file to your project's Assets/ directory:
-langversion:10
-nullable:enable
Godot
dotnet add package GroveGames.BehaviourTree.Godot
Getting Started
The Blackboard
The blackboard is a shared data space that nodes use to exchange state. Access is type-safe through BlackboardKey<T>: a key carries its value type, so reads and writes are checked at compile time, and value types are stored without boxing.
Define your keys once as static fields:
using GroveGames.BehaviourTree.Collections;
public static class BlackboardKeys
{
public static readonly BlackboardKey<int> Health = new("health");
public static readonly BlackboardKey<float> DetectionRange = new("detection_range");
public static readonly BlackboardKey<ITarget> Target = new("target");
}
Then read and write values through the keys:
var blackboard = new Blackboard();
blackboard.SetValue(BlackboardKeys.Health, 100);
blackboard.SetValue(BlackboardKeys.DetectionRange, 12.5f);
if (blackboard.TryGetValue(BlackboardKeys.Target, out var target))
{
// use target
}
blackboard.DeleteValue(BlackboardKeys.Target);
blackboard.Clear();
Creating Action Nodes
Inherit from BehaviourNode and implement Evaluate. Every node can reach the tree's blackboard through the Blackboard property:
using GroveGames.BehaviourTree.Collections;
using GroveGames.BehaviourTree.Nodes;
public sealed class Attack : BehaviourNode
{
public Attack(string? name = null) : base(name)
{
}
public override NodeState Evaluate(float deltaTime)
{
if (!Blackboard.TryGetValue(BlackboardKeys.Target, out var target))
{
return _nodeState = NodeState.Failure;
}
// attack the target...
return _nodeState = NodeState.Running;
}
}
Building a Tree
Inherit from BehaviourTree and override SetupTree to describe the structure with the fluent API:
using GroveGames.BehaviourTree;
using GroveGames.BehaviourTree.Nodes;
using GroveGames.BehaviourTree.Nodes.Composites;
using GroveGames.BehaviourTree.Nodes.Decorators;
public sealed class CharacterBT : BehaviourTree
{
public CharacterBT(IRoot root) : base(root)
{
}
public override void SetupTree()
{
var selector = Root.Selector();
selector
.Conditional(() => IsEnemyVisible())
.Cooldown(1f)
.Repeater(RepeatMode.UntilSuccess)
.Attach(new Attack());
selector
.Conditional(() => IsUnderAttack())
.Cooldown(1f)
.Repeater(RepeatMode.UntilSuccess)
.Attach(new Defend());
}
}
graph TD
Root(Root) --> Selector
Selector --> Conditional1["Conditional: IsEnemyVisible"]
Conditional1 --> Cooldown1["Cooldown 1s"]
Cooldown1 --> Repeater1["Repeater: UntilSuccess"]
Repeater1 --> Attack
Selector --> Conditional2["Conditional: IsUnderAttack"]
Conditional2 --> Cooldown2["Cooldown 1s"]
Cooldown2 --> Repeater2["Repeater: UntilSuccess"]
Repeater2 --> Defend
Running the Tree
Create a root with a blackboard, set up the tree, enable it, and tick it from your game loop:
var blackboard = new Blackboard();
var tree = new CharacterBT(new BehaviourRoot(blackboard));
tree.SetupTree();
tree.Enable();
// in your game loop:
tree.Tick(deltaTime);
// control at runtime:
tree.Abort(); // abort the running branch
tree.Disable(); // stop evaluating
tree.Reset(); // reset node states
Unity Integration
Quick Start
Derive from BehaviourTreeMono, seed the blackboard, and return your tree from CreateTree. The base component creates the blackboard and the tree in Awake; you drive ticking:
using GroveGames.BehaviourTree;
using GroveGames.BehaviourTree.Collections;
using GroveGames.BehaviourTree.Nodes;
using GroveGames.BehaviourTree.Unity;
using UnityEngine;
public sealed class EnemyBehaviourTree : BehaviourTreeMono
{
[SerializeField] private Transform _target;
[SerializeField] private float _detectionRange = 10f;
protected override BehaviourTree CreateTree(Blackboard blackboard)
{
blackboard.SetValue(BlackboardKeys.Target, _target);
blackboard.SetValue(BlackboardKeys.DetectionRange, _detectionRange);
blackboard.SetValue(BlackboardKeys.HomePosition, transform.position);
return new EnemyBT(new BehaviourRoot(blackboard));
}
private void Start()
{
Tree.SetupTree();
Tree.Enable();
}
private void Update()
{
Tree.Tick(Time.deltaTime);
}
private void OnDestroy()
{
Tree?.Disable();
}
}
using GroveGames.BehaviourTree.Collections;
using UnityEngine;
public static class BlackboardKeys
{
public static readonly BlackboardKey<Transform> Target = new("target");
public static readonly BlackboardKey<float> DetectionRange = new("detection_range");
public static readonly BlackboardKey<Vector3> HomePosition = new("home_position");
}
Dependency Injection
For agents that receive their services at runtime, skip the automatic Awake setup and build the tree in an Initialize method instead. The blackboard doubles as a lightweight service container — reference types are stored in a single lookup, and value types still avoid boxing:
using GroveGames.BehaviourTree;
using GroveGames.BehaviourTree.Collections;
using GroveGames.BehaviourTree.Nodes;
using GroveGames.BehaviourTree.Unity;
using UnityEngine;
public sealed class AgentBehaviourTree : BehaviourTreeMono
{
public void Initialize(IPathfinder pathfinder, ITargetSelector targetSelector, float detectionRange)
{
var blackboard = new Blackboard();
blackboard.SetValue(BlackboardKeys.Pathfinder, pathfinder);
blackboard.SetValue(BlackboardKeys.TargetSelector, targetSelector);
blackboard.SetValue(BlackboardKeys.DetectionRange, detectionRange);
blackboard.SetValue(BlackboardKeys.HomePosition, transform.position);
_tree = CreateTree(blackboard);
_tree.SetupTree();
_tree.Enable();
}
protected override BehaviourTree CreateTree(Blackboard blackboard)
{
return new AgentBT(new BehaviourRoot(blackboard));
}
protected override void Awake()
{
// Intentionally empty: the tree is created in Initialize with injected dependencies.
}
public void EnableAI()
{
_tree?.Enable();
}
public void DisableAI()
{
_tree?.Abort();
_tree?.Disable();
}
private void Update()
{
_tree?.Tick(Time.deltaTime);
}
private void OnDestroy()
{
_tree?.Disable();
}
}
Visual Debugger
Open Tools → GroveGames → Behaviour Tree Debugger to inspect trees live. In Play Mode the window lists every BehaviourTreeMono in the scene by its hierarchy path; selecting one shows the full tree with:
- Node states colored in real time (Running / Success / Failure / Idle)
- The active evaluation path highlighted, with evaluation order numbers
- A scrollable canvas for large trees
Godot Integration
Derive your tree from GodotBehaviourTree (a Godot.Node), set a root, and tick it from _Process:
public partial class Character : Godot.Node
{
private CharacterBT _characterBT;
public override void _Ready()
{
_characterBT = new CharacterBT();
_characterBT.SetRoot(new BehaviourRoot(new Blackboard()));
_characterBT.SetupTree();
_characterBT.Enable();
AddChild(_characterBT);
}
public override void _Process(double delta)
{
_characterBT.Tick((float)delta);
}
}
With the addon enabled, node states are displayed under the editor's Debugger tab so you can track the tree's evaluation frame by frame.
Customization
Extend the framework by creating custom nodes:
- Action nodes: inherit from
BehaviourNodeand implement the behaviour inEvaluate. - Decorator nodes: inherit from
Decoratorto modify the behaviour of a single child. - Composite nodes: inherit from
Compositeto define logic over multiple children.
Example: Custom Decorator
A TimeLimit decorator that fails its child if it runs longer than a given duration:
using GroveGames.BehaviourTree.Nodes;
using GroveGames.BehaviourTree.Nodes.Decorators;
public sealed class TimeLimit : Decorator
{
private readonly float _maxDuration;
private float _elapsed;
public TimeLimit(float maxDuration, string? name = null) : base(name)
{
_maxDuration = maxDuration;
}
public override NodeState Evaluate(float deltaTime)
{
_elapsed += deltaTime;
if (_elapsed >= _maxDuration)
{
_elapsed = 0f;
return _nodeState = NodeState.Failure;
}
return _nodeState = base.Evaluate(deltaTime);
}
public override void Reset()
{
base.Reset();
_elapsed = 0f;
}
}
Contributing
Contributions are welcome! To contribute:
- Fork the repository.
- Create a new branch (
git checkout -b feature/your-feature). - Commit your changes (
git commit -am 'Add new feature'). - Push the branch (
git push origin feature/your-feature). - Open a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on GroveGames.BehaviourTree:
| Package | Downloads |
|---|---|
|
GroveGames.BehaviourTree.Godot
A lightweight behaviour tree framework developed by Grove Games for .NET and Godot |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.1 | 46 | 7/13/2026 |
| 1.1.0 | 182 | 2/11/2026 |
| 1.0.3 | 135 | 2/11/2026 |
| 1.0.2 | 143 | 1/27/2026 |
| 1.0.1 | 128 | 1/22/2026 |
| 1.0.0 | 125 | 1/22/2026 |
| 0.4.11 | 128 | 1/22/2026 |
| 0.4.10 | 238 | 9/12/2025 |
| 0.4.9 | 822 | 1/22/2025 |
| 0.4.8 | 277 | 1/21/2025 |
| 0.4.7 | 559 | 1/10/2025 |
| 0.4.6 | 216 | 12/26/2024 |
| 0.4.5 | 337 | 12/11/2024 |
| 0.4.4 | 201 | 12/10/2024 |
| 0.4.3 | 199 | 12/10/2024 |
| 0.4.2 | 550 | 11/21/2024 |
| 0.4.1 | 192 | 11/21/2024 |
| 0.4.0 | 208 | 11/14/2024 |
| 0.3.10 | 265 | 11/12/2024 |
| 0.3.9 | 223 | 11/12/2024 |