Behaviour tree guard (Unity 6 / C#)
A minimal, package-free behaviour tree and a guard NPC that uses it. Companion code for behaviour-trees. Plain C# in two files; nothing to install.
Files
| Script | Responsibility |
|---|---|
BehaviourTree.cs | The framework: NodeState, BTNode, SequenceNode, SelectorNode, InverterNode, ConditionNode, ActionNode (about 100 lines) |
GuardAI.cs | Builds the guard’s tree in Awake, ticks it in Update, and holds the conditions and actions it calls |
The tree
Selector "Root" (first branch that does not fail wins)
├── Sequence "Flee when hurt"
│ ├── Condition Health low?
│ └── Action Flee (Running; regenerates health)
├── Sequence "Attack when close"
│ ├── Condition Player in reach?
│ ├── Inverter ── Condition Cooling down?
│ └── Action Strike (Success)
├── Sequence "Chase when seen"
│ ├── Condition Can see player?
│ └── Action Chase (Running)
└── Action "Patrol" (Running)
The tree is ticked from the root every frame and keeps no memory of which branch ran last, so a higher-priority branch interrupts a lower one the moment its condition comes true.
Scene setup (about 10 minutes)
- New 3D scene. Add a Plane at the origin for the floor.
- Player: add a Capsule, name it
Player, place it at (6, 1, 6). It has no script: you move it by dragging it in the Scene view during Play mode. - Waypoints: create three or four empty GameObjects (
WP1,WP2, …) spread around the plane at y = 1. - Guard: add another Capsule, name it
Guard, place it at (0, 1, 0) and addGuardAI. DragPlayerinto Player and the waypoints into Waypoints in order. - Optional walls: add some Cubes, put them on a new layer called
Walls, and set Obstacle Mask on the guard toWalls. The guard can no longer see you through them. - Press Play. Arrange the Game and Scene views side by side, select the guard to see its view cone (white), view radius (yellow) and reach (red), and watch Current Action in the Inspector. The capsule changes colour with the action: green patrol, yellow chase, red strike, cyan flee.
What to try
- Drag the player into the guard’s view cone, then behind it. Why does the guard stop chasing as soon as you are behind it?
- Stand within reach. Strikes are logged once per Attack Cooldown; between strikes the Inverter fails the attack branch and the guard falls through to Chase, which simply holds its distance.
- Right-click the
GuardAIheader and choose Take 25 damage three times (or drag the Health slider below 30). The guard flees and regenerates; it only returns to the fight above 60. Set Recover Above to 30 to see why two thresholds are used. - Move the “Chase when seen” branch above “Attack when close” in
Awake. What breaks, and why does order matter in a selector?
Extension ideas
- Memory: store the player’s last seen position and add a “Search last known position” branch between Chase and Patrol.
- Blackboard: move shared values (target, last seen position, health) into a
small
Blackboardclass that nodes read, instead of fields onGuardAI. - Wait node: write a
WaitNodedecorator or leaf that returns Running for N seconds, and make the guard pause at each waypoint. - Debug view: give each node a
LastStatefield and draw the tree withOnGUIso you can watch which branch is running. - NavMesh: replace
MoveTowardswith aNavMeshAgent(AI Navigation package) so the guard paths around walls. - Production tool: rebuild the same guard with Unity’s Behavior package
(
com.unity.behavior), a graph-based behaviour tree editor with runtime debugging, and compare the two.