一、如何定位问题
Profiler/Frame Debugger 之类工具确认瓶颈。
CPU bound:Profiler 里 Main Thread/Render Thread 很高,GPU 还很空;或降低分辨率没提升 FPS
GPU bound:GPU time 高;降低分辨率/关闭后处理 FPS 明显提升
内存/GC bound:卡顿呈“尖刺”,GC.Alloc 很多,周期性 GC spike,或内存持续增长导致加载/换场抖动
二、 CPU 优化(主线程、脚本、物理、动画、渲染提交)
A. 脚本 Update/逻辑过多
把“每帧轮询”改成事件驱动(状态变化才更新 UI/逻辑)
降频:
InvokeRepeating/自定义 tick(比如 10Hz/5Hz)替代每帧计算缓存引用:避免每帧
GetComponent/Find(只在初始化取一次)避免 LINQ/闭包/foreach 装箱等隐式分配(也会牵扯 GC)
B. 物理开销(Physics)
减少
Physics.Raycast数量(合批检测、分层检测、降频)合理 Layer 碰撞矩阵,减少无意义碰撞对
Rigidbody 数量/碰撞体复杂度控制;尽量用简单 Collider
FixedUpdate 频率不要过高(默认 50Hz,必要时调低并保持稳定)
C. 动画/IK
Animator 层数、BlendTree 复杂度、IK/约束数量控制
远处角色降低动画更新频率 / 关闭 IK
共享 AnimatorController、减少 Animator 组件数量
D. 渲染提交(CPU 侧)
DrawCall 太多:SRP Batcher、GPU Instancing、合批、静态批处理
UI Canvas:避免整个 Canvas 每帧重建,拆分静态/动态 Canvas,少改 Layout
大量 SetPass/材质切换:合材质、合贴图(图集)、减少 shader variant
三、 内存优化(峰值、常驻、加载、GC)
内存优化在 Unity 常见两类:减少分配 + 控制资源常驻/峰值。
A. 减少 GC(卡顿尖刺的主要来源)
避免每帧分配:字符串拼接、
new、LINQ、频繁Instantiate临时对象对象池:子弹、特效、UI item、怪物等(并配合预热)
复用容器:List/Dictionary 清理用
Clear(),避免反复 newUI 文本更新:减少频繁 SetText/字符串格式化(尤其移动端)
B. 控制资源内存(贴图/网格/音频)
贴图压缩格式正确(ASTC/ETC2/BCn 视平台),mipmap、分辨率分级
Mesh/SkinnedMesh:骨骼数量、网格面数、Blendshape 控制
音频:长音频用 streaming,短音效压缩/内存加载策略区分
C. 资源加载与峰值(Addressables/AssetBundle)
异步加载,避免主线程阻塞(尤其切场景)
资源分组、依赖分析,避免“一加载加载一堆”
正确释放:
Addressables.Release、卸载不用的 bundle、场景切换后清理关注“峰值内存”:加载新场景时旧场景资源未释放会导致瞬时爆内存
四、GPU 优化(像素、顶点、带宽、过度绘制)
GPU 优化核心是:少画、画得更便宜、减少带宽/状态切换。
A. 像素成本(Fillrate)与过度绘制
降低分辨率/动态分辨率(最直接验证 GPU bound)
减少全屏后处理(Bloom、SSAO、DoF、TAA 等)
UI/透明物体过多会 Overdraw:减少透明层级、能用不透明就不用透明
合理裁剪:UI Mask/ScrollRect 也会增加开销
B. Shader & 材质
Shader 复杂度:减少计算、纹理采样数、分支
减少 shader variant:剔除不用关键字,避免爆炸式编译与运行时切换
使用 SRP Batcher(URP/HDRP),减少 CPU+GPU 的状态成本
C. 几何(顶点)与阴影
LOD、Impostor、合并远景
阴影:降低 shadow distance、分辨率、cascade 数,减少投射/接收对象
粒子:控制数量、启用 GPU Instancing(支持时)
D. 带宽(移动端尤其重要)
贴图压缩、减少 RT 数量/分辨率(后处理链路)
避免频繁读写大 RenderTexture