Game AI Systems

v1.0.0

Game AI development guide covering behavior trees, state machines, pathfinding, and decision-making systems. Use when implementing enemy AI, NPC behaviors, f...

3· 957· 1 versions· 10 current· 11 all-time· Updated 20h ago· MIT-0
byMr.Tang@thb32133451

Install

openclaw skills install game-ai

游戏 AI 开发指南

AI 架构选择

架构对比

有限状态机 (FSM):
  优点: 简单、直观、易于调试
  缺点: 状态多时难以维护
  适用: 简单敌人、Boss 阶段
  
行为树 (BT):
  优点: 模块化、可复用、易扩展
  缺点: 需要框架支持
  适用: 复杂 NPC、战术 AI
  
效用系统 (Utility AI):
  优点: 灵活、智能决策
  缺点: 参数调节困难
  适用: 模拟人生类、策略游戏
  
GOAP:
  优点: 目标导向、动态规划
  缺点: 计算成本高
  适用: 智能敌人、复杂行为

有限状态机 (FSM)

基础实现

public enum EnemyState
{
    Idle,
    Patrol,
    Chase,
    Attack,
    Flee
}

public class EnemyFSM : Node
{
    private EnemyState _currentState;
    private Dictionary<EnemyState, IState> _states;
    
    public override void _Ready()
    {
        _states = new Dictionary<EnemyState, IState>
        {
            [EnemyState.Idle] = new IdleState(this),
            [EnemyState.Patrol] = new PatrolState(this),
            [EnemyState.Chase] = new ChaseState(this),
            [EnemyState.Attack] = new AttackState(this),
            [EnemyState.Flee] = new FleeState(this)
        };
        
        ChangeState(EnemyState.Idle);
    }
    
    public void ChangeState(EnemyState newState)
    {
        _states[_currentState]?.Exit();
        _currentState = newState;
        _states[_currentState]?.Enter();
    }
    
    public override void _Process(double delta)
    {
        _states[_currentState]?.Update(delta);
    }
}

public interface IState
{
    void Enter();
    void Update(double delta);
    void Exit();
}

public class ChaseState : IState
{
    private readonly EnemyFSM _fsm;
    private readonly CharacterBody2D _owner;
    private NavigationAgent2D _navAgent;
    
    public ChaseState(EnemyFSM fsm)
    {
        _fsm = fsm;
        _owner = fsm.GetParent<CharacterBody2D>();
        _navAgent = _owner.GetNode<NavigationAgent2D>("NavigationAgent2D");
    }
    
    public void Enter()
    {
        // 开始追逐动画
        _owner.GetNode<AnimationPlayer>("AnimationPlayer").Play("run");
    }
    
    public void Update(double delta)
    {
        var player = GetTree().GetFirstNodeInGroup("player") as Node2D;
        if (player == null) return;
        
        // 更新目标位置
        _navAgent.TargetPosition = player.GlobalPosition;
        
        // 移动向玩家
        var direction = _owner.ToLocal(_navAgent.GetNextPathPosition()).Normalized();
        _owner.Velocity = direction * _owner.Speed;
        _owner.MoveAndSlide();
        
        // 检查距离
        float distance = _owner.GlobalPosition.DistanceTo(player.GlobalPosition);
        if (distance < 50)
        {
            _fsm.ChangeState(EnemyState.Attack);
        }
        else if (distance > 500)
        {
            _fsm.ChangeState(EnemyState.Patrol);
        }
    }
    
    public void Exit()
    {
        _owner.Velocity = Vector2.Zero;
    }
}

行为树 (Behavior Tree)

节点类型

复合节点:
  Sequence: 顺序执行(一个失败则失败)
  Selector: 选择执行(一个成功则成功)
  Parallel: 并行执行
  
装饰节点:
  Inverter: 反转结果
  Repeater: 重复执行
  UntilFail: 直到失败
  
叶子节点:
  Condition: 条件检查
  Action: 执行动作

行为树实现

public enum NodeStatus
{
    Running,
    Success,
    Failure
}

public abstract class BTNode
{
    public abstract NodeStatus Execute();
}

public class Sequence : BTNode
{
    private readonly List<BTNode> _children = new();
    private int _currentChild = 0;
    
    public Sequence(params BTNode[] children)
    {
        _children.AddRange(children);
    }
    
    public override NodeStatus Execute()
    {
        while (_currentChild < _children.Count)
        {
            var status = _children[_currentChild].Execute();
            
            if (status == NodeStatus.Running)
                return NodeStatus.Running;
            
            if (status == NodeStatus.Failure)
            {
                _currentChild = 0;
                return NodeStatus.Failure;
            }
            
            _currentChild++;
        }
        
        _currentChild = 0;
        return NodeStatus.Success;
    }
}

public class Selector : BTNode
{
    private readonly List<BTNode> _children = new();
    private int _currentChild = 0;
    
