with one click
game-performance
优化游戏性能时使用。帧率、GC、Draw Call、对象池、分帧的通用规范。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
优化游戏性能时使用。帧率、GC、Draw Call、对象池、分帧的通用规范。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
做 Cocos Creator 多机型/多分辨率适配时使用。Canvas、Widget、安全区。
Cocos Creator 用 AssetBundle 做分包/远程资源时使用。加载、释放、依赖、缓存。
优化 Cocos Creator 渲染性能时使用。合批、图集、动静分离、Label。
给 Cocos Creator 原生包做热更新时使用。version manifest、增量、校验、回滚。
写 Cocos Creator 动效/动画时使用。tween、Animation、Spine、性能与清理。
做 Cocos Creator 大量条目列表时使用。虚拟列表、节点复用。
| name | game-performance |
| description | 优化游戏性能时使用。帧率、GC、Draw Call、对象池、分帧的通用规范。 |
| category | gamedev |
| tags | ["性能","帧率","对象池"] |
规则: 发现性能问题的第一步是开引擎 Profiler,定位瓶颈究竟在 CPU 游戏线程、渲染线程、GPU 还是 GC;确认瓶颈后再动手,不猜测。
为什么——真实会犯的错: 帧率跌到 40 fps,直觉上以为是 Draw Call 太多,花两天做合批,结果 Profiler 一开,GPU 占用 30%,真正的问题是游戏线程里每帧跑了一段 O(n²) 的敌人感知逻辑。两天白费,且合批引入了新的材质管理复杂度。还有一种常见误判:以为是 GC 卡顿,把所有对象都对象池化,结果真正问题是 Shader 编译 spike,池化没有任何帮助反而增加了大量代码复杂度。
怎么做:
stat unit/stat fps/stat game/stat gpu 命令,GPU Visualizer 看渲染耗时。RenderingServer.get_rendering_info() 查 Draw Call 数量。规则: 子弹、爆炸特效、伤害数字、拾取物等生命周期短且高频生成的对象,必须用对象池复用;禁止在游戏主循环中对这类对象频繁 new/instantiate + destroy/queue_free。
为什么——真实会犯的错:
射击游戏里每颗子弹都 Instantiate + Destroy,1 秒 20 发,C# 的 GC 每隔几秒做一次 Gen0 收集,帧时间 spike 到 50ms,玩家感知到明显卡顿。在移动端这个问题更严重,GC pause 动辄 100ms+。关键是这种卡顿只在长时间游戏后出现,开发期单次测试根本发现不了,上线后玩家投诉才暴露。
怎么做:
ObjectPool<T> 内置实现;Godot:手动维护 Array 池;Unreal:Actor 池或配合 Niagara 的 GPU 粒子。规则: 减少 Draw Call 的核心是让渲染器批处理更多对象:相同材质/纹理的对象合批;UI 元素用图集;避免运行时频繁修改材质参数导致 batch 断开。
为什么——真实会犯的错:
UI 界面上 200 个图标,每个图标用独立的 Sprite 图片,200 个 Draw Call 全在 UI 层,移动端帧率卡死。换成图集(Texture Atlas)后降到 3 个 Draw Call,帧率立刻上来。另一个常见错误:代码里动态 material.SetColor(...) 修改颜色,Unity 遇到 SetColor 会破坏 Static Batching 并触发 materialInstance 拷贝,导致原本可以合批的几百个对象全部分开渲染,Draw Call 暴涨。
怎么做:
规则: 耗时的非实时计算(寻路重算、视野检测、大批量 AI 决策)分帧执行或使用时间片;远处对象启用 LOD 降低面数;不在视锥之外的对象关闭 Tick/Update。
为什么——真实会犯的错: 100 个 NPC 每帧同时刷新寻路,游戏线程单帧耗时从 2ms 涨到 18ms,超过 16.6ms 预算,帧率掉到 45fps。把寻路更新分散到 10 帧内依次执行(每帧处理 10 个),平均耗时回到 3.8ms,帧率稳了。另一个教训:远处 500 米外的敌人和近处敌人同频 Tick,这些 NPC 玩家根本看不清,却占用了和近处 NPC 等量的 CPU,是纯粹的浪费。
怎么做:
is_visible_in_tree() / IsActorInViewFrustum(),不在视锥外做计算。规则: 关卡/场景切换时主动卸载不再需要的资源;纹理用压缩格式(ETC2/ASTC/DXT);图集不能无限增大;音频用流式播放替代全部加载到内存。
为什么——真实会犯的错:
手游关卡切换后没有调用资源卸载,前一关的纹理还驻留内存,玩到第三关时内存超 2GB 被系统杀掉。另一个常见包体事故:美术给了 4096×4096 的无损 PNG 做 UI 背景图,没有设置平台压缩格式,一张图 64MB,包体直接超标,且加载时间比 ASTC 压缩版慢 8 倍。背景音乐用 preload 全量加载,3 首 BGM 占 120MB 内存,改成流式(AudioStream with stream enabled)后降到 5MB。
怎么做:
Resources.UnloadUnusedAssets()(Unity)或 gc.collect()(Godot),不依赖运行时自动回收。// ❌ 反例(Unity C#)— 每帧生成和销毁子弹,GC 压力极大
public class Gun : MonoBehaviour
{
public GameObject bulletPrefab;
void Update()
{
if (Input.GetKey(KeyCode.Space))
{
// 每帧 new 一个 GameObject,GC 负担
GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
Destroy(bullet, 3f); // 3 秒后销毁,触发 GC
}
}
}
// ✅ 正例(Unity C#)— ObjectPool 复用子弹,零运行时分配
using UnityEngine.Pool;
public class Gun : MonoBehaviour
{
public GameObject bulletPrefab;
private ObjectPool<Bullet> _pool;
void Awake()
{
_pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab).GetComponent<Bullet>(),
actionOnGet: b => b.gameObject.SetActive(true),
actionOnRelease: b => b.gameObject.SetActive(false),
defaultCapacity: 30
);
}
void Update()
{
if (Input.GetKey(KeyCode.Space))
{
Bullet b = _pool.Get(); // ✅ 从池取,无 GC 分配
b.Init(firePoint.position, firePoint.rotation, _pool);
}
}
}
public class Bullet : MonoBehaviour
{
private ObjectPool<Bullet> _pool;
public void Init(Vector3 pos, Quaternion rot, ObjectPool<Bullet> pool)
{
transform.SetPositionAndRotation(pos, rot);
_pool = pool;
Invoke(nameof(ReturnToPool), 3f);
}
void ReturnToPool() => _pool.Release(this); // ✅ 归还池,不 Destroy
}