我最初在动作游戏项目里做敌人AI时,遇到过这样一个尴尬局面:敌人会追着玩家打,也会攻击,但玩起来就是"死板"两个字。玩家摸清规律后,站桩输出就能过关,战斗体验非常糟糕。后来我尝试给AI加上滑步动作,并让它能够在攻击、滑步、接近、后退等多个行为之间自由切换,整个战斗节奏立刻不一样了。这个"AI切换攻击滑步"的机制,说穿了就是让AI像一个真实玩家那样,用位移来创造攻击机会、用滑步来规避反击、用行为切换来制造不可预测性。这篇文章我会完整拆解这套机制的设计思路、状态切换逻辑、滑步位移的底层实现,以及我在实战中踩过的各种坑,适合正在做动作游戏AI的开发者、战斗策划,也适合想理解AI行为逻辑的玩家参考。
1. 为什么AI需要一个"滑步攻击"机制
1.1 传统AI为什么会显得僵硬
大部分动作游戏里,AI敌人的行为通常分两类:要么是纯粹的追人模式,要么是站在原地等玩家进入攻击范围后出手。这种方式最大的问题是行为太线性,玩家可以精准预判。今天这个怪要挥刀了、明天这个BOSS要冲锋了,背板就完了,不需要临场反应。
从我自己的项目经验看,问题不在"AI不会攻击",而在"AI没有移动博弈"这个维度。一个只会前进后退的敌人,本质上和木桩没有区别。玩家只要找到一个安全距离,就能无限风筝或者卡墙角。
1.2 滑步承担了哪些设计职责
滑步在动作游戏里是一种短距离快速位移方式,它和普通跑步的区别在于:速度更快、距离固定、通常带有短暂的无敌帧或闪避判定。把滑步交给AI后,它同时承担了三个职责:
- 进攻性接近:AI在攻击范围外时,通过滑步快速切入攻击距离,制造压迫感。
- 防守性闪避:当玩家攻击即将命中时,AI可以向侧方或后方滑步,规避伤害并创造反击窗口。
- 战术性切换:滑步可以让AI在攻击连段之间重新调整位置,避免连续站桩输出导致玩家轻松背板。
换句话说,滑步不是单纯炫技,它是AI攻防转换的"转折点"。AI什么时候滑步、滑步后接什么动作,直接决定了玩家面对的是一个"会动的木偶"还是一个"像真人一样的对手"。
1.3 这套机制解决了什么核心问题
从我实测的情况来看,加入AI切换攻击滑步机制之后,最直接的变化是:玩家必须同时关注AI的位移倾向和出手时机。AI滑步拉近距离时你不知道它要直接攻击还是要虚晃一枪再后退,这种不确定性正是战斗乐趣的来源。
对于开发者的价值也很明确:这套机制把"AI能否打出好看战斗"从美术资源问题变成了逻辑设计问题。不需要额外做复杂的动画,只要把滑步、攻击、待机几个状态和合理的切换规则做出来,AI的战斗表现就能提升一个档次。
2. 行为切换架构设计:从状态机到决策逻辑
2.1 状态划分:不要把逻辑全塞在Update里
我刚开始实现这套机制时,犯过一个经典错误:在一个大Update里用if-else堆所有行为判断,结果代码迅速膨胀,AI的行为变得难以追踪。后来我改用状态机思路,把所有行为拆成独立的状态,每个状态只关心自己该干什么。
以我项目的敌人AI为例,核心状态划分为:
- Idle(待机):AI不追人也不攻击,通常用于初始状态或脱战后的行为。
- Chase(接近):AI朝玩家移动,但不使用滑步,属于常规追击。
- Attack(攻击):AI执行攻击动作,包括出招前摇、命中判定、收招硬直。
- Slide(滑步):AI执行一次快速位移,可朝前、侧方或后方位移。
- Recover(硬直):AI受击后的恢复状态,期间不响应任何指令。
- Retreat(后撤):AI主动拉开距离,通常用于攻击落空后的战术调整。
状态划分的原则是:每个状态有明确的进入条件和退出条件,状态内部只处理自己的逻辑。比如Slide状态只负责位移和动画播放,它不关心玩家当前血量多少,也不关心自己上一次攻击用了几连击。
2.2 状态切换条件与优先级
有了状态划分后,最关键的是切换规则的设定。我的经验是:切换条件必须给出明确的距离阈值、时间窗口和随机因子,否则AI会表现得非常机械。
举一个典型的攻击到滑步的切换逻辑:
if (当前状态 == Attack && 攻击连段结束) { if (玩家距离 < 2.5f && 玩家正在出手前摇) { 切换到 Slide,方向 = 侧后方; } else if (玩家距离 < 1.8f && 玩家在攻击范围内) { 切换到 Slide,方向 = 后方; } else if (随机概率 < 0.25f) { 切换到 Slide,方向 = 侧方; } }这套逻辑的逻辑是:
- 攻击结束后,如果检测到玩家正在做攻击前摇,AI会优先选择闪避,而不是硬吃伤害。
- 如果玩家距离很近但前摇不明显,AI也会后撤拉开空间。
- 最后加一个随机概率,是为了避免AI每次攻击结束后行为完全一致,给玩家留出"读招"的观察空间,同时保留不可预测性。
切换优先级我建议单独定义一张表,而不是在代码里散落判断。比如滑步的优先级要高于攻击,硬直的优先级要高于一切主动行为。优先级表的好处是:后续要加新状态(比如弹反、格挡)时,不需要大改现有逻辑。
2.3 用行为树还是状态机
在我做过的项目里,这两种方案都有应用。状态机适合行为数量少、逻辑相对固定的敌人,实现简单、调试直观。行为树适合行为数量多、层级复杂的大Boss,比如一个Boss拥有近战、远程、召唤、狂暴多个阶段时,行为树的扩展性更好。
但滑步攻击这套机制本身,不管是状态机还是行为树都能实现。核心不在于用什么框架,而在于"状态之间怎么决策"。我见过很多项目用行为树写出来的AI一样很傻,因为节点里的条件判断写得稀烂。也有项目用纯状态机写出了很有压迫感的Boss战,关键在于切换条件和时机把握。
就我个人习惯,单兵种AI用状态机,Boss战用行为树加黑板数据。两种方案我会在后面的实战代码里都给一个简化版参考。
3. 滑步位移的底层实现:移动、碰撞和动画同步
3.1 位移方式的选型:直接改坐标还是用物理引擎
滑步的实现方式直接决定了手感。Unity里常见的有两种:
- 使用Rigidbody的velocity或AddForce驱动:适合有物理碰撞要求的角色,但容易出现速度过冲、抖动。
- 直接修改transform.position,配合Lerp或SmoothDamp:适合纯逻辑控制的敌人AI,位移可控性更强。
我的项目里AI敌人没有复杂的物理反馈需求,所以选择了直接改transform.position的方式,配合一条速度曲线来模拟"先快后慢"的滑步手感。如果你用的是UE,CharacterMovementComponent里的LaunchCharacter函数也能实现类似效果,但要注意控制飞行时间。
一个可用的Unity滑步协程示例:
public IEnumerator PerformSlide(Vector3 direction, float distance, float duration) { float elapsed = 0f; Vector3 startPos = transform.position; Vector3 endPos = startPos + direction.normalized * distance; while (elapsed < duration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); float easedT = slideCurve.Evaluate(t); // AnimationCurve 控制速度变化 transform.position = Vector3.Lerp(startPos, endPos, easedT); yield return null; } transform.position = endPos; OnSlideFinished?.Invoke(); }注意几个关键点:
- 滑步期间要把AI的碰撞体暂时切换为不阻挡玩家,但保留触发检测。否则AI滑步冲向玩家时会被自己的碰撞体卡住,导致位移距离不足。
- 如果游戏里有地面高度变化,一定要在每帧结束后做一次向下的射线检测,把角色的y坐标贴合地面。
- duration和distance两个参数不要用固定值,我建议根据AI和玩家的距离动态调整。比如距离远时滑步距离长、速度慢;距离近时滑步距离短、速度快,这样AI的压迫感更强。
3.2 滑步与攻击的动画衔接
位移逻辑只是滑步的一环,视觉层面动画的衔接才是真正决定AI看起来聪不聪明的部分。实测下来,一个滑步动画如果和实际位移不同步,玩家会明显感觉"画面很飘"。
我的处理方式是:先通过动画事件来驱动位移,而不是在Update里同时做动画播放和位移计算。具体流程是:
- 播放滑步动画。
- 在滑步动画的起始帧(比如第1帧)触发"位移开始"事件。
- 在动画的中间帧(比如第6帧)触发"位移结束"事件。
- 在动画的收尾帧触发"允许切换下一状态"事件。
这样做的优势是:位移时长和动画时长的对应关系统一由动画资产控制,不会出现动作播完了位移还在跑的情况。如果你在动画编辑器里调整了滑步动画的时长,位移逻辑也会自动适配,不需要改代码。
3.3 滑步和攻击的派生衔接
在实际战斗里,AI很少只做一次单纯滑步就停了。更多时候滑步是攻击连段的起手式,或者攻击后的收尾动作。把这个衔接做好,AI的连招逼迫感会非常强。
我的设计思路是两种派生方式:
- 滑步接近后接攻击:当滑步结束时,如果玩家距离小于攻击范围且玩家处于可受击状态,立即切换到Attack状态,并且自动让攻击的前摇时间减半,模拟一种"贴身快打"的压迫感。
- 攻击结束后接滑步后撤:当攻击连段结束或攻击落空时,AI有概率后撤滑步,重新拉开距离,准备下一轮试探。
这两种派生需要处理一个"攻击硬直取消"的问题。一般动作游戏里,攻击动作没播完是不能做其他动作的,否则动作看起来会很抽搐。我的处理方式是:在攻击动画的"可取消时间点"(通常是收招段)做一个标记,只有滑过这个时间点后才允许切换到后撤滑步。这个取消窗口的宽度直接决定AI的压制强度:窗口越宽,AI越灵活,但也越难打。
4. AI切换攻击的决策逻辑:什么时候出手、什么时候滑步
4.1 索敌与攻击范围判定
AI的所有决策都建立在"玩家在哪"这个基础上,但只有基础的距离判断是不够的。我实测中发现,如果只用距离做判定,AI会出现一种很蠢的行为:玩家明明在攻击范围内,AI却还在不断滑步调整位置,看起来像在犹豫。
解决思路是引入"当前武器攻击半径"和"玩家移动速度"两个扩展因子。我把攻击范围判断从简单的球体半径改成了一个"扇形+距离"的复合判定:
public bool IsPlayerInAttackRange() { float dist = Vector3.Distance(transform.position, player.position); float angle = Vector3.Angle(transform.forward, player.position - transform.position); return dist <= attackRange && angle <= attackAngle; }attackAngle一般设置在60度到90度之间,这样AI不会背对玩家出手,攻击动作会显得更有方向性。angle的判断同时解决了"玩家站在AI正后方,AI还往前打"的尴尬情况。
4.2 攻击时机的选择:硬直窗口与意图预测
AI不该一进范围就攻击,这样玩家会觉得很突然、不公平。好的攻击AI应该像真人一样,在窗口出现时出手,并给玩家可读的预警。
我的做法是把"玩家当前的硬直状态"当成攻击触发的核心条件。如果玩家正在做攻击动作、翻滚、跳跃等无法防御的行为,AI的攻击意愿会大幅提升。同时,我给AI加了一个"出手意图"的缓冲机制:AI决定要攻击后,会先播一个短前摇动画(比如0.25秒的抬手),这个前摇给了玩家反应时间,但前摇结束后的攻击速度必须快。
这个设计背后的原理是:前摇给玩家信息,后续的高速攻击给玩家挑战。如果AI每次都无前摇瞬发攻击,玩家体验会很差;如果AI前摇太长,玩家又会觉得AI太笨拙。关于前摇和出招速度的比例,我在项目里通常用0.2~0.3秒前摇配0.1~0.2秒的命中判定窗口,整体手感比较均衡。
4.3 滑步后的行为决策:追击、再滑步还是后退
滑步结束后AI下一步干什么,这是整套机制里变数最多的部分。我的方案是给滑步结束后加一个"行为评估点",这里重新跑一遍状态决策逻辑,但会根据当前情况的紧急程度调整分化概率。
比如AI向后滑步闪避成功后,会有三个分支:
- 分支A(概率最高):立刻反击,因为闪避成功后通常处于一个相对安全的进攻位置。
- 分支B(较低概率):再次后撤,继续拉扯距离,适合远程型AI的打法风格。
- 分支C(极小概率):原地停顿0.3秒,模拟一种"观察玩家反应"的战术停顿。
这个概率不是固定的,它会根据玩家当前的行为动态调整。比如玩家闪避成功次数太多,AI的后撤概率会提高,不再轻易出击;玩家被压制得很厉害,AI的追击概率会提高,主动进攻。
用参数而不是写死逻辑的好处是:战斗策划在配置表里就能调整AI的"性格",不需要改代码。我在项目里把这些概率参数全部开放到Inspector面板,实测调参效率提升了非常多。
4.4 避免AI"变脸太快"的抖动问题
刚开始做完这套逻辑时,AI被玩家吐槽"精神分裂",因为行为切换太频繁了。上一帧还在追击,下一帧就后撤,动作之间没有缓冲,看起来像抽风。
排查后发现根因是:每个状态结束的瞬间,AI立刻重新做决策,而决策条件里只有距离和角度这类瞬时值,没有加入时间缓冲。解决方法是引入两个机制:
- 状态冷却时间:每个主动行为状态(攻击、滑步、后撤)都有最短持续时间,比如攻击至少播完整个动画,滑步至少完成短距离位移。
- 决策冷却时间:每次行为切换后,至少等待0.2~0.4秒才能再次触发切换,防止AI在几个状态之间高频震荡。
这两个冷却时间在代码里就是两个Time.time的起始标记,实现很简单,但对手感提升非常明显。我在后续项目的所有AI里都保留了这两个成员变量。
5. 玩家视角的应对策略与开发者的调试手段
5.1 玩家如何读招:识别AI的滑步攻击意图
作为玩家,面对一个会切换攻击滑步的AI,最重要的判断依据其实是AI的朝向和距离变化。以我自己的体验总结,AI的滑步攻击一般会遵循一个规律:滑步靠近前会有一个短暂的停顿或方向调整,这个阶段就是"读招窗口"。
比如一个敌人向侧面滑步,它接下来的攻击通常不是直冲,而是绕侧后的斜向攻击;如果敌人向后滑步再快速朝前突进,那基本就是要出冲刺类攻击。这些组合不是死板的程序化套路,而是由AI决策参数决定的倾向性,但玩家可以通过多次对战积累经验。
对开发者而言,我的建议是:在设计AI时不要只考虑"AI要怎么赢",还要留出"玩家怎么读懂AI"的信息位。前摇动作、滑步朝向、声音提示,这些都是玩家在压力状态下能快速捕捉的信息通道。
5.2 用可视化调试观察AI的状态切换
写AI逻辑时最痛苦的不是实现,而是"AI为什么突然做了这个行为"。文本日志能帮你看到结果,但很难直观感受到AI的行为轨迹和决策过程。
我在项目里做了一个简单的Gizmos可视化调试层,每一帧把AI身上的关键信息绘制在Scene视图里:
- 当前状态名(文字)。
- 攻击判定半径(圆圈)。
- 滑步目标位置(绿色方块)。
- 决策冷却的剩余时间(进度条)。
这套工具帮我发现了很多单看代码发现不了的问题。比如有一次AI出现"无限滑步绕圈"的Bug,我从Gizmos里看到滑步方向每次都是顺时针偏移15度,持续多次后AI就在原地转圈。根源是方向向量每次累加了一个偏移量,累积效应导致方向漂移。如果只靠日志很难定位这种空间上的问题。
5.3 平衡性调参的经验基准
AI切换攻击滑步是一把双刃剑:调得太保守,AI就是个只会闪避的怂包;调得太激进,玩家会觉得被AI碾压。
我给自己定了一个调参基准表(以标准体型的近战敌人为例):
| 参数 | 保守值 | 适中值 | 激进值 |
|---|---|---|---|
| 滑步速度(m/s) | 5 | 7 | 10 |
| 滑步距离(m) | 1.5 | 2.5 | 4 |
| 攻击前摇(秒) | 0.4 | 0.25 | 0.15 |
| 攻击后硬直(秒) | 0.6 | 0.4 | 0.2 |
| 侧向滑步概率 | 0.1 | 0.2 | 0.35 |
| 攻击后撤概率 | 0.2 | 0.3 | 0.45 |
一般来说,小怪用保守值到适中值之间,精英怪用适中值到激进值之间。Boss战可以额外把滑步速度和距离调高,但攻击前摇也要相应加长,保证玩家依然有反应空间。每次调完参数后,我都会让不同水平的测试玩家各打几轮,记录他们的时机感受,而不是只看策划的主观判断。
6. 实战中踩过的坑:切换抖动、穿墙、动画错位与性能
6.1 状态来回横跳的根本原因与解法
前面提到的"AI精神分裂"问题,我在不同项目里反复踩过。除了缺少冷却时间之外,还有一个常见原因是"判定条件互相包含"。比如A状态要求"玩家距离小于5米进入",B状态要求"玩家距离大于3米进入",当玩家在3米到5米之间移动时,两个条件反复满足,AI就在A和B之间疯狂切换。
解法是给每个状态定义一个不可重叠的"主条件",切换时优先判断主条件,子条件只用于状态内部的行为选择。比如:
- Chase状态主条件:玩家距离大于攻击范围。
- Attack状态主条件:玩家在攻击范围内且未处于冷却。
- Slide状态主条件:玩家在攻击范围内但即将出手攻击。
这三个条件彼此互斥,AI不会因为距离波动而频繁跳状态。
6.2 滑步穿墙与环境碰撞
滑步作为一种强位移技能,最容易翻车的点就是穿墙。直接改transform.position的方式如果完全不做碰撞检测,AI一个滑步就滑到地图外面去了。
我的做法是分两步:
- 滑步方向计算阶段做射线检测,如果滑步目标方向上有墙壁或障碍物,就停止该方向滑步,改为侧向偏转。
- 滑步位移阶段每帧用SphereCast检测前方是否碰撞,如果碰到障碍物,就提前结束滑步并播一个碰撞停顿动画。
SphereCast的半径要和角色胶囊体的半径保持一致,半径设太小会部分嵌入墙体,设太大则会在接近墙角时提前停下,影响手感。我一般会稍微调小0.05米来平衡这两种情况。
6.3 动画与位移不同步的排查链路
有一次我把滑步距离从2米调到3米,结果玩家反馈"AI的脚在滑行,身体像在溜冰"。排查后发现是位移曲线和动画曲线不匹配:动画在前期快速迈步,但位移曲线在前期速度较慢。
排查链路可以这样走:
- 先在Scene视图里开启角色位移轨迹显示(用OnDrawGizmos把最近一帧的位置点连起来)。
- 确认每个关键帧动画里角色的脚部位置和实际位移的关系。
- 比对位移曲线的速度变化与动画曲线的速度变化,找到错位区间。
- 调整位移曲线的关键帧权重,或调整动画资产的播放速度。
整个过程虽然耗时,但一次调好之后,滑步的手感会非常扎实。我的经验是"位移速度曲线的峰值应该出现在滑步动画的中段"而不是开头或结尾,这更符合真实蹬地加速的物理感觉。
6.4 性能与多AI场景的取舍
如果场景里同时存在大量拥有这套AI逻辑的敌人,行为决策的实时计算会带来不小的CPU开销。实测下来,20个AI同时跑完整的状态机加滑步决策逻辑,平均帧开销比静态AI高出约1.5ms到2ms。
我的优化策略是:
- 距离剔除:距离玩家超过一定范围的敌人减少决策频率,比如每0.3秒做一次完整决策,而不是每帧都做。
- 状态机休眠:Idle状态的AI不做任何计算,只等触发器唤醒。
- 滑步物理检测降频:远处的AI不做SphereCast,只用射线检测地面,降低开销。
这些优化对单个AI的影响几乎察觉不到,但能保证多AI混战时的性能下限。我遇到过在Boss战召唤10个小怪后帧率骤降的案例,加上距离剔除后直接解决了问题。
7. 从这套机制延伸出去的更多可能性
AI切换攻击滑步不只适用于近战敌人。我在后面几个项目里把同一套行为切换思路应用到了远程怪、飞行怪,甚至NPC队友的行为设计上,效果都不错。
远程怪可以在"保持距离射击"和"被近身后滑步拉开"这两个状态之间切换,本质上是同一套决策逻辑换了一套技能配置。飞行怪则把滑步换成了俯冲和拉升,切换条件变成了高度差和玩家位置。NPC队友的AI也可以在"跟随、战斗、撤退"三种状态间切换,只是决策参数里多了一个"队友性格"变量。
如果你正在做动作游戏,我建议不要把滑步机制想成一个独立功能,而是把它纳入AI行为切换这个大框架里来设计。滑步只是多种位移技能中的一种,你后续还可以加翻滚、闪现、冲刺格挡等不同位移手段,所有的逻辑框架都不用变,只需要往状态机和决策参数表里加新选项。
另外,这套机制也很适合用行为树加黑板数据的架构来承接,尤其当Boss有多个阶段、多种攻击模式时,把"滑步接近"和"滑步后撤"设计成共享子树,不同Boss通过不同配置组合出差异化的战斗风格,维护成本会显著降低。我自己在后续使用行为树的项目中,已经把滑步相关的所有逻辑封装成了可复用的Task节点,整个战斗AI的搭建速度提升了不少。
最后分享一个我调试了很多次才意识到的小细节:AI滑步时保持的朝向非常重要。如果AI在做侧向滑步时还一直盯着玩家,视觉上会显得非常诡异;但如果AI在滑步时把朝向调整为移动方向,动作会自然很多。我处理的方式是单独用一个Transform负责朝向插值,在滑步开始后把朝向目标从"玩家方向"切换为"移动方向",等滑步结束前再快速转回盯人方向。这个细微的处理,会让AI看起来少了很多"机器人感"。