with one click
game-assets-memory
管理游戏资源与内存时使用。加载/卸载、图集、包体、泄漏防治。
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
管理游戏资源与内存时使用。加载/卸载、图集、包体、泄漏防治。
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-assets-memory |
| description | 管理游戏资源与内存时使用。加载/卸载、图集、包体、泄漏防治。 |
| category | gamedev |
| tags | ["资源","内存","加载"] |
规则: 纹理、音频、场景、模型等大资源一律走异步接口加载,绝不在主线程同步读取;加载期间必须有合适的 loading 表现,加载完成回调后再使用资源。
为什么: 最常犯的错是原型阶段用同步加载图方便,上线前"有时间再改"——结果积累了几十处同步加载,进游戏时主线程卡顿 2 秒,ANR 投诉爆仓。另一个坑:异步加载发起后立刻用资源(还没加载完),导致空引用崩溃或用到上一次缓存的错误资源。
怎么做:
// Unity 示例
// 反例 — 同步加载阻塞主线程
var tex = Resources.Load<Texture2D>("hero/sword"); // ❌ 主线程阻塞
// 正例 — 异步加载,回调中使用
IEnumerator LoadHeroAsync(string key, Action<GameObject> onLoaded) {
var handle = Addressables.LoadAssetAsync<GameObject>(key);
yield return handle; // ✅ 异步等待
if (handle.Status == AsyncOperationStatus.Succeeded) {
onLoaded(handle.Result); // ✅ 加载完再用
} else {
Debug.LogError($"加载失败: {key}");
}
}
规则: 每个资源的生命周期(谁持有、何时释放)必须在接入时明确设计;场景切换时主动释放当前场景独占的资源,不依赖 GC 或引擎的"自动"回收。
为什么: 最典型的泄漏路径:战斗场景加载了 50 个怪物预制体的纹理,战斗结束切回大厅,没有主动 Release,Addressables 的引用计数没归零,纹理全留在内存里。打完十场战斗内存涨到崩溃。反向问题也有:卸载太激进,在某个地方还持有引用时就强制卸载,运行时出现粉色/错误材质。
怎么做:
LoadAssetAsync 对应一次 Release,用 RAII 或引用计数封装保证配对。UnloadUnusedAssets。规则: UI 精灵和 2D 素材必须打图集减少 Draw Call;纹理压缩格式按平台和内容类型选择(Android 用 ETC2/ASTC,iOS 用 ASTC,PC 用 BC7/DXT5),不能全部用未压缩 RGBA32。
为什么: 没用图集的 UI 场景,100 个图标就是 100 个 Draw Call,低端机直接掉帧。纹理压缩格式用错更隐蔽:开发时在 PC 上用 RGBA32 完全没问题,发布到手机后内存翻 4 倍(一张 1024×1024 的 RGBA32 纹理占 4MB,ASTC 6×6 只占约 0.4MB),低端安卓机直接 OOM。
怎么做:
规则: 任何在对象初始化时注册的事件监听、委托回调、定时器、或加入全局缓存的引用,必须在对象销毁时对称地反注册/清除,禁止依赖"对象被回收后监听自动失效"的侥幸心理。
为什么: 最隐蔽的内存泄漏:UI 面板注册了全局事件总线的监听(EventBus.on("kill", OnKill)),面板关闭时忘了反注册。面板对象被「销毁」了,但事件总线还持有对它的引用,GC 永远不会回收它。玩家反复开关面板,内存里堆着几十个"已关闭"的面板实例。更严重的是:Dead 对象收到事件后访问已回收的 UI 组件,直接 NullReferenceException 崩溃。
怎么做:
// Unity 示例
public class KillFeedUI : MonoBehaviour {
void OnEnable() {
EventBus.Subscribe("kill", OnKill); // ✅ 启用时注册
}
void OnDisable() {
EventBus.Unsubscribe("kill", OnKill); // ✅ 禁用时对称反注册
}
// 反例 — 只在 Start 注册,没有对应的 OnDestroy 反注册
// void Start() { EventBus.Subscribe("kill", OnKill); } // ❌
}
WeakReference(弱引用),或在对象销毁时主动从缓存中移除。规则: 项目立项时为内存和包体设定明确预算(如:手机目标设备内存上限 1.2GB,包体初始安装包不超过 150MB),并在 CI 或版本里程碑节点用自动化工具检查,超预算视为 P1 问题。
为什么: 最常见的失控模式:开发初期没有预算约束,美术资源按"效果最好"的规格产出,等到上线前两周发现手机低端机直接崩溃或包体超出渠道限制,再来全量优化——成本是前期的 10 倍。另一个盲区:开发机内存 16GB,测试机 8GB,发布目标机 3GB,开发全程感觉"没问题",实际目标用户全程 OOM。
怎么做:
// 反例 — 加载后没有对应的 Release,切场景时内存全留着
public class BattleManager : MonoBehaviour {
List<AsyncOperationHandle<GameObject>> handles = new();
async void SpawnEnemy(string key) {
var h = Addressables.LoadAssetAsync<GameObject>(key);
await h.Task;
handles.Add(h);
Instantiate(h.Result, spawnPos, Quaternion.identity);
}
void OnDestroy() {
// ❌ 没有 foreach Release,所有纹理/预制体留在内存里
}
}
// 正例 — OnDestroy 中对称 Release
public class BattleManager : MonoBehaviour {
List<AsyncOperationHandle<GameObject>> handles = new();
async void SpawnEnemy(string key) {
var h = Addressables.LoadAssetAsync<GameObject>(key);
await h.Task;
handles.Add(h);
Instantiate(h.Result, spawnPos, Quaternion.identity);
}
void OnDestroy() {
foreach (var h in handles) {
Addressables.Release(h); // ✅ 对称释放,引用计数归零,资源可被卸载
}
handles.Clear();
}
}
# 反例 — 每个图标单独一张纹理
Assets/UI/Icons/
sword_icon.png (独立纹理,1 Draw Call)
shield_icon.png (独立纹理,1 Draw Call)
potion_icon.png (独立纹理,1 Draw Call)
... 80 个图标 = 80 个 Draw Call ❌
# 正例 — 打成图集,共享同一纹理
Assets/UI/Atlas/
BattleHUD_Atlas.png (1 张图集,包含所有战斗 HUD 图标,1 Draw Call ✅)
LoadAssetAsync 都有对应的 Release,且在对象销毁或场景卸载时被调用到。