☰
UE多敌人场景性能优化实战:从AI Tick到Draw Call全拆解
2026/9/28 15:16:05 网站建设 项目流程

这个标题我盯了很久:多敌人场景,听起来像是某个防守关卡的刷怪问题,实际上它是 UE 里一类非常典型的性能陷阱。我也趟过这条路——起初只是把刷怪点从 20 个加到 200 个,数值上的变化只动了一个数组,但设备上的 FPS 直接从 100 掉到 30 左右,操作延迟明显到能让人晕头转向。UE 的性能优化思路从来不是靠感觉调参,而是先把开销拆开,再分层消减。

这篇文章就围绕多敌人场景,完整展开我在实际项目里用到的排查方法和优化实践,覆盖 AI 逻辑、渲染、物理碰撞、寻路这些最容易堆积开销的模块。如果你也在做大规模敌群、防守波次、ROGUE 类刷怪玩法,看完至少能少走半个月弯路。

1. 多敌人场景下的卡顿到底败在哪个模块:先用剖析器定位

很多人的第一反应是“敌人太多所以卡”,这个结论不能说错,但等于没说。同样是 200 个敌人,可能是推理逻辑把 CPU 塞满,也可能是材质和阴影把 GPU 干穿,还有可能是碰撞查询把主线程卡到崩溃。不区分模块,盲目去调 LOD 或者删 AI 节点,基本是瞎忙。

1.1 什么叫“卡在哪儿”:stat unit 的正确读法

项目里跑起来后,输入下面这个命令,引擎会直接把整个帧的时间开销分项列出来:

stat unit

输出大致长这样:

指标含义初见时的数值
Frame一帧总耗时33.3ms
Game游戏线程耗时25.0ms
Draw渲染线程(Proxy 生成/剔除)耗时5.0ms
GPU单帧 GPU 执行耗时28.0ms

读法有个关键点:Frame 并不等于 Game、Draw、GPU 三者相加,这几个线程是并行推进的,Frame 时间是其中最慢的环节决定的。如果 Game 高,说明瓶颈偏向 CPU 上的玩法逻辑、蓝图、AI、物理、寻路;如果 GPU 高,就说明瓶颈偏向渲染指令、着色器、阴影、后处理这一类。

我当时的 Game 25ms、GPU 28ms,是典型的 CPU 和 GPU 双高,但论“面向玩家的感知”,GPU 的 28ms 更接近极限。于是后续操作就分成了两支:先看 Game 线程内部哪个系统在拖沓,再用 ProfileGPU 看渲染线程的提交内容,两条线同时推进。

1.2 把 CPU 开销拆到单个系统:stat game 和 ProfileGPU 的组合

stat unit只能定位到线程维度,要往下挖,还需要两条更细的命令:

  • stat game:把游戏线程的常见模块按 CPU 耗时排序,比如 Blueprint Time、Actor Tick、Physics、Navigation、Behavior Tree 这些都会露出真身。
  • ProfileGPU:按下快捷键后录制一段连续的 GPU 指令流,渲染内部某一环节具体花了多少毫秒,一目了然。

比命令更关键的,是“分层拆解”的习惯。我见过太多人只看全局数据,然后改了一堆没用的设置。正确顺序应该是:先用stat unit确认线程水位,再用stat game锁定游戏线程的头部项目,最后用 ProfileGPU 或者 CPU 捕获(trace)去做局部细节。这一步做完,优化的优先级自然浮出水面。

1.3 压力测试是前提:搭一个可复现的敌人潮场景

做性能剖析,最怕的就是“现场环境不可复现”。我一做性能优化的项目,一定会先搭一个专用测试关卡:固定刷怪点、固定敌人种类、固定运作时长。跑 60 秒、120 秒、240 秒,分别记录数据。

这个看起来很笨的做法,远比随手打开一个开着大量特效的测试关卡更科学。因为性能优化最需要的是“对照组”。你改了 AI 频率之后,必须跑同一个场景同一段时长,才能看出是否真的有效,而不是被场景随机的光照变化干扰。否则很可能会出现“改完了 FPS 好像高了一点”的错觉,过两天另一个场景又被打回原形。

2. 敌人 AI 的“每帧思考”模式:CPU 开销怎么压下来

多敌人场景最容易翻车的模块,恰恰是玩家感知最弱的部分:AI。200 个敌人的 AI 控制器、行为树、感知系统同时在跑,游戏线程的时间会瞬间膨胀。这部分的优化不是让敌人变傻,而是让它们在“看不见、打不着、听不到”的时候别白耗 CPU。

