个人游戏技术选型案例:资源加载为什么从直接引用改成分组加载

一个个人 2D 冒险游戏在直接资源引用、Resources 目录、Addressables 和自定义资源清单之间做技术选型的案例,详细讨论包体、加载卡顿、内存和发布复杂度。

写在前面:小项目也会被资源管理拖住

很多个人 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_ui
  • shared_audio
  • shared_character
  • chapter_01
  • chapter_02
  • chapter_03
  • demo_only

每个章节只放该章节独有资源。
通用资源放共享组。

这样好处很直接:

  • 进入章节前加载该章节组
  • 离开章节后释放章节独有资源
  • Demo 构建只包含 sharedchapter_01demo_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
  • 哪些资源属于正式版

个人游戏做技术选型时,资源加载常常被低估。
一旦它失控,性能、包体、发布和调试都会被拖住。

让资源边界清楚,比追求复杂热更新更重要。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页