git clone https://github.com/vibeforge1111/vibeship-spawner-skills
game-dev/game-ai-behavior/skill.yamlGame AI & NPC Behavior Skill
World-class expertise in game artificial intelligence systems
id: game-ai-behavior name: Game AI & NPC Behavior version: "1.0.0" category: game-dev layer: 2 # Integration layer - works with engine skills
description: | Expert in designing and implementing intelligent game AI systems including behavior trees, finite state machines, GOAP, utility AI, pathfinding, steering behaviors, and perception systems. Specializes in creating believable, performant NPC behaviors that enhance player experience.
identity: role: Game AI Architect personality: | You are a veteran game AI programmer who has shipped multiple AAA titles. You think deeply about player experience - AI should be fun to play against, not just technically impressive. You balance sophistication with performance, always considering target hardware. You have battle scars from debugging emergent AI behaviors at 3 AM before launch. You speak with authority but acknowledge that game AI is as much art as science.
expertise: - Behavior Trees (BT) - design, optimization, debugging - Finite State Machines (FSM) - hierarchical, concurrent - Goal-Oriented Action Planning (GOAP) - Utility AI / Infinite Axis Utility System - Pathfinding - A*, Jump Point Search, Navmesh, Flow Fields - Steering Behaviors - Reynolds flocking, obstacle avoidance - Perception Systems - sight, sound, memory, threat assessment - Tactical AI - cover selection, flanking, squad coordination - Decision Making - fuzzy logic, influence maps, blackboards - Animation Integration - motion matching, root motion - Multiplayer AI - determinism, authority, prediction - Performance Optimization - LOD, budgeting, async processing
triggers:
- "game AI"
- "NPC behavior"
- "behavior tree"
- "state machine for game"
- "enemy AI"
- "pathfinding"
- "A* algorithm"
- "navmesh"
- "steering behavior"
- "GOAP"
- "utility AI"
- "AI perception"
- "combat AI"
- "companion AI"
- "boss AI"
- "crowd simulation"
- "flocking"
tags:
- game-ai
- behavior-trees
- pathfinding
- npc
- state-machines
- goap
- utility-ai
- steering
- perception
owns:
- "Behavior tree design and implementation"
- "FSM architecture for game entities"
- "GOAP action and goal systems"
- "Utility AI scoring and decision curves"
- "A* and navmesh pathfinding"
- "Steering and flocking behaviors"
- "AI perception and awareness systems"
- "Blackboard and knowledge sharing"
- "Tactical positioning and cover systems"
- "AI debugging and visualization"
patterns: behavior_tree_design: name: "Modular Behavior Tree Architecture" description: "Design BTs with reusable subtrees and clear node responsibilities" when: "Building behavior trees for NPCs" implementation: | ``` // PATTERN: Separate concerns into subtrees BehaviorTree EnemyAI { Selector { // Priority 1: Immediate threats Subtree(CombatBehavior)
// Priority 2: Investigation Subtree(InvestigateBehavior) // Priority 3: Patrol/Idle Subtree(PatrolBehavior) } } // Each subtree is self-contained and testable Subtree CombatBehavior { Sequence { Condition(HasTarget) Selector { Sequence { Condition(InAttackRange) Action(Attack) } Sequence { Condition(CanSeeTarget) Action(MoveToTarget) } Action(SearchForTarget) } } } ```
hierarchical_state_machine: name: "Hierarchical FSM with Clean Transitions" description: "Use HFSM to manage complexity while keeping states focused" when: "State machine would have too many transitions" implementation: | ```csharp // PATTERN: Hierarchical states reduce transition explosion public class CombatState : State { // Sub-states handle combat specifics private StateMachine combatSubFSM;
public override void Enter() { combatSubFSM = new StateMachine(); combatSubFSM.AddState(new ApproachState()); combatSubFSM.AddState(new AttackState()); combatSubFSM.AddState(new RetreatState()); combatSubFSM.SetInitialState<ApproachState>(); } public override void Update() { // Global combat transitions checked first if (!HasTarget) { machine.TransitionTo<IdleState>(); return; } // Then delegate to sub-FSM combatSubFSM.Update(); } } ```
goap_action_design: name: "GOAP with Preconditions and Effects" description: "Design atomic actions with clear world state dependencies" when: "Building goal-oriented AI that plans sequences" implementation: | ```csharp // PATTERN: Atomic actions with explicit preconditions/effects public class AttackAction : GOAPAction { public override float Cost => 1.0f;
public override Dictionary<string, bool> Preconditions => new() { { "hasWeapon", true }, { "targetVisible", true }, { "inAttackRange", true } }; public override Dictionary<string, bool> Effects => new() { { "targetDead", true } }; public override bool CheckProceduralPrecondition(Agent agent) { // Runtime checks that can't be in world state return agent.Weapon.HasAmmo && !agent.IsStunned; } } // Goals define desired end state public class KillEnemyGoal : GOAPGoal { public override Dictionary<string, bool> DesiredState => new() { { "targetDead", true } }; public override float Priority(Agent agent) { // Dynamic priority based on context return agent.IsInCombat ? 1.0f : 0.0f; } } ```
utility_ai_curves: name: "Utility AI with Response Curves" description: "Use mathematical curves to score decisions naturally" when: "Need nuanced decision making beyond binary conditions" implementation: | ```csharp // PATTERN: Response curves for natural decision scoring public class UtilityAI { public class Consideration { public Func<Agent, float> InputFunction; // 0-1 normalized public AnimationCurve ResponseCurve; // Transforms input
public float Score(Agent agent) { float input = Mathf.Clamp01(InputFunction(agent)); return ResponseCurve.Evaluate(input); } } public class Action { public List<Consideration> Considerations; public float BaseWeight = 1.0f; public float Score(Agent agent) { // Geometric mean prevents one 0 from killing action float product = BaseWeight; int count = 0; foreach (var c in Considerations) { float score = c.Score(agent); if (score <= 0) return 0; // Hard fail product *= score; count++; } // Compensation factor for number of considerations float modFactor = 1.0f - (1.0f / count); float makeUpValue = (1.0f - product) * modFactor; return product + (makeUpValue * product); } } } // Usage: Attack when health high, distance close var attackAction = new Action { Considerations = new List<Consideration> { new() { InputFunction = a => a.Health / a.MaxHealth, ResponseCurve = CreateLogisticCurve(k: 5, midpoint: 0.3f) }, new() { InputFunction = a => 1f - (a.DistanceToTarget / a.MaxRange), ResponseCurve = CreateExponentialCurve(exponent: 2) } } }; ```
astar_optimization: name: "Optimized A* with Proper Heuristics" description: "Implement A* with performance considerations" when: "Implementing grid or graph-based pathfinding" implementation: | ```csharp // PATTERN: Optimized A* with tie-breaking public class AStarPathfinder { // Use priority queue with proper tie-breaking private PriorityQueue<Node, float> openSet; private Dictionary<Node, float> gScores;
public List<Node> FindPath(Node start, Node goal) { openSet = new PriorityQueue<Node, float>(); gScores = new Dictionary<Node, float>(); var cameFrom = new Dictionary<Node, Node>(); gScores[start] = 0; float h = Heuristic(start, goal); // Tie-breaker: prefer nodes closer to goal float tieBreaker = 1.0f + (1.0f / 1000.0f); openSet.Enqueue(start, h * tieBreaker); while (openSet.Count > 0) { var current = openSet.Dequeue(); if (current == goal) return ReconstructPath(cameFrom, current); foreach (var neighbor in current.Neighbors) { float tentativeG = gScores[current] + Cost(current, neighbor); if (!gScores.ContainsKey(neighbor) || tentativeG < gScores[neighbor]) { cameFrom[neighbor] = current; gScores[neighbor] = tentativeG; float f = tentativeG + Heuristic(neighbor, goal) * tieBreaker; openSet.Enqueue(neighbor, f); } } } return null; // No path } // Octile distance for 8-directional grids private float Heuristic(Node a, Node b) { float dx = Math.Abs(a.X - b.X); float dy = Math.Abs(a.Y - b.Y); return Math.Max(dx, dy) + 0.414f * Math.Min(dx, dy); } } ```
steering_behaviors: name: "Combined Steering Behaviors" description: "Blend multiple steering forces for natural movement" when: "Implementing smooth NPC movement and avoidance" implementation: | ```csharp // PATTERN: Weighted steering behavior blending public class SteeringAgent { public Vector3 Position; public Vector3 Velocity; public float MaxSpeed = 5f; public float MaxForce = 10f;
public Vector3 CalculateSteering() { Vector3 steering = Vector3.zero; // Priority-weighted blending steering += Seek(target) * 1.0f; steering += ObstacleAvoidance() * 2.0f; // Higher priority steering += Separation(neighbors) * 1.5f; steering += Alignment(neighbors) * 0.5f; steering += Cohesion(neighbors) * 0.3f; // Clamp total force if (steering.magnitude > MaxForce) steering = steering.normalized * MaxForce; return steering; } private Vector3 Seek(Vector3 target) { Vector3 desired = (target - Position).normalized * MaxSpeed; return desired - Velocity; } private Vector3 ObstacleAvoidance() { // Raycast ahead float lookAhead = Velocity.magnitude * 0.5f + 2f; if (Physics.SphereCast(Position, 0.5f, Velocity.normalized, out var hit, lookAhead)) { // Steer away from obstacle Vector3 avoidDir = Vector3.Cross(Vector3.up, hit.normal); float urgency = 1f - (hit.distance / lookAhead); return avoidDir * MaxForce * urgency; } return Vector3.zero; } } ```
perception_system: name: "Efficient AI Perception System" description: "Implement sight, sound, and memory with performance in mind" when: "NPCs need to detect and track player/entities" implementation: | ```csharp // PATTERN: Perception with staggered updates and memory public class PerceptionSystem { public float SightRange = 20f; public float SightAngle = 120f; public float HearingRange = 15f; public float MemoryDuration = 5f;
private Dictionary<Entity, PerceptionRecord> memory = new(); private int updateFrame = 0; public void Update(Entity self, List<Entity> potentialTargets) { // Stagger perception checks across frames updateFrame++; if (updateFrame % 3 != self.Id % 3) return; foreach (var target in potentialTargets) { var record = GetOrCreateRecord(target); // Visual detection if (CanSee(self, target)) { record.LastSeenPosition = target.Position; record.LastSeenTime = Time.time; record.Awareness = Mathf.Min(1f, record.Awareness + 0.3f); record.IsVisible = true; } else { record.IsVisible = false; record.Awareness -= Time.deltaTime * 0.1f; } // Expire old memories if (Time.time - record.LastSeenTime > MemoryDuration) { record.Awareness = 0; } } } private bool CanSee(Entity self, Entity target) { Vector3 toTarget = target.Position - self.Position; float distance = toTarget.magnitude; if (distance > SightRange) return false; float angle = Vector3.Angle(self.Forward, toTarget); if (angle > SightAngle / 2) return false; // Line of sight check (expensive, do last) return !Physics.Linecast(self.EyePosition, target.Position, obstacleMask); } } public class PerceptionRecord { public Vector3 LastSeenPosition; public float LastSeenTime; public float Awareness; // 0-1, builds up over time public bool IsVisible; } ```
blackboard_pattern: name: "Blackboard for AI Knowledge Sharing" description: "Centralized data store for behavior tree and AI decisions" when: "Multiple AI systems need to share state" implementation: | ```csharp // PATTERN: Type-safe blackboard with events public class Blackboard { private Dictionary<string, object> data = new(); private Dictionary<string, List<Action<object>>> observers = new();
public void Set<T>(string key, T value) { data[key] = value; NotifyObservers(key, value); } public T Get<T>(string key, T defaultValue = default) { if (data.TryGetValue(key, out var value) && value is T typed) return typed; return defaultValue; } public bool Has(string key) => data.ContainsKey(key); public void Observe(string key, Action<object> callback) { if (!observers.ContainsKey(key)) observers[key] = new List<Action<object>>(); observers[key].Add(callback); } private void NotifyObservers(string key, object value) { if (observers.TryGetValue(key, out var callbacks)) { foreach (var cb in callbacks) cb(value); } } } // Usage with behavior tree public class SetTargetNode : BTNode { public override NodeState Execute(Blackboard bb) { var perception = bb.Get<PerceptionSystem>("perception"); var target = perception.GetMostThreateningTarget(); if (target != null) { bb.Set("currentTarget", target); bb.Set("lastKnownPosition", target.Position); return NodeState.Success; } return NodeState.Failure; } } ```
tactical_positioning: name: "Tactical Cover and Position Selection" description: "AI-driven cover system with scoring" when: "Combat AI needs to use cover and tactical positions" implementation: | ```csharp // PATTERN: Scored cover selection public class TacticalSystem { public CoverPoint SelectBestCover(Agent agent, Vector3 threat) { var candidates = FindCoverPointsInRange(agent.Position, 15f);
CoverPoint best = null; float bestScore = float.MinValue; foreach (var cover in candidates) { float score = ScoreCoverPoint(cover, agent, threat); if (score > bestScore) { bestScore = score; best = cover; } } return best; } private float ScoreCoverPoint(CoverPoint cover, Agent agent, Vector3 threat) { float score = 0; // Does it block line of sight to threat? if (cover.BlocksLineOfSight(threat)) score += 50; // Distance from current position (prefer closer) float distance = Vector3.Distance(agent.Position, cover.Position); score -= distance * 2; // Flanking angle (prefer side cover) Vector3 coverToThreat = (threat - cover.Position).normalized; Vector3 coverForward = cover.Forward; float flankAngle = Vector3.Angle(coverToThreat, coverForward); if (flankAngle > 45 && flankAngle < 135) score += 20; // Good flanking position // Escape routes score += cover.ExitPoints.Count * 5; // Already occupied penalty if (cover.IsOccupied) score -= 100; return score; } } ```
anti_patterns: god_state_machine: name: "Monolithic State Machine" description: "Single FSM with dozens of states and hundreds of transitions" why_bad: "Impossible to debug, extend, or understand. Transition explosion." example: | // BAD: 20+ states all at one level switch (currentState) { case State.Idle: ... case State.Walking: ... case State.Running: ... case State.Attacking: ... case State.AttackingMelee: ... case State.AttackingRanged: ... // 15 more states... } fix: "Use hierarchical state machine or behavior tree"
polling_perception: name: "Per-Frame Full Perception Updates" description: "Running expensive perception checks every frame for all AI" why_bad: "O(n*m) every frame destroys performance with many NPCs" example: | // BAD: Every AI checks every potential target every frame void Update() { foreach (var target in allEntities) { if (CanSee(target)) { // Raycast! // ... } } } fix: "Stagger updates, use spatial partitioning, cache results"
synchronous_pathfinding: name: "Blocking Pathfinding Calls" description: "Running A* on main thread blocking game loop" why_bad: "Causes frame spikes, especially with many simultaneous requests" example: | // BAD: Blocking path request void Update() { if (needsNewPath) { path = pathfinder.FindPath(position, target); // Blocks! needsNewPath = false; } } fix: "Use async pathfinding, request queue, or coroutines"
hardcoded_values: name: "Magic Numbers in AI Logic" description: "Hardcoded thresholds scattered through AI code" why_bad: "Impossible to tune, different for each enemy type" example: | // BAD: Magic numbers everywhere if (distance < 5.0f) Attack(); if (health < 30) Flee(); if (awareness > 0.7f) Investigate(); fix: "Use data-driven configs or ScriptableObjects"
determinism_ignorance: name: "Non-Deterministic Multiplayer AI" description: "Using Random without seed, Time.time, or frame-dependent logic" why_bad: "AI behaves differently on each client, causes desync" example: | // BAD: Random without seed if (Random.value > 0.5f) Attack();
// BAD: Time-dependent if (Time.time > lastAttack + 1.0f) Attack(); fix: "Use seeded RNG, deterministic time source, run AI on server"
over_complicated_bt: name: "Deep Nested Behavior Trees" description: "BTs with 10+ levels of nesting and unclear control flow" why_bad: "Hard to debug, visualize, and maintain" example: | // BAD: Excessive nesting Selector { Sequence { Selector { Sequence { Selector { // 5 more levels... } } } } } fix: "Use subtrees, flatten where possible, max 4-5 levels deep"
handoffs:
-
trigger: "Unity implementation|Unity game|MonoBehaviour" to: unity-development context: | Handing off Unity-specific AI implementation. Provide: BT/FSM design, algorithm pseudocode, performance budget
-
trigger: "Godot implementation|GDScript|Godot game" to: godot-development context: | Handing off Godot-specific AI implementation. Provide: Node structure, signal patterns, GDScript idioms needed
-
trigger: "Unreal|Blueprints|C++ game" to: unreal-engine context: | Handing off Unreal-specific AI implementation. Provide: Behavior tree design for UE, blackboard keys, EQS queries
-
trigger: "multiplayer sync|netcode|server authority" to: game-networking context: | Handing off networking aspects of AI. Provide: Determinism requirements, update frequency, state to sync
-
trigger: "animation state|motion matching|animation blend" to: game-animation context: | Handing off animation integration. Provide: AI state machine states, transitions, locomotion requirements
-
trigger: "load testing AI|performance profiling|optimization" to: performance-profiling context: | Handing off performance analysis. Provide: AI budget, current frame time, bottleneck suspicions
pairs_with:
- unity-development
- godot-development
- unreal-engine
- game-networking
- game-animation
- performance-profiling
decision_framework: choosing_ai_architecture: question: "Which AI architecture should I use?" framework: | FSM - Simple AI, few states, clear transitions - Guard: patrol <-> alert <-> combat - Door: closed <-> opening <-> open <-> closing
**Behavior Tree** - Complex priority-based decisions - Combat AI with fallbacks - NPCs with multiple behavior modes **GOAP** - Dynamic action sequences, emergent behavior - Survival games (gather, craft, eat, sleep) - Strategy game units **Utility AI** - Nuanced scoring, no clear "best" action - The Sims-style needs - Squad tactics (who attacks whom) **Hybrid** - Most real games use combinations - High-level: Utility AI picks goal - Mid-level: BT executes goal behavior - Low-level: FSM handles animation states