| name | behavior-trees |
| description | Behavior tree design and implementation skill for game AI. Enables creation of behavior tree structures, custom nodes, decorators, composites, and integration with game engines for NPC and enemy AI systems. |
| allowed-tools | Read, Grep, Write, Bash, Edit, Glob, WebFetch |
| graph | {"domains":["domain:gaming"],"specializations":["specialization:game-development"],"skillAreas":["skill-area:game-ai-pathfinding","skill-area:gameplay-programming"],"roles":["role:game-developer"]} |
Behavior Trees Skill
Comprehensive behavior tree design and implementation for game AI systems, supporting multiple engines and frameworks.
Overview
This skill provides capabilities for designing and implementing behavior trees for game AI. It covers the creation of tree structures, custom nodes, blackboard systems, and integration with Unity, Unreal Engine, and Godot behavior tree implementations.
Capabilities
Tree Design
- Design behavior tree structures from specifications
- Create hierarchical AI behaviors
- Balance between reactive and goal-oriented behaviors
- Optimize tree execution for performance
Node Types
- Composite Nodes: Sequence, Selector, Parallel, Random
- Decorator Nodes: Inverter, Repeater, Cooldown, Conditional
- Leaf Nodes: Actions, Conditions, Services
Blackboard System
- Design blackboard schemas
- Implement blackboard observers
- Manage shared AI state
- Handle blackboard key types
Engine Integration
- Unity: NodeCanvas, Behavior Designer, custom implementations
- Unreal: Behavior Tree Editor, custom tasks and services
- Godot: Beehave, LimboAI, custom implementations
Debugging
- Tree visualization
- Node state tracking
- Execution logging
- Performance profiling
Prerequisites
Unity (Node Canvas)
Unreal Engine (Built-in)
PublicDependencyModuleNames.AddRange(new string[] {
"AIModule",
"GameplayTasks"
});
Godot (Beehave)
# Install via Asset Library
Beehave or LimboAI
Usage Patterns
Basic Behavior Tree Structure
Root
โโโ Selector (Try behaviors until one succeeds)
โโโ Sequence (Attack if possible)
โ โโโ Condition: HasTarget
โ โโโ Condition: InAttackRange
โ โโโ Action: Attack
โโโ Sequence (Chase target)
โ โโโ Condition: HasTarget
โ โโโ Decorator: Cooldown(0.5s)
โ โ โโโ Action: MoveToTarget
โ โโโ Service: UpdateTargetLocation
โโโ Sequence (Patrol)
โโโ Action: MoveToPatrolPoint
โโโ Action: Wait(2s)
Unity Implementation (Custom)
public class BehaviorTree : MonoBehaviour
{
private BTNode _root;
private Blackboard _blackboard;
private void Start()
{
_blackboard = new Blackboard();
_root = BuildTree();
}
private void Update()
{
_root?.Execute(_blackboard);
}
private BTNode BuildTree()
{
return new Selector(
new Sequence(
new HasTargetCondition(),
new InRangeCondition(attackRange: 2f),
new AttackAction()
),
new Sequence(
new HasTargetCondition(),
new Cooldown(0.5f,
new MoveToTargetAction()
)
),
new Sequence(
new PatrolAction(),
new WaitAction(2f)
)
);
}
}
public abstract class BTNode
{
public enum NodeState { Running, Success, Failure }
public NodeState State { get; protected set; }
;
}
:
{
BTNode[] _children;
{
_children = children;
}
{
( child _children)
{
state = child.Execute(blackboard);
(state != NodeState.Failure)
{
State = state;
State;
}
}
State = NodeState.Failure;
State;
}
}
:
{
BTNode[] _children;
_currentIndex;
{
_children = children;
}
{
(_currentIndex < _children.Length)
{
state = _children[_currentIndex].Execute(blackboard);
(state == NodeState.Failure)
{
_currentIndex = ;
State = NodeState.Failure;
State;
}
(state == NodeState.Running)
{
State = NodeState.Running;
State;
}
_currentIndex++;
}
_currentIndex = ;
State = NodeState.Success;
State;
}
}
{
Dictionary<, > _data = ();
=> _data[key] = ;
=> _data.TryGetValue(key, ) ? (T) : ;
=> _data.ContainsKey(key);
=> _data.Remove(key);
}
Unreal Engine Implementation (C++)
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_AttackTarget.generated.h"
UCLASS()
class MYGAME_API UBTTask_AttackTarget : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_AttackTarget();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
protected:
UPROPERTY(EditAnywhere, Category = "Attack")
float AttackDamage = 10.0f;
UPROPERTY(EditAnywhere, Category = "Attack")
float AttackDuration = 1.0f;
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetKey;
};
#include "BTTask_AttackTarget.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_AttackTarget::UBTTask_AttackTarget()
{
NodeName = "Attack Target";
bNotifyTick = true;
}
EBTNodeResult::Type
{
AAIController* AIController = OwnerComp.();
(!AIController)
{
EBTNodeResult::Failed;
}
UBlackboardComponent* BlackboardComp = OwnerComp.();
AActor* TargetActor = <AActor>(BlackboardComp->(TargetKey.SelectedKeyName));
(!TargetActor)
{
EBTNodeResult::Failed;
}
EBTNodeResult::Succeeded;
}
()
UBTService_UpdateTargetLocation : UBTService
{
()
:
();
:
;
(EditAnywhere, Category = )
FBlackboardKeySelector TargetKey;
(EditAnywhere, Category = )
FBlackboardKeySelector TargetLocationKey;
};
()
UBTDecorator_InRange : UBTDecorator
{
()
:
();
:
;
(EditAnywhere, Category = )
AcceptableRadius = ;
(EditAnywhere, Category = )
FBlackboardKeySelector TargetKey;
};
Godot Implementation (GDScript with Beehave)
# enemy_ai.gd
extends CharacterBody2D
@onready var behavior_tree: BeehaveTree = $BeehaveTree
@onready var blackboard: Blackboard = $Blackboard
func _ready() -> void:
blackboard.set_value("patrol_points", $PatrolPoints.get_children())
blackboard.set_value("current_patrol_index", 0)
# has_target_condition.gd
extends ConditionLeaf
class_name HasTargetCondition
func tick(actor: Node, blackboard: Blackboard) -> int:
var target = blackboard.get_value("target")
if target != null and is_instance_valid(target):
return SUCCESS
return FAILURE
# in_attack_range_condition.gd
extends ConditionLeaf
class_name InAttackRangeCondition
@export var attack_range: float = 50.0
func tick(actor: Node, blackboard: Blackboard) -> int:
var target = blackboard.get_value("target")
if target == null:
return FAILURE
var distance = actor.global_position.distance_to(target.global_position)
if distance <= attack_range:
return SUCCESS
return FAILURE
# attack_action.gd
extends ActionLeaf
class_name AttackAction
@export var damage: int = 10
@export var attack_duration: float = 0.5
var _attack_timer: float = 0.0
var _is_attacking: bool = false
func tick(actor: Node, blackboard: Blackboard) -> int:
if not _is_attacking:
_start_attack(actor, blackboard)
return RUNNING
_attack_timer -= get_process_delta_time()
if _attack_timer <= 0:
_finish_attack(actor, blackboard)
return SUCCESS
return RUNNING
func _start_attack(actor: Node, blackboard: Blackboard) -> void:
_is_attacking = true
_attack_timer = attack_duration
# Play attack animation, etc.
func _finish_attack(actor: Node, blackboard: Blackboard) -> void:
_is_attacking = false
var target = blackboard.get_value("target")
if target and target.has_method("take_damage"):
target.take_damage(damage)
# move_to_target_action.gd
extends ActionLeaf
class_name MoveToTargetAction
@export var move_speed: float = 100.0
@export var arrival_distance: float = 10.0
func tick(actor: Node, blackboard: Blackboard) -> int:
var target = blackboard.get_value("target")
if target == null:
return FAILURE
var target_pos = target.global_position
var distance = actor.global_position.distance_to(target_pos)
if distance <= arrival_distance:
return SUCCESS
var direction = (target_pos - actor.global_position).normalized()
actor.velocity = direction * move_speed
actor.move_and_slide()
return RUNNING
Integration with Babysitter SDK
Task Definition Example
const behaviorTreeTask = defineTask({
name: 'behavior-tree-design',
description: 'Design and implement behavior tree for AI',
inputs: {
engine: { type: 'string', required: true },
aiType: { type: 'string', required: true },
behaviors: { type: 'array', required: true },
outputPath: { type: 'string', required: true }
},
outputs: {
treePath: { type: 'string' },
nodeFiles: { type: 'array' },
success: { type: 'boolean' }
},
async run(inputs, taskCtx) {
return {
kind: 'skill',
title: `Design behavior tree for ${inputs.aiType}`,
skill: {
name: 'behavior-trees',
context: {
operation: ,
: inputs.,
: inputs.,
: inputs.,
: inputs.
}
},
: {
: ,
:
}
};
}
});
Common Behavior Patterns
Combat AI
Selector
โโโ Sequence [Flee if low health]
โ โโโ Condition: HealthBelowThreshold(20%)
โ โโโ Action: FleeFromTarget
โโโ Sequence [Attack in range]
โ โโโ Condition: HasTarget
โ โโโ Condition: InAttackRange
โ โโโ Action: Attack
โโโ Sequence [Approach target]
โ โโโ Condition: HasTarget
โ โโโ Action: MoveToTarget
โโโ Action: SearchForTarget
Patrol AI
Selector
โโโ Sequence [Investigate disturbance]
โ โโโ Condition: HeardNoise
โ โโโ Action: MoveToNoiseLocation
โ โโโ Action: LookAround
โโโ Sequence [Patrol]
โ โโโ Action: MoveToNextPatrolPoint
โ โโโ Action: Wait(2s)
โ โโโ Action: AdvancePatrolIndex
โโโ Action: Idle
Companion AI
Selector
โโโ Sequence [Help player in combat]
โ โโโ Condition: PlayerInCombat
โ โโโ Condition: HasTarget
โ โโโ Action: AttackPlayerTarget
โโโ Sequence [Heal player]
โ โโโ Condition: PlayerHealthLow
โ โโโ Condition: HasHealAbility
โ โโโ Action: HealPlayer
โโโ Sequence [Follow player]
โ โโโ Condition: TooFarFromPlayer
โ โโโ Action: MoveToPlayer
โโโ Action: IdleNearPlayer
Best Practices
- Keep Trees Shallow: Deep trees are harder to debug and maintain
- Use Services: Update blackboard values in services, not conditions
- Fail Fast: Put cheap conditions before expensive ones
- Blackboard Keys: Use typed keys and validate at design time
- Modular Nodes: Create reusable, single-purpose nodes
- Debug Visualization: Always implement tree visualization for debugging
Performance Considerations
| Optimization | Description |
|---|
| Conditional Aborts | Stop lower-priority branches when conditions change |
| Service Intervals | Don't update every frame if not needed |
| Blackboard Observers | React to changes instead of polling |
| Node Pooling | Reuse node instances for dynamic trees |
References