Unity asmdef 与编辑器脚本:Player 构建失败的经典原因
AI 摘要
Unity 项目中若某目录缺少 asmdef,其脚本会落入 Assembly CSharp,导致带 UnityEditor 引用的编辑器脚本被打包进 Player。解决方案包括:为 Editor 子目录添加含 includePlatforms: Editor 的 asmdef、用 if UNITY_EDITOR 包裹、或将脚本放入 Editor/ 目录(Unity 内建规则)。推荐三层防护同时生效,兼顾安全性与编译性能。
WOTW3D 里踩过一次:ScriptsGameCore/Level/ 目录下没有 asmdef 文件,于是它归属默认的 Assembly-CSharp。而 Assembly-CSharp 里的脚本在 Player 构建时是会被编译进去的——包括那些 using UnityEditor; 的编辑器脚本。
结果就是开发时一切正常,一打包就报:
error CS0234: The type or namespace name 'UnityEditor' does not exist
in the namespace 'Unity' (are you missing an assembly reference?)
根因
Unity 的编译单元是程序集。没有 asmdef 的脚本全部落进 Assembly-CSharp,而这个程序集在两个“编译平台”下都会被编译:
- Editor 平台:能访问
UnityEditor - Player 平台:不能
编辑器脚本没被隔离,就会被 Player 编译流程抓到。
三种解法
1. 加 asmdef,限定只编译给 Editor。
在 ScriptsGameCore/Level/Editor/ 下放一个 WOTW.Level.Editor.asmdef:
{
"name": "WOTW.Level.Editor",
"references": ["WOTW.Level"],
"includePlatforms": ["Editor"],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
includePlatforms: ["Editor"] 是核心——这个程序集只在编辑器下编译。
2. 用 #if UNITY_EDITOR 包住。
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(LevelDef))]
public sealed class LevelDefEditor : Editor { /* ... */ }
#endif
3. 文件名带 .Editor.cs 后缀或者放进 Editor/ 目录。
Unity 对名为 Editor 的文件夹有内建规则:里面的脚本自动只在编辑器下编译。这是最简单的做法,但只对顶层路径中含 Editor 目录的脚本生效。
我的选择
两者都做:目录隔离 + asmdef 显式声明 + #if UNITY_EDITOR 兜底。
听起来冗余,但这三层的失效模式不同:
Editor/目录规则:有人把脚本移到别处就失效- asmdef:有人新建文件忘了更新引用就失效
#if UNITY_EDITOR:有人复制粘贴时漏了就失效
只用一层的话,总有一天会在赶进度的晚上破掉。三层全上的成本是每条编辑器脚本多两行,我愿意付。
顺带:asmdef 的编译速度收益
加 asmdef 还有个副作用是好的——改代码时的重编译变快了。
Unity 的编译单元是程序集,改一个文件会重编整个程序集。没有 asmdef 时所有脚本挤在 Assembly-CSharp 里,改一行代码要重编全部。拆成 WOTW.Sim / WOTW.Presentation / WOTW.Level 之后,改 Sim 层只重编 Sim 那个程序集。
而且 WOTW.Sim 不依赖 UnityEngine(这是架构硬约束,见确定性模拟那篇),所以它是纯 C# —— 可以单独拿出来给测试工程引用,也可以设成 noEngineReferences: true。
这是一条笔记,不是成稿文章。它可能不完整、可能有错,也可能过段时间就被我推翻。 想看成体系的文章,去 Blog。