    public Selector(params BTNode[] children)
    {
        _children.AddRange(children);
    }
    
    public override NodeStatus Execute()
    {
        while (_currentChild < _children.Count)
        {
            var status = _children[_currentChild].Execute();
            
            if (status == NodeStatus.Running)
                return NodeStatus.Running;
            
            if (status == NodeStatus.Success)
            {
                _currentChild = 0;
                return NodeStatus.Success;
            }
            
            _currentChild++;
        }
        
        _currentChild = 0;
        return NodeStatus.Failure;
    }
}

// 使用示例
public class EnemyAI : Node
{
    private BTNode _behaviorTree;
    
    public override void _Ready()
    {
        _behaviorTree = new Selector(
            // 优先攻击
            new Sequence(
                new Condition(IsPlayerInRange),
                new Condition(HasAmmo),
                new Action(Attack)
            ),
            // 追逐玩家
            new Sequence(
                new Condition(CanSeePlayer),
                new Action(ChasePlayer)
            ),
            // 巡逻
            new Action(Patrol)
        );
    }
    
    public override void _Process(double delta)
    {
        _behaviorTree.Execute();
    }
}

寻路算法

A* 算法

public class AStar
{
    private readonly Grid _grid;
    
    public List<Point> FindPath(Point start, Point end)
    {
        var openSet = new PriorityQueue<Point>();
        var cameFrom = new Dictionary<Point, Point>();
        var gScore = new Dictionary<Point, float>();
        var fScore = new Dictionary<Point, float>();
        
        openSet.Enqueue(start, 0);
        gScore[start] = 0;
        fScore[start] = Heuristic(start, end);
        
        while (openSet.Count > 0)
        {
            var current = openSet.Dequeue();
            
            if (current == end)
                return ReconstructPath(cameFrom, current);
            
            foreach (var neighbor in _grid.GetNeighbors(current))
            {
                float tentativeGScore = gScore[current] + _grid.GetCost(current, neighbor);
                
                if (!gScore.ContainsKey(neighbor) || tentativeGScore < gScore[neighbor])
                {
                    cameFrom[neighbor] = current;
                    gScore[neighbor] = tentativeGScore;
                    fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, end);
                    
                    if (!openSet.Contains(neighbor))
                        openSet.Enqueue(neighbor, fScore[neighbor]);
                }
            }
        }
        
        return null; // 无路径
    }
    
    private float Heuristic(Point a, Point b)
    {
        // 曼哈顿距离
        return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y);
    }
    
    private List<Point> ReconstructPath(Dictionary<Point, Point> cameFrom, Point current)
    {
        var path = new List<Point> { current };
        
        while (cameFrom.ContainsKey(current))
        {
            current = cameFrom[current];
            path.Insert(0, current);
        }
        
        return path;
    }
}

Godot NavigationAgent2D

public class EnemyNavigation : CharacterBody2D
{
    private NavigationAgent2D _navAgent;
    private Node2D _target;
    
    public override void _Ready()
    {
        _navAgent = GetNode<NavigationAgent2D>("NavigationAgent2D");
        
        // 配置
        _navAgent.PathDesiredDistance = 10.0f;
        _navAgent.TargetDesiredDistance = 10.0f;
        _navAgent.Radius = 20.0f;
        
        // 连接信号
        _navAgent.VelocityComputed += OnVelocityComputed;
    }
    
    public void SetTarget(Node2D target)
    {
        _target = target;
        UpdatePath();
    }
    
    private async void UpdatePath()
    {
        if (_target == null) return;
        
        // 异步计算路径
        _navAgent.TargetPosition = _target.GlobalPosition;
        await ToSignal(_navAgent, NavigationAgent2D.SignalName.PathChanged);
    }
    
    public override void _PhysicsProcess(double delta)
    {
        if (_navAgent.IsNavigationFinished())
            return;
        
        var nextPos = _navAgent.GetNextPathPosition();
        var direction = GlobalPosition.DirectionTo(nextPos);
        var velocity = direction * Speed;
        
        // 避障
        _navAgent.Velocity = velocity;
    }
    
    private void OnVelocityComputed(Vector2 safeVelocity)
    {
        Velocity = safeVelocity;
        MoveAndSlide();
    }
}

群体行为

Boids 算法

public class Boid : Node2D
{
    private Vector2 _velocity;
    private List<Boid> _neighbors = new();
    
    public override void _Process(double delta)
    {
        // 更新邻居列表
        UpdateNeighbors();
        
        // 计算三个力
        var separation = CalculateSeparation();
        var alignment = CalculateAlignment();
        var cohesion = CalculateCohesion();
        
        // 合并力
        var acceleration = separation * 1.5f + alignment * 1.0f + cohesion * 1.0f;
        
        // 更新速度和位置
        _velocity += acceleration * (float)delta;
        _velocity = _velocity.LimitLength(MaxSpeed);
        
        Position += _velocity * (float)delta;
    }
    
