2.3 资源导入与 .meta
把一张 coin.png 拖进资源管理器,看起来只是一次文件复制。其实编辑器在背后做了三件事:把它导入成引擎认识的资源、给它分配一个身份(uuid)、在它旁边生成一个 .meta 文件。这一节讲的就是这三件事。
为什么值得单独一节?因为 .meta 是卷二里「最容易被误操作、代价又最大」的文件:删掉它,引擎会给资源换一个新身份,所有引用瞬间全断;手工改它,编辑器可能直接读不懂;忘了提交它,同事拉下来引用全空。 这三句话基本覆盖了资源管线 90% 的线上事故。
本节跟着一张贴图走完整条导入链路,看
.meta里到底存了什么、图片导入设置有哪些选项、uuid 冲突怎么排查,最后用resources按 sub-asset 路径加载一个SpriteFrame。这是第 4 章资源管线的前置课。
2.3.1 导入:从文件到资源
「文件」和「资源」不是一回事。文件系统里的 coin.png 是一个二进制文件;引擎眼里的「资源」是一个带 uuid、带类型、带导入设置的对象。中间的转换过程叫导入(import)。
导入的触发时机有三个:资源管理器里新增/修改文件、编辑器启动、手动点「刷新」。导入结果缓存在工程根的 library/ 里(1.3 讲过它不进版本库)——所以 library/ 可以随便删,编辑器会重建;但 assets/ 里的 .meta 绝对不能删。
在编辑器里观察这个过程:把 coin.png 拖进 assets/textures/,你会看到资源管理器出现 coin 条目,它的左边多了一个可展开的小箭头。展开后是一个名为 spriteFrame 的子资源——图片资源会被自动拆出一个 sprite-frame 子资源,2.3.8 加载时用的正是这个路径。
2.3.2 .meta 里存了什么
以下片段需 Cocos Creator 3.8 编辑器打开,本节不可命令行复现。它是一张图片 .meta 的结构示意:
{
"ver": "1.0.0",
"importer": "image",
"imported": true,
"uuid": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"files": [".json", ".png"],
"subMetas": {
"6c48a": {
"importer": "sprite-frame",
"uuid": "b2c3d4e5-6f70-4a8b-9c0d-1e2f3a4b5c6d",
"name": "coin"
}
},
"userData": {
"type": "sprite-frame",
"hasAlpha": true,
"fixAlphaTransparencyArtifacts": false
}
}
四个字段决定一切:
| 字段 | 含义 |
|---|---|
importer | 用哪个导入器(图片是 image,音频是 audio,JSON 是 json) |
uuid | 资源身份,全局唯一,引用靠它 |
subMetas | 子资源(图片的 spriteFrame、图集的各帧) |
userData | 你在 Inspector 里改的导入设置 |
选中 coin 图片,Inspector 会显示「图片」导入面板:Type 下拉(raw / texture / sprite-frame / normal map)、Trim 勾选、Border 九宫格输入框、Packable 勾选。这些值最终都写进 .meta 的 userData。
subMetas 是理解图片资源的关键:一张图在引擎里既能当 Texture2D 用,也能拆出一个 SpriteFrame。上面那个 name: "coin" 的子资源,就是 2.3.8 加载路径末段 /spriteFrame 所指的东西。
2.3.3 uuid:资源的身份
引擎内部不用文件路径引用资源,用 uuid。原因很实际:路径会变(改名、挪目录),uuid 不变。所以 .scene 里存的是 { "__uuid__": "..." }(2.2.4 见过),而不是 textures/coin.png。
这带来一个直接后果:删掉 .meta 再让编辑器重新导入,引擎会分配一个全新的 uuid——原来的引用全指向了一个不存在的 uuid,表现为「贴图全白、预制体全丢」。所以 .meta 必须跟源文件一起提交、一起移动。
| 操作 | 后果 |
|---|---|
| 改文件名 | uuid 不变,引用不断(安全) |
| 挪目录 | uuid 不变,引用不断(安全) |
删 .meta 重新导入 | uuid 变了,引用全断(危险) |
复制 .meta 到另一个文件 | uuid 重复,冲突(危险) |
2.3.4 图片导入设置
图片是最常改导入设置的资源。几个选项的实际影响:
Type:要当精灵用就选sprite-frame,要当纹理喂给材质就选texture。选错会表现为「拖拽槽里拖不进去」。Border:九宫格。把Border的四条边填上(如Top/Left/Bottom/Right各8),图片拉伸时四角不变形——做 UI 底板必用。Trim:裁掉透明边。做图集时开着省内存,但单独用一张图时要注意尺寸会变。Packable:是否允许进自动图集。做 UI 的图建议开,做背景大图建议关。
按用途选设置,可以少走很多弯路:
| 图片用途 | Type | Border | Packable |
|---|---|---|---|
| 角色、金币精灵 | sprite-frame | 0 | 开 |
| UI 底板、按钮 | sprite-frame | 有值 | 开 |
| 全屏背景 | sprite-frame | 0 | 关 |
| 程序化纹理输入 | texture | — | — |
操作步骤是:选中图片 → Inspector → 改设置 → 点右下角「应用」。改完,coin.png.meta 的修改时间会变,git diff 里能看到 userData 的差异——这也是为什么 .meta 必须是可 diff 的文本(1.3 强调过别把它塞进 LFS)。
2.3.5 音频导入设置
音频相对简单,但有两个选项会影响包体与播放:
| 选项 | 取值 | 建议 |
|---|---|---|
Audio Type | audio-clip | 都要用这个 |
Load Mode | Audio / Download / Web Audio | 短音效用 Web Audio(低延迟) |
Download Mode | Download / Stream | BGM 用 Stream(边下边播) |
10.1 会讲音频管理器怎么配合这些设置做音效池。这里只需记住:BGM 用 Stream、短音效用 Web Audio,选错会分别表现为「首屏卡顿」与「点击音效延迟」。
还有一点和图片不同:音频的 .meta 里没有 subMetas——一个音频文件就是一个资源,不存在子资源拆分。所以加载音频时路径不带任何后缀,直接写 audio/jump 即可,这一点在 10.1 会再次用到。
2.3.6 为什么 .meta 必须提交、又绝不能手改
两条规则看似矛盾,其实是同一件事的两面:
必须提交——因为 uuid 在 .meta 里。不提交,同事 clone 后编辑器重新生成 uuid,他本地的引用全指向新 uuid,你提交的 .scene 里的旧 uuid 就全成了悬空引用。
不能手改——因为 uuid 与 subMetas 里的键是编辑器维护的内部一致性;ver 与字段结构会随编辑器版本变化。手改一个 uuid,等于把资源从所有引用里「摘出去」;手改 subMetas 的键,等于让子资源对不上号。所有改动都通过 Inspector 完成。
唯一的例外是批量脚本:如果确实要用脚本生成 .meta(比如批量导入美术资源),必须严格复制编辑器的格式,并在导入后让编辑器「刷新」一次做校验。
如果确实遇到了「.meta 坏了、需要重新导入」,正确的操作是:在资源管理器里右键该资源 → 重新导入资源;或者把 library/ 整个删掉,重启编辑器让它全量重建。这两条都不会换 uuid,所以引用是安全的——这也是「删 library/ 没事、删 .meta 出事」这个不对称的来源。
2.3.7 uuid 冲突怎么排查
最隐蔽的一类事故是 uuid 重复:两个不同的资源文件拥有同一个 uuid。它几乎总是来自「复制粘贴」——你把 coin.png 连同 coin.png.meta 一起复制成 coin2.png,两个文件共享了同一个 uuid。
症状是随机的:有时候加载出来是旧图,有时候是新的,取决于引擎先解析到哪个。排查方法:
- 搜一下工程里有没有重复的 uuid 字符串(在
.meta与.scene里搜)。 - 最干净的修法是删掉复制出来的那个
.meta,让编辑器重新生成一个新 uuid。 - 如果引用已经绑错,改完后要重新在 Inspector 里绑一遍。
预防手段很简单:复制资源用编辑器的「复制/粘贴」菜单,不要用文件管理器——编辑器会自动分配新 uuid。
2.3.8 用代码加载资源
资源导入之后,运行时按 sub-asset 路径加载。注意图片的 SpriteFrame 要写 /spriteFrame 后缀:
import { _decorator, Component, Sprite, SpriteFrame, resources } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('IconLoader')
export class IconLoader extends Component {
@property(Sprite)
target: Sprite | null = null;
loadIcon(name: string): void {
resources.load(`textures/icons/${name}/spriteFrame`, SpriteFrame, (err, frame) => {
if (err) {
console.error('[block-dash] 图标加载失败:', err.message);
return;
}
if (this.target) this.target.spriteFrame = frame;
});
}
}
resources 是内置 Bundle,它的根目录是 assets/resources/——所以上面这个路径实际指向 assets/resources/textures/icons/<name>/spriteFrame。路径里不带扩展名(1.2.4 的 configPath 也是同理),/spriteFrame 是图片的子资源名,来自 2.3.2 里 subMetas 的那个 name。
拿到 SpriteFrame 后赋给 Sprite.spriteFrame,图就换了。别用 assetManager.loadRemote 去加载包内资源——那是给远程 URL 用的(4.3 会讲)。
2.3.9 常见导入错误速查
下面这张表覆盖了资源导入阶段 90% 的报错。遇到时按现象对号入座:
| 现象 | 最可能的原因 | 修法 |
|---|---|---|
| 贴图全白 | .meta 被删,uuid 变了 | 撤销删除,或重新绑定引用 |
| 拖拽槽拖不进 | 图片 Type 不是 sprite-frame | 改 Type 后应用 |
| 加载返回 null | 路径没带 /spriteFrame 后缀 | 补上子资源后缀 |
| 加载返回 null | 文件不在 assets/resources/ 下 | 挪进 resources/ 或走 Bundle |
| 编辑器报「资源损坏」 | LFS 没 install,拉的是指针 | git lfs install 后重新拉 |
| 加载出来是旧图 | uuid 重复,解析到了另一个文件 | 删复制出来的 .meta 重建 |
| 构建后图片丢失 | 资源名大小写与引用不一致 | 全小写重命名(见 1.2.8) |
| 面板改了设置不生效 | 忘了点「应用」 | 点「应用」后重新导入 |
前两条是新手最高频的;中间两条则是「代码看着没错、就是拿不到资源」的典型,几乎都出在路径上。
2.3.10 字体与图集资源
除了图片和音频,还有两类资源值得提前认识:
字体。把 .ttf 拖进 assets/ 会导入成字体资源,挂到 Label 的 Font 槽上就能用。中文游戏常遇到的问题是字体文件太大(一个中文字库动辄几 MB),对策是只打包用到的字形(11.3 会讲)。等宽数字可以用 bitmap font,但那是另一套导入流程。
图集(atlas)。把多张小图打成一张大图,是降低 Draw Call 的核心手段(13.1 会量化)。Cocos 里有两种做法:一是手动创建 .atlas 资源并把图片拖进去;二是打开图片的 Packable,让引擎在构建时自动合图。两种方式的 .meta 都会记录各自的帧信息,落在 subMetas 里。
| 资源类型 | 导入器 | 关键设置 |
|---|---|---|
| 图片 | image | Type / Border / Packable |
| 音频 | audio | Load Mode / Download Mode |
| 字体 | font | 打包字形范围 |
| 图集 | sprite-atlas | 包含的图片列表 |
图集不是越大越好:一张 2048×2048 的图集如果只装了 10 张小图,浪费的显存远超收益。经验做法是按「使用场景」合图——同一屏同时出现的 UI 放一张,不同关卡的道具分开放。这样既能合批,又不会为了加载一个图标把整张巨图拉进内存。
2.3.11 资源命名与目录纪律
1.2.8 定过命名约定,这里补上「放进 assets/ 之前」的三条纪律:
- 原始素材不进
assets/。.psd、.ai、未压缩的.wav留在工程外的素材库,只把导出后的.png/.mp3拖进来。否则主包会被源文件撑爆。 - 一图一用途,不搞「万金油图」。 一张图既当 UI 又当场景元素,改一处会牵动另一处;宁可多导出一份。
- 导入后立刻设好
Type与压缩。 默认设置往往不是你要的,攒到最后统一改,容易漏掉几张,构建出来才发现某张图没压缩。
这三条和 1.3.7 的角色分工是一体的:美术只往 textures/ audio/ 里放导出后的成品,程序只管引用,谁也不越界。
做到这一点,git diff 里出现的资源改动就都是「有意义的成品变化」,而不是「某人把源文件拖进来了」。这也是把资源管线做成纪律、而不是靠自觉的意义——第 4 章的 Bundle 分包,正建立在这套干净的目录与命名之上。
2.3.12 本节自检清单
- 每张图片、每个音频旁边都有对应的
.meta,且都被提交 -
.meta是文本、可 diff,没有进 LFS - 没有手工编辑过任何
.meta - 没有出现重复 uuid(搜过
.meta) - 图片的
Type选的是sprite-frame(要用作精灵时) - BGM 的
Download Mode是Stream,短音效是Web Audio -
resources.load的路径带/spriteFrame子资源后缀 - 原始素材(
.psd/.wav)没有进assets/ - 需要合批的图已勾选
Packable,背景大图未勾选 - 每张图导入后都设好了
Type,没有留默认值 - 遇到「资源损坏」时先确认 LFS 已
install - 复制资源走编辑器菜单,没有直接复制
.meta - 音频加载路径不带后缀,图片带
/spriteFrame
2.3.13 工程视角的收口
第 2 章到此结束。这两章里,工程从「只有目录骨架」长出了真正的内容:main.scene、coin.prefab、level-tuning.ts,以及一批带 .meta 的资源。你现在应该能回答三个问题——节点树存成什么、面板的值存成什么、资源靠什么被引用。答案分别是 .scene 的 __id__、__type__ 下的字段、.meta 里的 uuid。
第 3 章转向代码本身:@ccclass 与 @property 的类型系统、组件生命周期的执行顺序、组件之间怎么通信。2.2 已经让你见过 @property 的用法,3.1 会把它的类型规则讲透——包括为什么 Cocos 的装饰器和标准 TypeScript 装饰器不完全一样。
阅读导航:上一节:2.2 属性面板与序列化 · 下一节:3.1 @ccclass / @property 与类型系统 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。