2.1 百个 AI 的 tick 风暴:每个敌人都在每帧“上班”

默认情况下,UE 的 Actor 和 Component 是以“每帧 Tick”为基准工作的。行为树会每帧评估,蓝图事件也可能每帧触发。200 个敌人,没有特殊设置的话,就相当于 200 个角色每帧都在执行自己的 AI 逻辑——哪怕它们站在墙角发呆。

实测过程中,最让人吃惊的往往是:发呆的敌人开销比战斗中的敌人还高。因为行为树默认不停地刷新 Decorator 条件、感知系统不停扫描周围信号、NavMesh 系统不停查询移动路径。这个开销并不显眼,但它是线性叠加的,敌人数一多,立刻成灾。

2.2 用距离可见性和“工作状态”做 AI 预算

如果局势允许,最好的优化路线是把 AI 从“每帧思考”改成“按需思考”。

一个最基本的手段是动态开关 Tick 频率。玩家靠近时,敌人可能要紧盯目标,每帧更新是合理的;但 200 个敌人背后 80% 都在较远距离外,这时可以把它们的 Actor Tick 间隔拉大到 0.1 秒、0.25 秒甚至 1 秒。具体做法很简单,比如在BeginPlay时给所有敌人一个默认的“远离玩家”规划,后面按距离切到高频率的 Tick 区间。

再进一步,可以把整个敌人群体拆成集合管理:只有通过可见性检测、距离检测和某种重要性评估的目标,才被允许进入完整 AI 状态。剩余的敌人只需要做最低限度的移动/动画迁徙,没必要让行为树和感知系统全面爆表。

我自己常用的一种做法,是把 AI 状态分成三个档位:

档位触发条件处理方式
完整 AI距玩家小于 30 米且可被玩家看到行为树、感知、朝向、攻击全部开启
简化逻辑距玩家 30 到 80 米只保留移动和轻量感知,行为树暂停或放到低频
休眠超过 80 米或完全不可见Tick 频率降到 0.2 秒以上,连移动逻辑都可以省

这套机制在多个尺度上都可靠,因为它不依赖“把敌人变成木头”的粗暴手段,而是让每个敌人只在自己“可能被玩家感知”的范围内全功率运行。

2.3 行为树与感知系统降频后的“伪活”效果

很多人担心降频之后敌人反应变慢,观感像 PPT。其实只要处理得当,玩家根本察觉不到。关键是给“低频 AI”一个缓冲:敌人不需要每帧都重新规划路径,走两步停一下才换方向,反而会显得更自然。

感知系统也是一样。UE 的 AI 感知组件自带“感知 Tick 间隔”,默认可能每帧扫一次,你可以把它调到 0.5 秒或 1 秒。对于玩家来说,敌人延迟 0.5 秒发现你,几乎不会造成明显感知差异;但对 CPU 来说,这是一个巨大的量级削减——200 个感知系统从每秒 60 次采样降到每秒 2 次采样,省掉的不只是一点点。

另外切记,行为树节点尽量别在 Selector 下堆一大堆复杂条件。跑 Profile 时你会发现,频繁评估的黑板条目也会造成显著开销。能合并的条件尽量合并,能缓存的查询尽量缓存。

3. 绘制调用、阴影与 LOD:GPU 侧开支的消减路径

CPU 侧理顺之后,下一个大头通常是渲染。多敌人意味着多个骨骼网格体、多个材质实例、多个阴影投射器。GPU 的负载由三角形数量、Shader 复杂度、Draw Call 数量共同决定,缺一不可。

3.1 先从 Draw Call 和状态切换下手

Draw Call 是渲染线程提交给 GPU 的绘制命令,也就是引擎得先把对象的网格、材质、变换状态准备好,再由 GPU 执行。单个 Draw Call 的耗时并不高,但 200 个敌人加上武器、特效、场景物件,轻松就是几千个 Draw Call,到了移动端或者中低端 PC,帧率直接崩盘。

消灭多余 Draw Call 最常用的两个手段:一是合并静态/简易网格,二是用实例化绘制。对于同种类的敌人,尽量用同一个 Skeletal Mesh 和同一套材质,这样引擎在排序时可以把相同 Draw Call 合并为实例化批次。使用HISM(Hierarchical Instanced Static Mesh)或者Instanced Static Mesh组件,会比一个个单独放置 Actor 高效得多。

