主题
发布资源缺失排查指南
本文用于排查这样一类问题:编辑器里调试正常,但玩家通过游戏平台进入游戏后,出现模型看不到、粒子不播放、音效听不到、动画不对或运行时报资源缺失。
本页的资源统计链用于客户端预下载云资源。项目 res 内模型、贴图、音频已封装在客户端包中,不要求资源统计或 objref.txt 条目;缺失时检查本地文件、引用及项目打包。模型包导入见 项目模型资源包导入。动画/碰撞的派生产物生成是另一项需求,不等同于项目资源预下载统计。
这类问题通常只会在真实发布链路里暴露,因为编辑器调试环境常常已经下载了全量官方资源;而玩家进入游戏前,平台只会按项目发布出的资源引用清单自动下载需要的资源。
如果问题发生在“外网环境正常,迁移到线上环境后自定义模型、粒子或音频缺失”,还要先区分是不是自定义云端资源没有同步。外网和线上的自定义云端资源互相独立,资源统计无法凭空补齐目标环境不存在的云资源。先看 部署环境与运行时环境指南,再继续排查 objref.txt / full.ref。
先理解这条链路
项目发布时,客户端资源清单的关键输入之一是项目根目录下的:
text
ref/objref.txt这份文件由 dotnet build -c Client-Resource 触发的 IFileResourceAnalyzer 在编译期间生成。分析器会扫描 GameEntry 项目的 C# 代码,找出写进 IFile 类型、IFile 属性或 IFile 字段中的资源路径,并把结果写到项目根目录的 ref/objref.txt。同时它还会生成:
text
ref/model_animation.json
ref/model_collision.jsonmodel_collision.json 收集设置了 GameDataModel.CollisionBake 的模型,供编辑器生成服务端预烘焙模型碰撞。运行时产物与排错方式见 服务端预烘焙模型碰撞。
发布流程再基于 objref.txt、地图依赖和其他 ref 文件,合成最终的:
text
ref/full.ref玩家进入游戏前,平台优先依据发布产物中的 full.ref 和相关依赖清单决定要下载哪些资源。
为什么编辑器里正常,实机却缺资源
最常见的原因不是“资源文件不存在”,而是“资源没有进入发布清单”。典型场景:
- 编辑器调试机已经有全量官方资源,本地运行能直接命中缓存。
- 发布时平台只按项目声明的资源去下载,未进入清单的资源不会自动补齐。
- 代码里通过字符串拼接、运行时计算、配置热拼接或反射间接得到资源路径,静态分析器无法可靠还原。
因此,实机缺资源时,第一反应不应该只是去查资源库,也要查这条资源统计链是否把它统计进了 objref.txt。
IFile 分析器能识别什么
IFileResourceAnalyzer 不是运行时录制器,而是编译期静态分析器。它当前主要识别这些模式:
- 直接写在
IFile类型上的字面量路径。 - 对包含
IFile属性或字段的对象初始化器赋值。 - 对
IFile属性或字段的直接赋值。 - 传给轻量 helper 的字面量参数,前提是该参数在 helper 内直接转发到
IFile属性或字段,例如RegisterGameSound("sound/custom/click.ogg")最终只做sound.Asset = asset。 GameDataModel.Asset、HumanoidSourceAnimations以及模型动画映射相关的常见写法。- 常见的单位技能动画链:
GameDataUnit(及其子类)的Model+Abilities→GameDataAbility.Animation→GameDataAnimation中的动画素材。分析器会把解析到的技能动画合并到单位模型对应的model_animation.json项中。 - 运行时要挂载的第一人称武器、手臂、瞄具或枪械视图模型,只要最终要随包发布,也应以
GameDataModel/GameDataAnimation/ 显式资源清单的方式让分析器看见。 - 普通字符串字面量和
"path"u8这类 UTF-8 字面量。
典型可识别写法:
csharp
var effect = ParticleSystem.Load("effect/eff_tongyong/huoqiu2/particle.effect");
var model = new GameDataModel(heroModel)
{
Asset = "characters/general/sk_basic2/model.prefab"u8,
HumanoidSourceAnimations =
[
"anim/human/barehanded_anim/hand_05/skill_01.ani",
],
};HumanoidSourceAnimations 不再是技能动画进入烘焙列表的唯一入口。例如,单位的 Model 和 Abilities、技能的 Animation、以及对应 GameDataAnimationSimple.File / 组合动画素材都使用静态且可追踪的写法时,分析器可以自动展开这条引用链。
不过这仍然是编译期静态分析,不是完整的运行时求值。helper 封装、运行时条件、动态集合修改、反射、复杂工厂或无法静态还原的 link 都可能让分析器漏掉动画。对于必须稳定烘焙的动画,仍建议把路径显式写进对应 GameDataModel.HumanoidSourceAnimations 作为保险。
这里的显式字段只解决动画清单声明。Maker 导入的外部 MDL 必须先按 Maker 人形工作流 读取源/目标骨架并完成显式重定向;不能因为模型已进入 GameDataModel.Asset 或已填写 HumanoidSourceAnimations,就跳过 boneMap、alignmentBones 和实际动作验收。
IFile 分析器识别不到什么
下面这些写法都应默认视为高风险,因为它们未必能稳定进入 objref.txt:
- 字符串拼接,例如
"effect/" + effectName + "/particle.effect"。 - 插值字符串里带变量,例如
$"effect/{effectName}/particle.effect"。 - 从配置表、Json、数据库、网络消息中读出后再传给资源 API。
- 多层封装后才构造路径,最终赋值点看不到字面量。
- 用普通
string到处传路径,最后才在深层函数里喂给资源 API。 - 只在运行时批量
new GameDataSound/new GameDataActorSound,并从数组、配置或逻辑分支里生成Asset路径;这类写法即使表面用了GameDataSound类型,也不等同于静态可收集数据。
换句话说,分析器更擅长“代码里就能直接看见的资源路径”,不擅长“运行时才算出来的资源路径”。
如果你的代码是 AI 助手生成的,或者手写时图方便直接拼接字符串,尤其要警惕这一点。
应该先看哪些文件
先看项目根目录下这几个文件:
text
<ProjectRoot>/
├── ref/objref.txt
├── ref/model_animation.json
├── ref/model_collision.json
└── Client-Resource.log意义分别是:
ref/objref.txt:编译期统计出的原始资源清单。ref/model_animation.json:模型和动画的映射结果,主要帮助动画依赖展开。ref/model_collision.json:声明了CollisionBake的模型与烘焙配置,供编辑器生成服务端碰撞产物。Client-Resource.log:手动执行资源统计时保留的编译日志,便于搜诊断码和异常。
如果是通过编辑器发布流程自动统计,成功后日志可能被清理;这时更适合用手动方式再跑一遍,保留日志排查。
最直接的排查步骤
1. 先确认资源路径本身写对了
先对照 项目资源路径指南 检查:
- UI 图片是否放在
ui/下,代码路径不带ui/前缀。 - 项目私有模型、粒子、音频是否放在
res/下,代码路径不带res/前缀。 - 是否误把引擎资源当成
user_files/...普通文件来处理。
路径规则写错时,统计成功也没有意义,因为运行时本身就加载不到。
2. 手动生成一次资源统计
项目根目录通常自带:
text
生成资源统计ref.bat它本质上执行的是:
powershell
dotnet build src\GameEntry.csproj -c Client-Resource直接在项目根目录执行这份 bat,可以把发布流程拆开单独验证。这样做的好处是:你可以立刻查看本次统计生成的 ref/objref.txt,而不需要先走完整发布。
3. 打开 ref/objref.txt 搜具体资源
如果你怀疑某个模型、粒子或音效没被统计,直接在 ref/objref.txt 搜对应路径。例如:
characters/general/sk_basic2/model.prefabeffect/eff_tongyong/huoqiu2/particle.effectsound/.../*.ogg
判断标准很简单:
- 能搜到:说明
Client-Resource这一步已经看见了它。 - 搜不到:优先怀疑写法超出了分析器能力边界,或赋值点根本没有进入
IFile分析链。
4. 编译失败时看 Client-Resource.log
如果 bat 运行失败,或 objref.txt 没刷新,就看:
text
<ProjectRoot>/Client-Resource.log可重点搜索这些诊断码:
IFILE001:分析器整体执行失败。IFILE002:资源清单成功生成。IFILE003:输出路径解析信息。IFILE004:无法确定输出目录。IFILE005:无法创建输出目录。IFILE006:写objref.txt失败。IFILE008:model_animation.json生成成功。IFILE009:写model_animation.json失败。IFILE010:无法创建model_collision.json输出目录。IFILE011:写model_collision.json失败。IFILE012:model_collision.json生成成功。
5. 再看发布产物里的 ref/full.ref
如果 objref.txt 已经有目标资源,但实机仍然缺资源,再看发布目录里的:
text
<PublishRoot>/<ProjectName>/ref/full.reffull.ref 是最终给发布链消费的清单。它不一定和 objref.txt 逐行相同,因为发布阶段还会做依赖展开,例如从 prefab 继续补出模型、材质、纹理、动画等底层依赖。
排查重点是:
objref.txt里有没有目标资源。- 发布包里的
ref/objref.txt是否和源项目一致。 full.ref里是否已经展开出对应依赖。
常见现象和优先怀疑点
模型看不到
优先检查:
model.prefab是否出现在ref/objref.txt。- 发布后的
full.ref是否继续展开出了对应model/m.mdl、.material、纹理。 - 是否把项目私有模型错误地放到了
ui/或user_files/,而不是res/。
第一人称手臂或枪看不到
如果编辑器里正常、平台里只缺第一人称持枪视图,优先检查:
- 挂载的第一人称模型路径是否只出现在运行时代码字符串里,例如
characters/_user/fpsarms1_4u1j/model.prefab。 - 这类资源是否进入了
GameDataModel.Asset、GameDataModel.HumanoidSourceAnimations、GameDataAnimation,或项目自己的显式资源清单。 ref/objref.txt和ref/model_animation.json是否真的包含了这些路径。- 平台缓存里是否只下到了枪本体,但没有下到手臂模型或
anim/*.ani。
这类问题通常不是“资源文件写错目录”,而是“资源没有进入发布清单,所以平台没下载”。
粒子看不到
优先检查:
particle.effect是否出现在ref/objref.txt。- 粒子路径是不是运行时拼出来的。
- 粒子引用的纹理、材质是否在
full.ref中被展开出来。
粒子是最容易因为字符串拼接而漏统的一类资源。
音效听不到
优先检查:
.ogg或其他音频资源路径是否直接以字面量形式出现。- 是否通过配置表、字符串变量或运行时动态注册间接传给
SoundResource.Load(...)/GameDataSound.Asset。 - AI 生成代码是否为了省事在启动时临时注册一批
GameDataSound;静态音频定义应优先进入数据文件、代码静态定义或显式资源清单。 - 客户端和服务端是否混淆了资源路径与普通文件路径。
动画不对或部分动作丢失
优先检查:
ref/model_animation.json是否生成。GameDataModel.Asset是否用了分析器能识别的写法。- 单位的
Model/Abilities、技能的Animation与最终GameDataAnimation素材是否形成了分析器能静态追踪的引用链。 - 如果这条链比较动态,是否已在
GameDataModel.HumanoidSourceAnimations中显式列出必要动画。 - 是否把动作名当成运行时字符串拼接,而不是把具体动画资源路径显式列出。
编辑器调试时 Humanoid 技能动画播放不出来
如果现象是:官方 Humanoid 动画资源存在,例如 dash.ani,但编辑器调试时技能动作不播放,并且项目 res/anim 下没有对应模型的烘焙动画,优先按两步判断:
- 原始 Humanoid 动画资源是否存在。
- 当前模型对应的烘焙动画是否已经生成到项目
res/anim/<ModelName>/...。
前者缺失时,先修正动画名或动画库资源引用;后者缺失时,不要只改代码,应先执行编辑器菜单:
text
调试 / 全量资源统计并调试该命令会先执行全量资源统计,生成或刷新 ref/model_animation.json,再根据其中的模型-动画映射生成 Humanoid 烘焙动画,最后启动调试。AI 助手也可以通过 SCE Editor MCP 调用:
json
{ "tool": "debug_start_with_resource_statistics", "arguments": {} }如果只想生成资源和动画而不启动调试,调用:
json
{ "tool": "resource_statistics_generate_animations", "arguments": {} }生成后重点检查:
<ProjectRoot>/ref/model_animation.json是否包含目标模型和dash.ani等动画。<ProjectRoot>/res/anim/<ModelName>/...是否出现对应烘焙结果。- 编辑器客户端日志和信息面板中是否提示“原始 Humanoid 动画不存在”或“原始动画存在但烘焙动画不存在”。
如果自动统计始终无法覆盖某种代码结构,还有两种保险方式:
- 对已经完成骨骼适配的模型,将动画显式加入对应
GameDataModel.HumanoidSourceAnimations,再执行“统计资源并生成”。Maker 外部模型先完成 官方动作重定向,不要把这一步当作首次适配。 - 手动编辑
<ProjectRoot>/ref/model_animation.json,然后只执行编辑器菜单 “工具 → 资源统计和动画重定向 → 生成”。不要再点“统计资源”或“统计资源并生成”,否则编译期分析结果会重新生成并覆盖手工配置。
对代码写法的建议
如果某个资源必须进入发布清单,优先选择“显式可分析”的写法,而不是“运行时动态组装”的写法。
优先:
csharp
var hitEffect = ParticleSystem.Load("effect/effect_new/effect_hit/eff_hit_01/particle.effect");谨慎:
csharp
var hitEffect = ParticleSystem.Load($"effect/{folder}/{effectName}/particle.effect");如果业务上确实必须动态选择资源,至少要额外准备一个可静态分析的资源表、常量清单或显式映射,让所有候选资源都能被编译期看到。
不要把运行时 new GameDataSound 当作资源声明机制。它可以作为已经明确收集过资源后的运行时播放配置,但不应该成为 AI 助手生成音效清单的默认方式。运行时挂载模型、第一人称手臂、枪械视图和动画路径也一样:可以动态选择,但所有候选资源必须额外进入 GameDataModel、GameDataAnimation、HumanoidSourceAnimations 或显式资源清单。轻量 helper 只有在参数字面量直接流入 Sound / IFile 字段,并且 Client-Resource 后确认 ref/objref.txt 出现对应路径时,才可视为发布安全。
推荐给 AI 助手用户的额外约束
如果你经常让 AI 直接生成技能、特效、模型或音频相关代码,建议在项目规则里明确要求:
- 资源路径尽量使用字面量,不要随手字符串拼接。
- 音频优先通过静态
GameDataSound.Asset/GameDataActorSound数据或显式资源清单声明;模型、第一人称手臂、武器视图和动画优先通过GameDataModel/GameDataAnimation/HumanoidSourceAnimations或显式资源清单声明;不要让 AI 只在运行时代码中临时注册音效或拼出资源路径。 - 新增资源引用后,手动执行一次
生成资源统计ref.bat检查ref/objref.txt。 - 发布前遇到实机缺资源,优先检查
objref.txt、model_animation.json和full.ref。
这样可以把“编辑器里没问题、实机才出错”的问题尽量前置到开发阶段。
一句话判断法
如果编辑器里正常、发布后缺资源,先不要只盯着资源库;先问自己两件事:
- 这条资源路径是不是写对了。
- 这条资源路径是不是以分析器能看懂的方式进入了
ref/objref.txt。
只要第二条答案是否定的,真实玩家环境就很可能下载不到它。