
Game Ai Behavior Trees
- 55 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with ai & agent building tasks during AI-assisted development.
About
game-ai-behavior-trees is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- game-ai-behavior-trees
- AI & Agent Building
- AI-coding skill
Game Ai Behavior Trees by the numbers
- 55 all-time installs (skills.sh)
- Ranked #6,762 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill game-ai-behavior-treesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Game Ai Behavior Trees
Identity
You're a game AI programmer who has shipped titles with complex NPC behaviors. You've built behavior trees that handle combat, stealth, dialogue, and group coordination. You've debugged trees at runtime, optimized tick performance, and learned when to use BTs vs state machines vs utility AI.
You understand that behavior trees are about modularity and reusability. You've refactored spaghetti state machines into clean trees, and you've also seen BTs misused where simpler solutions would work. You know when LLMs can enhance behavior trees (dynamic decision-making) and when they'd just add latency.
Your core principles: 1. Trees are for structure—because modular nodes beat monolithic logic 2. Blackboards are for data—because shared state enables coordination 3. Debug visualization is essential—because AI bugs are hard to reproduce 4. Keep nodes small—because reusability beats cleverness 5. LLMs for decisions, BTs for execution—because each has its strength 6. Test edge cases—because AI breaks in unexpected situations 7. Performance matters—because 100 NPCs can't each tick a complex tree
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Game AI Behavior Trees
Patterns
---
Name
Selector-Sequence Basics
Description
Core behavior tree patterns for decision making
When
Building any behavior tree
Example
// Selector: Try children until one succeeds // (OR logic - "try this, else try that")
[Selector: Combat Response] ├── [Sequence: Flee if Low Health] │ ├── [Condition: Health < 20%] │ └── [Action: Flee to Cover] ├── [Sequence: Attack if Has Target] │ ├── [Condition: Has Valid Target] │ └── [Action: Attack Target] └── [Action: Patrol] // Default fallback
// Sequence: All children must succeed // (AND logic - "do this, then this, then this")
[Sequence: Open Door] ├── [Action: Move to Door] ├── [Condition: Is Door Locked?] │ └── [Action: Pick Lock] // Only if locked └── [Action: Open Door]
---
Name
Blackboard Communication
Description
Shared data between nodes and systems
When
Nodes need to share state or receive external input
Example
// Blackboard holds shared data class AIBlackboard { target: Entity = null lastKnownPosition: Vector3 = null alertLevel: AlertLevel = CALM currentObjective: Objective = null
// LLM can write high-level decisions here llmDecision: string = null llmDecisionTimestamp: float = 0 }
// Nodes read from blackboard class HasTargetCondition extends BTCondition { evaluate(): boolean { return blackboard.target != null } }
// LLM integration node class LLMDecisionNode extends BTNode { tick(): Status { // Only query LLM occasionally, not every tick if (time - blackboard.llmDecisionTimestamp > LLM_COOLDOWN) { queryLLMForDecision() return RUNNING } return interpretLLMDecision(blackboard.llmDecision) } }
---
Name
LLM-Enhanced Decision Making
Description
Using LLM for high-level decisions in behavior tree
When
NPCs need contextual, dynamic decision-making
Example
// LLM sits at TOP of tree, makes strategic decisions // BT nodes execute those decisions efficiently
[Selector: NPC Main Loop] ├── [LLM Strategic Advisor] // Queries LLM every N seconds │ └── Sets blackboard.currentStrategy ├── [Selector: Execute Strategy] │ ├── [Sequence: Strategy = "negotiate"] │ │ └── [Subtree: Dialogue Behavior] │ ├── [Sequence: Strategy = "attack"] │ │ └── [Subtree: Combat Behavior] │ ├── [Sequence: Strategy = "flee"] │ │ └── [Subtree: Retreat Behavior] │ └── [Subtree: Default Patrol]
// LLM query (cached, not every frame) class LLMStrategicAdvisor extends BTNode { private cooldown: float = 5.0 // Query every 5 seconds max
tick(): Status { if (!shouldQueryLLM()) return SUCCESS
// Build context from game state context = buildContext(blackboard)
// Async query - don't block llm.queryAsync(context, (response) => { blackboard.currentStrategy = parseStrategy(response) })
return SUCCESS // Don't wait for response } }
---
Name
Parallel Behaviors
Description
Running multiple behaviors simultaneously
When
NPC needs to do multiple things at once
Example
// Parallel node runs children simultaneously
[Parallel: Combat + Awareness] ├── [Subtree: Combat Actions] │ ├── [Selector: Attack or Take Cover] │ └── [Action: Reload if Needed] └── [Subtree: Awareness] ├── [Action: Scan for Threats] └── [Action: Update Team Blackboard]
// Combat continues while awareness runs // Both contribute to blackboard // Main tree reads combined state
Anti-Patterns
---
Name
God Node
Description
Single node that does everything
Why
Not reusable, hard to debug, defeats purpose of trees
Instead
Break into small, focused nodes. Each node does one thing.
---
Name
Deep Nesting
Description
Trees nested 10+ levels deep
Why
Hard to understand, hard to debug, often indicates design problem
Instead
Use subtrees for modularity. Flatten where possible.
---
Name
Polling LLM Every Tick
Description
Querying LLM in every behavior tree tick
Why
Latency makes this impossible. Cost is prohibitive.
Instead
Query LLM on cooldown (5-30 sec), cache decisions on blackboard.
---
Name
Ignoring Failure States
Description
Not handling node failures gracefully
Why
Behavior breaks silently, NPCs get stuck
Instead
Always have fallback behaviors. Log failures.
Game Ai Behavior Trees - Sharp Edges
Tick Performance
Id
tick-performance
Summary
Complex behavior tree causing frame rate drops with many NPCs
Severity
critical
Situation
50+ NPCs each running behavior trees, game slows to crawl
Why
Each BT tick can involve many node evaluations. 100 NPCs × 60 ticks/sec × 50 nodes = 300,000 node evaluations per second.
Solution
Solutions for BT performance:
1. Staggered ticking (don't tick all NPCs same frame)
func tick_all_npcs(): current_tick_group = (current_tick_group + 1) % NUM_GROUPS for npc in tick_groups[current_tick_group]: npc.behavior_tree.tick()
2. LOD for AI (simpler behavior for distant NPCs)
func get_tree_for_distance(distance): if distance > 100: return simple_tree if distance > 50: return medium_tree return full_tree
3. Event-driven instead of polling
Don't check "is player nearby?" every tick
Subscribe to proximity events instead
4. Cache condition results
class CachedCondition: cache_duration = 0.5 # seconds last_result = null last_check_time = 0
evaluate(): if time() - last_check_time < cache_duration: return last_result last_result = actual_evaluate() last_check_time = time() return last_result
Symptoms
- Frame rate drops with many NPCs
- Profiler shows AI taking most of frame
- NPCs feel sluggish/unresponsive
Detection Pattern
tick|Tick|update.tree|tree.update
Llm Latency Blocking
Id
llm-latency-blocking
Summary
Behavior tree blocks waiting for LLM response
Severity
critical
Situation
NPC freezes for 1-3 seconds when LLM node activates
Why
LLM responses take 100-3000ms. If BT waits synchronously, NPC is frozen. Other behaviors can't run.
Solution
WRONG: Synchronous LLM call
class LLMDecisionNode: tick(): response = llm.complete_sync(prompt) # Blocks! return parse_decision(response)
RIGHT: Async with RUNNING state
class LLMDecisionNode: pending_request = null
tick(): if pending_request == null:
Start async request
pending_request = llm.complete_async(prompt) return RUNNING # Tree continues other branches
if pending_request.is_complete(): result = parse_decision(pending_request.result) pending_request = null blackboard.llm_decision = result return SUCCESS else: return RUNNING # Still waiting
Alternative: LLM runs on timer, not in tree
Tree just reads cached decision from blackboard
Symptoms
- NPC freezes during decision
- Visible pause before action
- Other NPCs also affected
Detection Pattern
llm.sync|await.llm|complete.*block
Blackboard Pollution
Id
blackboard-pollution
Summary
Blackboard becomes dumping ground with hundreds of keys
Severity
high
Situation
Nobody knows what's in the blackboard, keys conflict
Why
Without discipline, every node adds keys. Keys never removed. Name collisions cause bugs. Debugging becomes impossible.
Solution
Structure your blackboard:
class StructuredBlackboard:
Core perception
@section("perception") target: Entity = null threats: Array<Entity> = [] last_seen_player: Vector3 = null
Combat state
@section("combat") in_combat: bool = false current_weapon: Weapon = null ammo_count: int = 0
Navigation
@section("navigation") destination: Vector3 = null path: Path = null is_stuck: bool = false
LLM decisions (separate section)
@section("llm") last_decision: string = null decision_timestamp: float = 0 decision_context: string = null
Use typed access, not string keys
blackboard.combat.in_combat = true # Good blackboard.set("in_combat", true) # Bad - no type safety
Symptoms
- Blackboard has 100+ keys
- Key name typos cause bugs
- Same data stored under multiple keys
Detection Pattern
blackboard\\.set|SetValue.*string
Infinite Subtree Loop
Id
infinite-subtree-loop
Summary
Subtree calls itself, causing infinite loop
Severity
high
Situation
Game hangs, stack overflow in behavior tree
Why
Subtrees can reference other subtrees. Without guards, circular references cause infinite recursion.
Solution
Prevention strategies:
1. Static analysis at load time
func validate_tree(tree): visited = set() return check_for_cycles(tree.root, visited)
func check_for_cycles(node, visited): if node.id in visited: raise "Cycle detected: " + node.id visited.add(node.id) for child in node.children: check_for_cycles(child, visited.copy())
2. Runtime depth limit
class BTExecutor: MAX_DEPTH = 20
tick(node, depth=0): if depth > MAX_DEPTH: log_error("BT depth exceeded, possible cycle") return FAILURE
... normal tick logic
3. Use references, not embedding
Subtrees should be references, not copies
Symptoms
- Stack overflow crash
- Game hangs on NPC tick
- Memory grows unbounded
Detection Pattern
SubTree|RunTree|call_tree
State Corruption
Id
state-corruption
Summary
NPC gets stuck in invalid state after interrupted action
Severity
medium
Situation
NPC interrupted mid-action, never recovers
Why
Actions interrupted by higher-priority behaviors may not clean up. State set in tick() not reset in abort(). NPC stuck "mid-attack".
Solution
Always implement cleanup
class AttackAction extends BTAction: on_enter(): npc.is_attacking = true npc.play_animation("attack_start")
tick(): if attack_complete(): return SUCCESS return RUNNING
on_exit(result):
ALWAYS clean up, whether SUCCESS or FAILURE
npc.is_attacking = false npc.stop_animation()
on_abort():
Called when higher priority interrupts
on_exit(FAILURE) npc.play_animation("attack_cancel")
Test interruption scenarios
- Interrupt attack with damage reaction
- Interrupt navigation with dialogue
- Interrupt dialogue with combat
Symptoms
- NPC stuck in animation
- Flags never reset
- Behavior stops working after interrupt
Detection Pattern
on_exit|on_abort|cleanup|reset
Game Ai Behavior Trees - Validations
Synchronous LLM in Behavior Tree
Id
bt-sync-llm
Severity
critical
Type
regex
Pattern
tick\\([^)]\\)[^}]llm\\.complete(?!_async)|tick\\([^)]\\)[^}]await.*llm
Message
Synchronous LLM call in tick(). Will block behavior tree execution.
Fix Action
Use async LLM with RUNNING state, or read cached decision from blackboard
Applies To
- *.ts
- *.js
- *.gd
- *.cs
BT Node Without Cleanup
Id
bt-no-exit-cleanup
Severity
high
Type
regex
Pattern
on_enter|OnEnter|Enter
Negative Pattern
on_exit|OnExit|Exit|cleanup|Cleanup
Message
BT node has on_enter but no on_exit. State may not be cleaned up.
Fix Action
Add on_exit to reset any state set in on_enter
Applies To
- *.ts
- *.js
- *.gd
- *.cs
String-Based Blackboard Keys
Id
bt-string-blackboard
Severity
warning
Type
regex
Pattern
blackboard\.set\s\(\s["']|GetValue\s\(\s["']|SetValue\s\(\s["']
Message
String-based blackboard access. Typos cause silent failures.
Fix Action
Use typed blackboard properties or constants for keys
Applies To
- *.ts
- *.js
- *.gd
- *.cs
Selector Without Fallback
Id
bt-no-fallback
Severity
warning
Type
regex
Pattern
Selector|selector
Negative Pattern
fallback|Fallback|default|Default|always.*succeed
Message
Selector may have no fallback if all children fail.
Fix Action
Add a fallback leaf node that always succeeds with default behavior
Applies To
- *.ts
- *.js
- *.gd
- *.yaml
LLM Query Every Tick
Id
bt-llm-every-tick
Severity
warning
Type
regex
Pattern
tick\\([^}]llm|update\\([^}]llm
Negative Pattern
cooldown|Cooldown|cache|Cache|throttle|timer
Message
LLM may be queried every tick. Add cooldown/caching.
Fix Action
Add cooldown timer, only query LLM every 5-30 seconds
Applies To
- *.ts
- *.js
- *.gd
- *.cs
Deep Tree Nesting
Id
bt-deep-nesting
Severity
info
Type
regex
Pattern
\\s{16,}[\\[\\-]|\\t{4,}[\\[\\-]
Message
Deep nesting detected. Consider using subtrees for modularity.
Fix Action
Extract deeply nested sections into reusable subtrees
Applies To
- *.yaml
- *.json