这个标题我盯了很久多敌人场景听起来像是某个防守关卡的刷怪问题实际上它是 UE 里一类非常典型的性能陷阱。我也趟过这条路——起初只是把刷怪点从 20 个加到 200 个数值上的变化只动了一个数组但设备上的 FPS 直接从 100 掉到 30 左右操作延迟明显到能让人晕头转向。UE 的性能优化思路从来不是靠感觉调参而是先把开销拆开再分层消减。这篇文章就围绕多敌人场景完整展开我在实际项目里用到的排查方法和优化实践覆盖 AI 逻辑、渲染、物理碰撞、寻路这些最容易堆积开销的模块。如果你也在做大规模敌群、防守波次、ROGUE 类刷怪玩法看完至少能少走半个月弯路。1. 多敌人场景下的卡顿到底败在哪个模块先用剖析器定位很多人的第一反应是“敌人太多所以卡”这个结论不能说错但等于没说。同样是 200 个敌人可能是推理逻辑把 CPU 塞满也可能是材质和阴影把 GPU 干穿还有可能是碰撞查询把主线程卡到崩溃。不区分模块盲目去调 LOD 或者删 AI 节点基本是瞎忙。1.1 什么叫“卡在哪儿”stat unit 的正确读法项目里跑起来后输入下面这个命令引擎会直接把整个帧的时间开销分项列出来stat unit输出大致长这样指标含义初见时的数值Frame一帧总耗时33.3msGame游戏线程耗时25.0msDraw渲染线程Proxy 生成/剔除耗时5.0msGPU单帧 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. 绘制调用、阴影与 LODGPU 侧开支的消减路径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 合并为实例化批次。使用HISMHierarchical 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 复杂度、渲染细节全部要放在同一张预算表上看谁超标谁让路帧率自然回来。