我见过一个简化过程:把 200 个远程敌人从独立骨骼网格改成共享动画蓝图、共享材质,并尽量在渲染距离内使用同一个 LOD 级别。GPU 的时间立刻降了一个量级——因为状态切换少,绘制顺序也更加紧凑。

3.2 LOD 与实例化:把美术资产的密度降到合理位置

LOD 是降低 GPU 负载最直接的手段。UE 默认支持 LOD 自动切换,但很多项目根本没有为每个敌人模型制作多级 LOD,或者只准备了 LOD 0 一个超高质量网格。

在敌人数量多的场景,LOD 对帧率的影响非常大。以一敌为单位,LOD 0 可能需要 2 万三角面,LOD 1 则是 1 万,LOD 2 可以压到 3000 到 5000。距离玩家超过 50 米的敌人,完全用不上 LOD 0 的细节。此时可以设定一条比较激进的距离分配策略:近距离用 LOD 0,中距离用 LOD 1,远距离直接 LOD 2,超出可视范围的干脆剔除。

要特别注意,LOD 切换的“跳变”会被人眼捕捉到。如果模型减面做得好,切换阈值附近不会有强烈违和感;但如果你用自动化工具直接删面,可能远看还行、近看一坨毛刺。建议找美术配合,保留轮廓特征和动作关键部位,不要让手部、头部这种视觉焦点在 LOD 切换后崩掉。

3.3 阴影和光照策略

阴影是 GPU 另一个隐形分水岭。200 个敌人如果全部投射实时阴影,那 GPU 就要额外生成一张巨大的阴影贴图,并在每个光源角度下重新记录深度。这个开销属实大,但玩家在多数战斗状态下并不会死盯着远处敌人的影子看。

优化思路是“分级处理”:主光源影响下的近战敌人保留实时阴影;中距离敌人可以只投射次级简化阴影或者干脆关闭;远距离敌人连阴影投影组件都不开。UE 里的 Dynamic Shadow、Capsule Shadow(胶囊阴影)都是现成的手段,特别是 Capsule Shadow,它用胶囊体近似替代真实网格影,开销非常小。

还有一点容易踩坑:场景里别放太多点光源,尤其是那些“顺便补个光”的点光源。每多加一个光源,所有受光物体的阴影和光照计算都会倍增。在多敌人场景里,更加要克制。如果你的光照需求不复杂,室内场景用一个主方向光加一个补光,室外场景用方向光加环境光照,会稳定很多。

4. 物理碰撞与寻路系统:开销最高的隐形贡献者

这个部分很少有人第一时间怀疑,但它往往是很多大型项目“后期才能真正体验优化收益”的环节。多敌人的世界里,CPU 不仅要做 AI 决策,还要处理海量碰撞查询和路径计算。它们不像渲染那样肉眼可见,却能让 Game 线程时间偷偷膨胀。

4.1 碰撞体是引擎里最沉默的堆体

每个 Actor 在场景里都可能有碰撞盒、胶囊体、物理体。200 个敌人,就意味着 200 个甚至更多的碰撞体在实时参与物理查询。如果这些碰撞体不仅用于“阻挡玩家攻击”,还加入了移动查询、武器命中查询、导航阻挡、视线追踪,那叠加起来就是一套极为可观的物理开销。

最常见的优化是“裁剪碰撞体层级”:并非所有敌人必须使用完整物理模拟的刚体,多数情况下一个简单的胶囊体就足以完成阻挡和移动判定。把不必要的高精度碰撞网格全部换成简单盒子或胶囊,把碰撞通道里“这个敌人需要查询什么、不需要查询什么”梳理清楚,让物理引擎真正做该做的事。

4.2 动态物体与物理模拟的边界

多敌人场景最不适合的做法,就是让几十上百个敌人全部开启Simulate Physics。每开启一个动态物理体,引擎就得处理它的物理材质、摩擦力、重力、碰撞响应,GPU 和 CPU 都要额外负担。

我自己的项目里,只有被玩家击飞、爆体、触发特殊演出时才会临时开启物理模拟,常态下所有敌人都是用Movement Component做运动控制,不上物理刚体。这样做不仅省了物理 tick,还防止了敌人之间相互碰撞导致的小范围“卡墙”现象——这在很多防守关卡里尤其致命。

另外,PhysScene也会在多个子场景之间调度物理模拟。如果某个 Level 里开了过多的物理子场景,还是把这些子场景合并到主物理场景中,然后按距离决定是否激活物理模拟。对于远处打不到的敌人,物理模拟和碰撞查询都可以进入休眠状态。

