en un clic
game-assets-memory
管理游戏资源与内存时使用。加载/卸载、图集、包体、泄漏防治。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
管理游戏资源与内存时使用。加载/卸载、图集、包体、泄漏防治。
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
做 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,且在对象销毁或场景卸载时被调用到。