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.
Designs and implements behavior tree structures for game AI systems across multiple engines.
npx claudepluginhub a5c-ai/babysitterThis skill is limited to using the following tools:
README.mdComprehensive behavior tree design and implementation for game AI systems, supporting multiple engines and frameworks.
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.
# Install via Package Manager or Asset Store
# Node Canvas, Behavior Designer, or similar
// Enable AI Module in Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"AIModule",
"GameplayTasks"
});
# Install via Asset Library
Beehave or LimboAI
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)
// BehaviorTree.cs
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)
)
);
}
}
// BTNode.cs
public abstract class BTNode
{
public enum NodeState { Running, Success, Failure }
public NodeState State { get; protected set; }
public abstract NodeState Execute(Blackboard blackboard);
}
// Selector.cs
public class Selector : BTNode
{
private readonly BTNode[] _children;
public Selector(params BTNode[] children)
{
_children = children;
}
public override NodeState Execute(Blackboard blackboard)
{
foreach (var child in _children)
{
var state = child.Execute(blackboard);
if (state != NodeState.Failure)
{
State = state;
return State;
}
}
State = NodeState.Failure;
return State;
}
}
// Sequence.cs
public class Sequence : BTNode
{
private readonly BTNode[] _children;
private int _currentIndex;
public Sequence(params BTNode[] children)
{
_children = children;
}
public override NodeState Execute(Blackboard blackboard)
{
while (_currentIndex < _children.Length)
{
var state = _children[_currentIndex].Execute(blackboard);
if (state == NodeState.Failure)
{
_currentIndex = 0;
State = NodeState.Failure;
return State;
}
if (state == NodeState.Running)
{
State = NodeState.Running;
return State;
}
_currentIndex++;
}
_currentIndex = 0;
State = NodeState.Success;
return State;
}
}
// Blackboard.cs
public class Blackboard
{
private readonly Dictionary<string, object> _data = new();
public void Set<T>(string key, T value) => _data[key] = value;
public T Get<T>(string key) => _data.TryGetValue(key, out var value) ? (T)value : default;
public bool Has(string key) => _data.ContainsKey(key);
public void Remove(string key) => _data.Remove(key);
}
// BTTask_AttackTarget.h
#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;
};
// BTTask_AttackTarget.cpp
#include "BTTask_AttackTarget.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_AttackTarget::UBTTask_AttackTarget()
{
NodeName = "Attack Target";
bNotifyTick = true;
}
EBTNodeResult::Type UBTTask_AttackTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* AIController = OwnerComp.GetAIOwner();
if (!AIController)
{
return EBTNodeResult::Failed;
}
UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
AActor* TargetActor = Cast<AActor>(BlackboardComp->GetValueAsObject(TargetKey.SelectedKeyName));
if (!TargetActor)
{
return EBTNodeResult::Failed;
}
// Perform attack logic
// ...
return EBTNodeResult::Succeeded;
}
// BTService_UpdateTargetLocation.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "BTService_UpdateTargetLocation.generated.h"
UCLASS()
class MYGAME_API UBTService_UpdateTargetLocation : public UBTService
{
GENERATED_BODY()
public:
UBTService_UpdateTargetLocation();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetKey;
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetLocationKey;
};
// BTDecorator_InRange.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDecorator_InRange.generated.h"
UCLASS()
class MYGAME_API UBTDecorator_InRange : public UBTDecorator
{
GENERATED_BODY()
public:
UBTDecorator_InRange();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
UPROPERTY(EditAnywhere, Category = "Range")
float AcceptableRadius = 200.0f;
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector TargetKey;
};
# 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
const behaviorTreeTask = defineTask({
name: 'behavior-tree-design',
description: 'Design and implement behavior tree for AI',
inputs: {
engine: { type: 'string', required: true }, // unity, unreal, godot
aiType: { type: 'string', required: true }, // enemy, npc, companion
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: 'design_tree',
engine: inputs.engine,
aiType: inputs.aiType,
behaviors: inputs.behaviors,
outputPath: inputs.outputPath
}
},
io: {
inputJsonPath: `tasks/${taskCtx.effectId}/input.json`,
outputJsonPath: `tasks/${taskCtx.effectId}/result.json`
}
};
}
});
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
Selector
├── Sequence [Investigate disturbance]
│ ├── Condition: HeardNoise
│ ├── Action: MoveToNoiseLocation
│ └── Action: LookAround
├── Sequence [Patrol]
│ ├── Action: MoveToNextPatrolPoint
│ ├── Action: Wait(2s)
│ └── Action: AdvancePatrolIndex
└── Action: Idle
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
| 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 |
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.