外观
Stage03|Runtime Objects & Assets
Lesson029|编辑器导入系统与运行时资源系统的区别
Tags: #Creator2.x #Stage03 #AssetDB #Import #Build #RuntimeDifficulty: ⭐⭐⭐⭐☆
一、本课从“文件明明在 assets 里,为什么加载失败”开始
开发者把头像放到:
text
assets/game-data/avatars/player-01.pngCreator 资源管理器能看到它,Sprite 组件也能通过 Inspector 使用。于是运行时代码写成:
ts
cc.resources.load(
'game-data/avatars/player-01.png',
cc.SpriteFrame,
callback
);结果报找不到资源。开发者随后尝试:
- 加上
assets/前缀; - 改用操作系统绝对路径;
- 复制到多个目录;
- 删除
.meta让编辑器“重新生成”; - 修改
library/中的导入产物。
这些尝试混淆了三套不同系统:
text
源文件与编辑器导入
→ 构建时资源收集与打包
→ 游戏运行时资源定位和加载本课要回答的核心问题是:
“项目里有这个文件”“编辑器认识这个资源”和“运行时能按路径加载”为什么是三件事?
二、本课目标
完成本课后,你应该能够:
- 解释 AssetDB 导入系统的职责。
- 区分源文件、
.meta、导入产物和运行时 Asset。 - 说明资源为什么会或不会进入构建。
- 选择 Inspector 静态引用、
resources动态加载或 Asset Bundle。 - 识别图片的 Texture2D、SpriteFrame 和 Atlas 子资源。
- 用 Creator 2.4.x 实验验证路径、扩展名、类型和构建边界。
- 避免修改
library/、伪造.meta或硬编码 UUID。
三、先建立一条完整资源流水线
以 player-01.png 为例:
text
源文件进入 assets
↓
AssetDB 发现文件与 .meta
↓
识别类型、导入设置、UUID、子资源和依赖
↓
生成编辑器缓存/导入产物
↓
Scene、Prefab 或脚本建立资源引用
↓
构建系统收集需要发布的资源
↓
生成平台资源布局、配置和包体
↓
运行时 Asset Manager 按引用或路径取得 Asset
↓
Sprite 等组件使用 SpriteFrame每个箭头都可能失败,而且错误表现不同。排查时必须先确定问题属于哪一段。
四、AssetDB 管的不是“运行时读文件”
Creator 编辑器中的 AssetDB 负责把项目资源变成可被编辑器识别和引用的数据。
它通常需要处理:
- 监听
assets/中的新增、移动、重命名和删除; - 根据扩展名选择导入器;
- 读取导入设置;
- 建立或维护 UUID;
- 生成图片的 Texture/SpriteFrame 等资源信息;
- 建立资源依赖索引;
- 通知编辑器界面刷新;
- 为脚本编译和组件识别提供信息。
所以拖入一张 PNG 后,资源管理器出现缩略图,不是“编辑器直接把源 PNG 当 Sprite 用”,而是导入系统已经识别并组织了它。
AssetDB 主要存在于编辑器工作流。发布后的游戏不会打开 AssetDB 窗口扫描你的 assets/ 源目录。
五、.meta 是资源身份与导入设置的一部分
Creator 为资源维护 .meta。其中最重要的概念是 UUID:
text
文件路径可能改变
UUID 用于维持序列化引用身份
导入设置描述资源应如何处理如果 Scene 中 Sprite 引用了某个 SpriteFrame,序列化关系不是简单保存“Windows 文件路径”。资源移动后,只要通过 Creator 编辑器/AssetDB 正确完成,引用有机会随资源身份继续保持。
必须遵守:
- 新资源由 Creator 编辑器/AssetDB 生成
.meta; - 不手工伪造 UUID;
- 移动、重命名、删除资源优先在 Creator 编辑器中操作;
- 替换已有图片内容时保留原
.meta,维持引用和导入设置; - 复制为新资源时不要复用旧
.meta,避免 UUID 冲突。
.meta 是项目数据,不是修复加载错误时随手删除的缓存。
六、library/ 为什么不能作为修复目标
library/、temp/、local/、build/ 是导入、预览、构建或本机缓存产物。它们有两个共同特征:
- 内容由工具生成;
- 下一次导入或构建可能覆盖。
直接修改 library/ 中某个 JSON 或图片,也许会让当前机器短暂出现变化,但源资源和导入规则没有改变:
text
手改缓存
→ 当前预览似乎恢复
→ AssetDB 再导入
→ 修改丢失
→ 其他成员机器仍失败正确修复点应该是源资源、导入设置、合法的编辑器引用、构建配置或运行时代码。
七、一张 PNG 为什么能对应多个资源对象
开发者口中的“图片”可能指不同层:
text
player-01.png
├── 源文件
├── Texture2D:纹理数据与 GPU 使用对象
└── SpriteFrame:描述纹理中的可显示区域、旋转、边界等Sprite 组件通常需要:
ts
sprite.spriteFrame = frame;它要的是 cc.SpriteFrame,不只是一个文件字节数组。
动态加载时明确类型:
ts
cc.resources.load(
'avatars/player-01',
cc.SpriteFrame,
(err, frame: cc.SpriteFrame) => {
if (err) {
cc.error('[Avatar] load failed', err);
return;
}
this.avatar.spriteFrame = frame;
}
);如果按 Texture2D 加载:
ts
cc.resources.load(
'avatars/player-01',
cc.Texture2D,
(err, texture: cc.Texture2D) => {
// texture 与 SpriteFrame 不是同一层对象
}
);资源路径相同不意味着请求类型可以任意互换。L033 会继续深入 Texture、SpriteFrame 和 Atlas 的依赖关系。
八、编辑器引用为什么不要求资源位于 resources
假设 Prefab 的 Sprite 属性在 Inspector 中已经绑定 assets/ui/common/button.png 对应的 SpriteFrame。
关系是:
text
Button.prefab
→ 序列化引用 SpriteFrame
→ 构建系统沿依赖关系收集资源
→ 运行时实例恢复引用这个图片不需要因为“运行时要用”就搬进 resources。它通过 Prefab 的静态依赖进入构建。
固定依赖优先 Inspector 绑定,优势是:
- 引用在编辑器中可见;
- 类型可以检查;
- Prefab/Scene 自描述;
- 构建系统可沿依赖收集;
- 无需业务代码拼路径和管理异步。
这与 L023~L025 的序列化引用链完全相连。
九、什么时候需要 resources 动态加载
Creator 2.4.x 的 resources 目录为运行时按相对路径加载提供入口。例如:
text
assets/resources/avatars/player-01.png加载路径通常写:
ts
cc.resources.load(
'avatars/player-01',
cc.SpriteFrame,
callback
);注意路径规则:
- 相对
resources根目录; - 通常不写
assets/resources/; - 通常不写文件扩展名;
- 类型要与想取得的 Asset 一致;
- 大小写在不同平台可能更严格。
动态加载适合:
- 配置决定的头像或皮肤;
- 大量可选内容;
- 不希望随首场景立即加载的资源;
- 运行中按需选择的资源。
代价是要处理异步、错误、缓存、并发、生命周期和释放。
十、为什么不能把所有资源都塞进 resources
“放进去就能 load”很方便,但边界会失控:
- 路径字符串分散在代码里;
- 无用资源更难发现;
- 包体和首包策略变模糊;
- 同名资源和目录迁移风险增加;
- 大量资源的加载与释放责任落到业务代码;
- 本该清晰的 Prefab 静态依赖变成隐藏运行时依赖。
选择标准:
text
固定、结构性依赖
→ Inspector/Prefab 引用
运行时按配置选择的小规模本地资源
→ resources
模块化、分包、远程或大规模内容
→ Asset Bundle不是“哪种 API 最强”,而是哪种依赖边界最清楚。
十一、构建是编辑器和运行时之间的边界
构建系统不会简单把整个项目目录原样复制出去。它需要:
- 从构建场景和依赖入口收集资源;
- 处理
resources和 Bundle 入口; - 组织依赖配置;
- 按平台压缩、转换或拆分;
- 生成运行时可定位的资源布局;
- 可能改变文件名和物理存放方式。
因此:
text
编辑器资源管理器能看到
≠ 一定进入构建
进入构建
≠ 一定能用任意源文件路径访问
本地预览成功
≠ 远程或原生构建必然成功运行时代码应遵守 Asset Manager 的逻辑路径和 Bundle 规则,不应猜构建目录中的物理文件名。
十二、开发预览为什么会掩盖问题
预览环境通常离源项目和编辑器服务更近,可能具有:
- 更宽松或不同的资源服务方式;
- 已经存在的编辑器缓存;
- 大小写不敏感的 Windows 文件系统;
- 与原生平台不同的解码能力;
- 本地文件,没有网络与 CDN 缓存;
- 未开启正式包的压缩和分包策略。
常见跨平台问题:
text
代码写 avatars/Player-01
实际文件 avatars/player-01
Windows 预览可能不明显
区分大小写的平台或远端 CDN 加载失败资源功能至少要在目标构建形态验证一次,而不是只看编辑器预览。
十三、Asset Bundle 解决的是另一层组织问题
Creator 2.4.x Asset Manager 支持 Asset Bundle。Bundle 可以把一组资源作为明确模块组织,并可配置本地、远程等策略。
概念流程:
text
加载 Bundle
→ 在 Bundle 中按路径加载 Asset
→ Asset 加载自己的依赖
→ 业务持有并使用
→ 按 Bundle/Asset 所有权管理释放典型方向:
text
base:启动必需
lobby:大厅模块
battle:战斗模块
events:活动远程内容Bundle 不是把目录改个名字就完成。还需要版本、CDN、缓存、失败回退和依赖边界,L034 会专门展开。
十四、贯穿案例为什么加载失败
原文件:
text
assets/game-data/avatars/player-01.png原代码:
ts
cc.resources.load(
'game-data/avatars/player-01.png',
cc.SpriteFrame,
callback
);至少有两个错误:
- 文件不在
resources目录,不能通过cc.resources的根路径定位; - 动态资源逻辑路径通常不带源文件扩展名。
有三种合法修复方向。
方案 A:固定头像
在组件中声明属性,通过 Inspector 绑定:
ts
@property(cc.SpriteFrame)
defaultAvatar: cc.SpriteFrame = null;适合固定默认图。
方案 B:本地按配置加载
通过 Creator 编辑器将资源移动到:
text
assets/resources/avatars/player-01.png等待 AssetDB 完成导入,再加载:
ts
cc.resources.load('avatars/player-01', cc.SpriteFrame, callback);方案 C:头像属于独立内容模块
放入明确的 Asset Bundle,通过 Bundle API 加载。适合大量、分包或远程资源。
不能采用的“修复”包括硬编码 UUID、读取编辑器 library 路径和手工修改 .meta。
十五、Creator 2.4.x 编辑器实验
资源创建、移动、重命名和导入都通过 Creator 编辑器/AssetDB 完成。不要手工创建或修改
.meta,不要修改library/、temp/、local/或build/。
实验目标
验证:
- Inspector 静态引用不要求资源位于
resources; cc.resources.load使用相对路径且不带扩展名;- 请求 SpriteFrame 和 Texture2D 得到不同类型;
- 重命名后字符串路径会失效,而编辑器引用的维护机制不同。
准备资源
- 在 Creator 资源管理器中新建
assets/resources/import-lab/。 - 通过资源管理器导入一张小 PNG,命名
sample-icon.png。 - 等待导入完成。
- 创建
ImportRuntimeLab.scene。 - 创建两个 Sprite 节点:
StaticSprite和DynamicSprite。
实验 A:静态引用
把 sample-icon 对应 SpriteFrame 拖到 StaticSprite 的 Sprite Frame 属性。
预期:运行场景时 StaticSprite 显示图片,没有调用 cc.resources.load。
接着再导入另一张图片到非 resources 目录,通过 Inspector 绑定到新 Sprite。预期仍能显示,因为它由 Scene 静态引用进入依赖图。
创建 ResourceLoadProbe.ts
ts
const { ccclass, property } = cc._decorator;
@ccclass
export default class ResourceLoadProbe extends cc.Component {
@property(cc.Sprite)
target: cc.Sprite = null;
start() {
this.loadAsSpriteFrame();
}
private loadAsSpriteFrame() {
cc.resources.load(
'import-lab/sample-icon',
cc.SpriteFrame,
(err, frame: cc.SpriteFrame) => {
if (err) {
cc.error('[ImportLab] SpriteFrame failed', err);
return;
}
cc.log(
'[ImportLab] SpriteFrame loaded',
frame instanceof cc.SpriteFrame
);
this.target.spriteFrame = frame;
}
);
}
}通过 Inspector 把 DynamicSprite 绑定到 target。
实验 B:正确路径
运行场景。预期:
text
[ImportLab] SpriteFrame loaded true
DynamicSprite 显示 sample-icon实验 C:三种错误路径
依次测试:
text
assets/resources/import-lab/sample-icon
resources/import-lab/sample-icon
import-lab/sample-icon.png预期:它们不符合 cc.resources 的逻辑路径规则,应进入 err 分支。保存每次具体错误日志。
实验 D:请求 Texture2D
把类型临时改为:
ts
cc.resources.load(
'import-lab/sample-icon',
cc.Texture2D,
(err, texture: cc.Texture2D) => {
cc.log(
'[ImportLab] Texture loaded',
texture instanceof cc.Texture2D
);
}
);预期:拿到 Texture2D,而不是可直接赋给 sprite.spriteFrame 的 SpriteFrame。这证明“同一源图片”可以有不同 Asset 层次。
实验 E:重命名路径
在 Creator 资源管理器中把 sample-icon.png 重命名为 sample-icon-v2.png,等待导入完成。
观察:
- Inspector 静态引用是否仍保持,由编辑器实际结果验证;
- 旧字符串路径
import-lab/sample-icon应加载失败; - 新路径
import-lab/sample-icon-v2应成功。
这个实验显示:UUID 引用与路径字符串具有不同重构特性。
十六、实验失败时怎么查
资源管理器没有出现图片
text
文件是否真正进入项目 assets
→ Creator 控制台是否有导入错误
→ 文件格式是否可识别
→ AssetDB 是否正在刷新
→ .meta 是否由编辑器正常生成不要先修改 library 或伪造 .meta。
Inspector 可绑定,resources.load 却失败
这恰好证明静态引用与动态路径是不同入口。检查资源是否位于某个 resources 根下、逻辑路径是否相对该根、是否去掉扩展名、大小写是否一致。
路径正确但类型失败
确认需要的是 SpriteFrame、Texture2D、JsonAsset、Prefab 还是其他 Asset。查看编辑器中资源及子资源的实际类型。
编辑器成功,构建失败
text
资源是否进入构建依赖
→ 大小写是否完全一致
→ Bundle 是否已加载
→ 远程地址和版本是否正确
→ 目标平台是否支持资源格式
→ 是否错误依赖编辑器绝对路径十七、真实项目故障:更新 PNG 后部分界面还是旧图
现象
美术用同名文件替换 button-buy.png。某些场景显示新图,另一些构建仍显示旧图。团队开始删除所有 .meta 和 library。
风险
删除 .meta 会让资源获得新身份,已有 Scene/Prefab 引用可能丢失;大范围删缓存又掩盖究竟是导入、构建还是远端缓存问题。
证据链
text
确认源文件内容和修改时间
→ 在 Creator 中等待该资源重新导入
→ 检查原 .meta 是否保留
→ 在引用该图的 Prefab 中确认 SpriteFrame
→ 做干净的目标平台构建
→ 若为远程资源,检查版本号、URL 和 CDN 缓存
→ 在运行时记录实际 Bundle 与资源路径正确原则
替换已有资源内容时保留原 .meta。需要排除缓存时,也要先确认缓存目录和生成机制,并通过编辑器或构建流程重建,不能把删除资源身份文件当成常规刷新按钮。
十八、错误方案为什么危险
错误一:硬编码 UUID 加载资源
UUID 是资源身份机制,不是业务可读路径 API。硬编码会降低可维护性,也绕过明确的 Bundle/资源目录边界。
错误二:运行时读取 assets/ 绝对路径
发布包中的资源布局已经由构建系统重组,用户设备上通常不存在开发机的项目路径。
错误三:手改 library/ 修复导入结果
生成缓存会被重建,修改不可重复、不可协作。
错误四:删除 .meta 解决一切
这可能改变 UUID,造成引用丢失和大面积无关变更。
错误五:所有资源放 resources
短期省去入口设计,长期扩大包体、隐藏依赖并增加运行时管理成本。
错误六:回调没有 err 就说明类型正确
还应验证取得的 Asset 类型以及下游组件真正需要的对象。
十九、版本边界
本课主线是 Cocos Creator 2.4.x:
- 新代码使用
cc.resources、cc.assetManager和cc.AssetManager.Bundle。 - 存量项目可能仍有
cc.loader.loadRes,但它属于旧资源系统兼容入口。 - 2.4.x 不应假设旧
cc.loader与 Asset Manager 的缓存、依赖和释放语义完全相同。
Creator 3.x 的资源模块、类型导入和部分路径/API 表达不同。课程中的 cc. 全局写法不能直接复制到 3.x。
项目升级时先确认当前引擎小版本、资源目录和构建策略,再迁移 API。
官方验证入口:
二十、练习
A. 概念题
- AssetDB、构建系统和运行时 Asset Manager 各解决什么问题?
- 为什么 SpriteFrame 和 Texture2D 不是同一个对象?
- 为什么非 resources 目录资源仍可通过 Inspector 引用进入游戏?
- 为什么不能手工修改
library或伪造.meta?
B. 选择题
为下列资源选择静态引用、resources 或 Asset Bundle:
text
按钮 Prefab 固定背景
根据角色 ID 选择的 20 个本地头像
数百兆远程活动资源
启动场景必需的默认字体说明理由。
C. 诊断题
assets/resources/ui/Icon.png 在 Windows 编辑器预览能加载,安卓包中 cc.resources.load('ui/icon', cc.SpriteFrame) 失败。给出排查顺序。
D. 迁移题
旧项目大量使用 cc.loader.loadRes。为什么不应只做文本替换为 cc.resources.load 就宣布迁移完成?
二十一、练习参考答案
A. 概念题答案
- AssetDB 识别源资源、维护身份/导入设置和编辑器索引;构建系统收集依赖并生成平台资源布局;Asset Manager 在运行时定位、加载、缓存和组织 Asset。
- Texture2D 表示纹理数据,SpriteFrame 描述如何从纹理取出可供 Sprite 显示的区域及相关信息;一个纹理可以被一个或多个 SpriteFrame 使用。
- Scene/Prefab 序列化了对资源的依赖,构建系统会沿静态依赖收集它,不需要通过 resources 路径查找。
library是可重建缓存;.meta承载资源身份和导入设置。手改会产生不可重复结果、UUID 冲突或引用丢失。
B. 选择题答案
text
按钮固定背景 → Inspector 静态引用
20 个本地头像 → resources 可接受,也可按模块 Bundle;取决于加载策略
数百兆远程活动资源 → 独立远程 Asset Bundle
启动默认字体 → Scene/Prefab 静态引用,除非有明确动态替换需求关键是固定依赖显式化,可选内容按生命周期和发布模块组织。
C. 诊断题答案
text
1. 检查实际文件名 Icon 与代码 icon 的大小写。
2. 确认文件确在 resources 根下并已由 AssetDB 导入。
3. 确认逻辑路径不带扩展名和 resources 前缀。
4. 在编辑器检查请求的是 SpriteFrame 子资源。
5. 检查资源是否进入安卓构建和对应配置。
6. 查看设备日志中的完整 err,而不是只看 UI 空白。
7. 检查是否加载了错误 Bundle 或同名资源。
8. 修正后重新构建并在目标设备验证。题目中特别可疑的是大小写:Windows 文件系统可能掩盖 Icon 与 icon 的差异。
D. 迁移题答案
Asset Manager 与旧 loader 在 Bundle、缓存、依赖、引用计数和释放语义上存在差异。迁移需要盘点加载入口、路径、类型、并发回调、缓存所有权、释放 API 和构建配置,并做目标平台回归。只替换函数名可能让“能编译”掩盖生命周期错误。
二十二、本课总结
text
源文件存在、AssetDB 导入成功和运行时可加载是三件事。
.meta 维护资源身份和导入设置,不能手工伪造。
library/temp/build 是生成目录,不是修复源头。
固定依赖优先 Inspector 引用。
resources 使用相对根路径、通常不带扩展名,并要指定正确类型。
构建会重组资源,运行时不应依赖开发机文件路径。
Texture2D、SpriteFrame 和 Atlas 位于不同资源层次。二十三、下一课预告
text
Lesson030|resources.load / loadRes 背后的加载流程下一课会沿着一次 cc.resources.load 请求继续向下:路径怎样解析、缓存如何命中、依赖何时加载、回调为何异步,以及错误和竞态怎样进入工程代码。