4.3 寻路避障:群体化才扛得住

寻路系统是另一个容易被忽略的隐形炸弹。默认情况下,所有 AIController 都可能发起独立的 NavMeshPath 查询。200 个敌人同时寻路,再加上避障计算,Game 线程几乎要被堵死。

优化方向不是“禁用避障”,而是“让敌人群体走同一条路径,然后做局部微调”。UE 自带的Detour Crowd Manager就是专门解决这个问题的:一组角色共享导航网格,由 Crowd 模块统一调度避障和速度,不再各自为战。开启 Crowd Manager 后,你还可以通过AvoidanceGroup控制哪些敌人相互避让、哪些敌人可以重叠,从而降低计算量。

另一个实用思路是扩大NavMesh的格子尺寸,或者减少动态障碍物数量。注意,动态障碍物越多,避障计算越复杂,所以尽量把可移动物体、门板、临时掩体都做成“动态阻挡”而非“永久阻挡”。目标在 NavMesh 里是通行走道,就不做额外计算;敌人被撞到边缘的容错阈值调大一点,体验反而更好。

5. 从实战中沉淀的优化清单:一步一步把帧率捞回来

前面讲的都是单个模块的技术手段。但在真实项目里,这些优化是串在一起执行的,你得有清晰的操作节奏,才能避免“改一处崩一处”的窘境。下面是我在多敌人场景里反复打磨后的落地流程,按优先级排序,可以直接抄作业。

5.1 按优先级排序:先定位最贵的,再修最痛的

第一优先级永远是“先让 Game 线程喘口气”。你把 AI Tick、行为树频率、感知频率、碰撞查询都捋顺了,CPU 负载降下来,帧率通常能恢复到可接受范围。第二优先级才是“把 GPU 的渲染负载降下来”,包括 LOD、实例化绘制、阴影控制。第三优先级是“处理特殊大规模群体逻辑”,比如寻路避障的群体化、物理休眠、动画实例内存管理。

这个顺序不是随意定的。因为 AI 和物理导致的卡顿往往是“全局性”的,不解决它们,后续做任何渲染优化都会事倍功半;而渲染优化又是局部性的,如果基础逻辑已经顺畅,渲染的优化结果会立刻体现为更稳定的帧率。

我一般在跑完stat unit和stat game之后,会先把优先级写在便利贴上,比如:

  • 高压:200 敌人 AI tick 降频
  • 高压:远距离敌人关闭实时阴影
  • 中压:LOD 距离调整
  • 低压:Collision 碰撞通道整理
  • 待观察:Crowd Manager 避障参数

每完成一项,重新跑一次同一段压力测试,记录stat unit数据。这样的流程能确保每一步都有效果,而不是改了一大圈后不知道哪个改动真正起了作用。

5.2 验证要盯住 1% Low 帧:别再只信平均帧率

性能优化最容易犯的错误,是只看平均帧率。平均帧率 80 看起来不错,但实际游玩时总感觉“偶尔猛卡一下”。这时候真正要害的数据是 1% Low 帧——也就是一帧中最差的 1% 帧的平均值。

1% Low 帧低,意味着存在瞬间高开销或线程卡顿。比如 AI 感知系统每帧狂扫、物理碰撞在某帧突然同步、寻路查询争抢主线程资源,都会造成 1% Low 帧大幅波动。我在优化过程中特别关注这个数值:平均帧率提高了,但 1% Low 帧没动,那就说明我修的只是“顺风局”,而不是真正的卡顿源头;只有当 1% Low 帧也稳步抬升时,玩家体感才会真正变得顺畅。

5.3 优化后别忘了给美术和策划留足够记录

大型项目经常会遇到“部署后帧率莫名其妙变化”的情况。如果你不做记录,完全不知道上一版优化是否被某个资源刷新覆盖了。我会在每个优化节点写明:改动对象、改动内容、改动前后stat unit数值、对应的场景和测试时长。

这样做还有一个好处:当策划提出“敌人数量还要翻倍”的时候,你手上有足够的数据去判断,是直接拒绝,还是把 AI 频率和 LOD 再压一档,让系统继续扛得动。多敌人场景的性能优化,不只是一场“调参战”,更是一次“预算管理”。你的敌人数量、AI 复杂度、渲染细节,全部要放在同一张预算表上看,谁超标谁让路,帧率自然回来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询