    private Vector2 CalculateSeparation()
    {
        var steer = Vector2.Zero;
        int count = 0;
        
        foreach (var neighbor in _neighbors)
        {
            float distance = Position.DistanceTo(neighbor.Position);
            if (distance > 0 && distance < SeparationRadius)
            {
                var diff = Position - neighbor.Position;
                diff = diff.Normalized() / distance;
                steer += diff;
                count++;
            }
        }
        
        if (count > 0)
            steer /= count;
        
        return steer;
    }
    
    private Vector2 CalculateAlignment()
    {
        var avgVelocity = Vector2.Zero;
        int count = 0;
        
        foreach (var neighbor in _neighbors)
        {
            if (Position.DistanceTo(neighbor.Position) < AlignmentRadius)
            {
                avgVelocity += neighbor._velocity;
                count++;
            }
        }
        
        if (count > 0)
        {
            avgVelocity /= count;
            avgVelocity = avgVelocity.Normalized() * MaxSpeed;
            return avgVelocity - _velocity;
        }
        
        return Vector2.Zero;
    }
    
    private Vector2 CalculateCohesion()
    {
        var center = Vector2.Zero;
        int count = 0;
        
        foreach (var neighbor in _neighbors)
        {
            if (Position.DistanceTo(neighbor.Position) < CohesionRadius)
            {
                center += neighbor.Position;
                count++;
            }
        }
        
        if (count > 0)
        {
            center /= count;
            return (center - Position).Normalized() * MaxSpeed - _velocity;
        }
        
        return Vector2.Zero;
    }
}

决策系统

效用 AI

public class UtilityAI
{
    private readonly List<UtilityAction> _actions = new();
    
    public void AddAction(UtilityAction action)
    {
        _actions.Add(action);
    }
    
    public UtilityAction ChooseBestAction()
    {
        float bestScore = float.MinValue;
        UtilityAction bestAction = null;
        
        foreach (var action in _actions)
        {
            float score = action.CalculateScore();
            
            // 添加随机性
            score += GD.Randf() * 0.1f;
            
            if (score > bestScore)
            {
                bestScore = score;
                bestAction = action;
            }
        }
        
        return bestAction;
    }
}

public abstract class UtilityAction
{
    public abstract float CalculateScore();
    public abstract void Execute();
}

public class AttackAction : UtilityAction
{
    private readonly Enemy _enemy;
    
    public AttackAction(Enemy enemy)
    {
        _enemy = enemy;
    }
    
    public override float CalculateScore()
    {
        float score = 0f;
        
        // 玩家距离近,分数高
        float distance = _enemy.DistanceToPlayer();
        score += Mathf.Clamp(1.0f - distance / 200.0f, 0, 1) * 0.5f;
        
        // 有弹药,分数高
        score += _enemy.HasAmmo ? 0.3f : 0.0f;
        
        // 生命值高,更倾向于攻击
        score += _enemy.HealthPercent * 0.2f;
        
        return score;
    }
    
    public override void Execute()
    {
        _enemy.Attack();
    }
}

感知系统

视觉感知

public class VisionSensor : Node2D
{
    [Export] public float ViewDistance = 300.0f;
    [Export] public float ViewAngle = 90.0f;
    [Export] public LayerMask VisionLayer;
    
    public Node2D Target { get; private set; }
    
    public override void _Process(double delta)
    {
        Target = null;
        
        var players = GetTree().GetNodesInGroup("player");
        foreach (Node2D player in players)
        {
            if (CanSee(player))
            {
                Target = player;
                break;
            }
        }
    }
    
    private bool CanSee(Node2D target)
    {
        var direction = target.GlobalPosition - GlobalPosition;
        float distance = direction.Length();
        
        // 检查距离
        if (distance > ViewDistance)
            return false;
        
        // 检查角度
        float angle = Mathf.RadToDeg(direction.Angle());
        float angleDiff = Mathf.Abs(Mathf.AngleDifference(angle, GlobalRotationDegrees));
        if (angleDiff > ViewAngle / 2)
            return false;
        
        // 射线检测
        var space = GetWorld2D().DirectSpaceState;
        var query = new PhysicsRayQueryParameters2D
        {
            From = GlobalPosition,
            To = target.GlobalPosition,
            CollisionMask = VisionLayer
        };
        
        var result = space.IntersectRay(query);
        if (result.Count > 0)
        {
            var collider = result["collider"].As<Node>();
            return collider == target;
        }
        
        return true;
    }
    
    // 可视化调试
    public override void _Draw()
    {
        // 绘制视野扇形
        DrawArc(Vector2.Zero, ViewDistance, -ViewAngle / 2, ViewAngle / 2, 32, Colors.Yellow, 1.0f);
    }
}

参考资源

Version tags

latestvk97fb2sxwncgfxwv3g2d3w5pmd828agq