Unity横版清关游戏AI优化求助:复刻经典巡逻-追逐-攻击逻辑
复刻经典横版清关游戏敌人AI行为系统实现方案
一、状态机重构:明确核心状态与转换逻辑
基于你现有的非MonoBehaviour状态机,需扩展为以下核心状态,每个状态包含进入逻辑、更新逻辑、退出逻辑,状态转换由状态机管理器根据实时感知数据触发:
- 巡逻(Patrol):无玩家威胁时的默认状态
- 追逐(Chase):检测到玩家后切换
- 攻击(Attack):玩家进入攻击范围且满足攻击条件时切换
- 受击/硬直(HitStun):被玩家攻击时中断当前状态
- 返回巡逻(ReturnPatrol):玩家超出追逐范围后返回巡逻起点
状态转换规则示例:
- Patrol → Chase:玩家进入敌人视野范围且无障碍物阻挡
- Chase → Attack:玩家进入攻击判定范围,且敌人不在冷却/硬直状态
- Chase → Patrol:玩家超出追逐最大范围超过2秒
- Attack → Chase:攻击动作完成且玩家仍在视野内但超出攻击范围
二、巡逻逻辑:复刻经典固定路径/区域往返模式
替代当前随机选点逻辑,采用符合复古Beat-Em-Up风格的巡逻机制:
- 固定路径巡逻:为每个敌人预设巡逻点数组(如
Vector2[] patrolPoints),敌人按顺序在点之间移动,到达最后一个点后反向遍历数组;每个点停留0.5-1秒(可配置),模拟敌人“巡逻观察”的行为 - 区域巡逻:设定巡逻矩形区域,敌人在区域内随机选点,但仅在当前位置的左右方向移动(符合横版游戏的横向限制),到达边界后转向
- 巡逻时添加视野检测:每帧用射线检测(2D环境用
Physics2D.Raycast)或触发器判断前方是否有玩家,射线长度设为巡逻视野范围(如5-8个单位),需检测是否有障碍物(如墙壁、箱子)阻挡视线
三、追逐逻辑:结合A* Pathfinding实现流畅追踪
利用A* Pathfinding Project免费版实现横版适配的追逐行为:
- A*配置优化:
- 启用2D网格模式,将敌人的
AIPath组件移动速度设为巡逻速度的1.5-2倍,区分巡逻与追逐的移动节奏 - 关闭
AIPath的自动旋转(横版敌人仅左右翻转),手动控制Sprite翻转:根据当前移动方向与玩家位置的X轴差值切换
- 启用2D网格模式,将敌人的
- 实时目标更新:
- 追逐状态下,每0.1秒(而非0.5秒)更新A*的目标点为玩家当前位置,避免移动生硬
- 添加追逐距离阈值:当玩家超出最大追逐范围(如15个单位),触发返回巡逻状态
- 障碍物处理:
- 确保A*网格正确标记可通行区域,敌人追逐时会自动绕开障碍物;若玩家被障碍物完全阻挡且超出视野,敌人会回到最近的巡逻点
四、攻击逻辑:还原复古游戏的攻击节奏与判定
攻击逻辑需贴合经典游戏的“前摇-攻击-后摇”循环:
- 攻击触发条件:
- 玩家处于攻击判定范围内(如X轴距离≤1.5单位,Y轴高度差≤0.5单位,适配横版平面)
- 敌人不在攻击冷却、硬直状态
- 玩家在敌人前方(左右方向匹配)
- 攻击动作控制:
- 攻击状态下,先播放攻击前摇动画(0.2-0.3秒),前摇结束时触发攻击判定(用
Collider2D或射线检测命中玩家) - 攻击完成后进入后摇状态(0.1-0.2秒),期间无法移动或再次攻击
- 设置攻击冷却时间(1-1.5秒),避免敌人无限连打
- 攻击状态下,先播放攻击前摇动画(0.2-0.3秒),前摇结束时触发攻击判定(用
- 攻击类型扩展:可添加轻击、重击两种攻击模式,轻击冷却短、伤害低,重击冷却长、伤害高,敌人可根据与玩家的距离随机选择(近距离用重击,中距离用轻击)
五、非MonoBehaviour状态机适配要点
由于你的状态机是非MonoBehaviour实现,需注意:
- 每个状态类需持有敌人的核心组件引用(如
Transform、AIPath、Animator),通过状态机管理器注入 - 状态更新逻辑需绑定到Unity的
Update或FixedUpdate循环,可通过状态机管理器的单例调用每个状态的Update()方法 - 感知数据(玩家距离、视野检测结果)需由状态机管理器统一收集,传递给当前状态用于判断转换条件
代码片段示例(状态机核心逻辑)
// 状态枚举 public enum EnemyAIState { Patrol, Chase, Attack, HitStun } // 状态机管理器(非MonoBehaviour) public class EnemyStateMachine { private IEnemyState _currentState; public EnemyData EnemyData { get; private set; } // 持有敌人的Transform、AIPath等数据 public void Initialize(EnemyData data) { EnemyData = data; SwitchState(EnemyAIState.Patrol); } public void Update() { _currentState?.Update(); CheckStateTransitions(); } private void SwitchState(EnemyAIState newState) { _currentState?.Exit(); _currentState = CreateState(newState); _currentState.Enter(); } // 状态转换判断逻辑 private void CheckStateTransitions() { float playerDistance = Vector2.Distance(EnemyData.Transform.position, EnemyData.PlayerTransform.position); bool isPlayerInSight = EnemyData.IsPlayerInSight(); if (EnemyData.IsHitStun) { SwitchState(EnemyAIState.HitStun); return; } if (isPlayerInSight) { if (playerDistance <= EnemyData.AttackRange) { SwitchState(EnemyAIState.Attack); } else { SwitchState(EnemyAIState.Chase); } } else { SwitchState(EnemyAIState.Patrol); } } private IEnemyState CreateState(EnemyAIState stateType) { switch (stateType) { case EnemyAIState.Patrol: return new PatrolState(this); case EnemyAIState.Chase: return new ChaseState(this); case EnemyAIState.Attack: return new AttackState(this); case EnemyAIState.HitStun: return new HitStunState(this); default: return null; } } }
内容的提问来源于stack exchange,提问作者Grantlbart
相关产品推荐
相关产品推荐

