写在前面:小项目也会被资源管理拖住
很多个人 2D 游戏早期不需要复杂资源系统。
图片拖进场景。
音效挂到组件上。
Prefab 直接引用贴图。
脚本里保留一些配置引用。
这样做很快,也很符合原型开发。
但当内容增长后,资源引用会悄悄变成问题:
- 主菜单加载很慢
- 内存一直降不下来
- 某些章节资源提前被打进包
- 切场景时卡顿
- 删除资源后还有隐藏引用
- Demo 包包含正式版资源
陆青做过一款 2D 叙事冒险游戏。
玩家在一列长途夜车上穿过不同车厢,与乘客对话,收集车票、行李标签和旧照片。
项目从一个 40 分钟短篇扩展到 6 个章节后,资源加载问题开始明显。
她在直接引用、Resources 目录、Addressables 和自定义清单之间做了技术选型。
最终采用“按章节分组的 Addressables,加少量自定义资源清单”。
一、直接引用为什么失控
早期最方便的方式是直接引用。
每个场景引用自己的背景、角色、音频和 UI。
对 Unity 或类似引擎来说,这很自然。
问题是,场景之间共享资源越来越多:
- 主角立绘
- 通用 UI
- 常用音效
- 车厢基础贴图
- 字体
- 对话框
- 物品图标
有些资源被多个 Prefab 间接引用。
有些资源只在后期章节用,却因为主菜单预览而被提前加载。
陆青发现,自己已经很难回答:
第一章启动时到底加载了哪些资源?
Demo 包里为什么包含第五章音频?
切到餐车时为什么突然卡 2 秒?
当开发者无法解释资源加载,就很难优化。
二、Resources 目录不是长期答案
她考虑过把资源放进 Resources 目录,然后按路径加载。
优点是简单。
脚本里 Load("chapter_01/bg_train") 就能拿到资源。
但问题也明显:
- 路径字符串容易写错
- 缺少构建期依赖检查
- 资源容易全部进入包
- 卸载和引用关系不清楚
- 重命名成本高
- 后期难做分包
Resources 适合少量、明确、稳定的资源。
不适合作为整个游戏的内容加载管线。
个人项目一旦把大量内容都丢进去,后面会很难清理。
三、Addressables 的优势和成本
Addressables 能解决很多问题:
- 按地址加载
- 按组管理
- 异步加载
- 依赖分析
- 远程或本地包
- 更清晰的卸载
- 构建包体更可控
但它也有学习成本。
陆青担心:
- 配置复杂
- 构建出错难查
- 异步加载影响代码结构
- 资源地址命名需要规范
- 小项目可能过度工程
所以她没有一上来把所有资源都 Addressable。
她先做了两周验证,只迁移第一章和通用 UI。
验证目标很明确:
- 第一章能异步加载
- 离开第一章能释放大资源
- Demo 包能排除后续章节
- 加载界面能显示进度
- 找不到资源时能给出清楚错误
通过后才正式迁移。
四、按章节分组
陆青把资源分成几类组:
shared_uishared_audioshared_characterchapter_01chapter_02chapter_03demo_only
每个章节只放该章节独有资源。
通用资源放共享组。
这样好处很直接:
- 进入章节前加载该章节组
- 离开章节后释放章节独有资源
- Demo 构建只包含
shared、chapter_01和demo_only - 正式版构建包含全部章节
她还规定,章节资源不能直接引用后续章节资源。
这个规则用脚本检查。
如果第一章 Prefab 引用了第五章贴图,构建时会警告。
五、为什么还需要自定义资源清单
Addressables 解决加载,但不解决所有内容关系。
陆青仍然需要知道:
- 某章节有哪些背景
- 某角色在该章节有哪些表情
- 某段对话需要预加载哪些语音
- 某个物品图标属于哪个章节
她做了一个简单资源清单。
例如章节清单记录:
chapter = "chapter_02"
backgrounds = ["carriage_dining_night", "corridor_rain"]
characters = ["mei", "conductor_old"]
voiceBanks = ["ch02_dialogue_main"]
preload = ["ticket_machine", "rain_loop"]
游戏根据清单决定加载顺序。
Addressables 负责实际加载资源。
这让内容组织更清楚。
工具不是互斥的。
引擎系统负责资源寻址,自定义清单负责游戏语义。
六、异步加载如何避免代码变乱
迁移后,很多资源加载变成异步。
如果每个界面、每段对话都自己写加载逻辑,代码会很乱。
陆青做了一个 ChapterLoader:
- 读取章节清单
- 预加载必须资源
- 显示加载进度
- 缓存常用资源句柄
- 进入章节后交给场景使用
- 离开章节时释放
业务代码不直接调用底层加载。
例如对话系统只请求:
GetPortrait("mei", "sad")
它不关心资源来自哪个 bundle,也不关心加载路径。
这保持了代码边界。
七、内存释放要实际验证
陆青一开始以为调用释放就好了。
后来发现,资源仍然被引用:
- 某个单例保存了角色立绘
- UI 预览缓存没清
- 对话历史记录保留了语音引用
- 调试面板保留了背景图
她加入了章节切换内存检查。
每次离开章节后,在开发构建里打印:
- 当前加载组
- 未释放资源
- 最大贴图内存
- 音频缓存大小
- 仍持有引用的系统
这让泄漏更早暴露。
资源系统不只是加载。
卸载同样重要。
八、最终取舍
陆青的最终方案是:
- 通用资源和章节资源分组
- Addressables 负责异步加载和依赖
- 自定义清单负责章节语义
- Demo 和正式版用不同构建配置
- 章节切换集中加载和释放
- 开发构建检查跨章节引用
- 不做远程热更新
她没有接远程资源更新。
因为这是单机叙事游戏,没有必要增加 CDN、版本校验和补丁复杂度。
分组加载已经解决了当前问题。
结语:资源加载方案要让内容边界可见
陆青的项目不是大型开放世界。
但内容变多后,资源管理仍然成了技术风险。
好的资源方案不是把所有东西都换成高级系统。
而是让开发者知道:
- 什么时候加载
- 加载了什么
- 谁还引用它
- 什么时候释放
- 哪些资源属于 Demo
- 哪些资源属于正式版
个人游戏做技术选型时,资源加载常常被低估。
一旦它失控,性能、包体、发布和调试都会被拖住。
让资源边界清楚,比追求复杂热更新更重要。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。