核心概念
工作表是唯一真实来源
你的数据只有一种权威形式:工作表。其他一切都是由它派生出来的:
- IR(不可变的 Definitions)是工作表经过验证、组装后的形式。
- 生成的 C# 类是 IR 的架构,以强类型形式呈现。
- 烘焙出的 ScriptableObject 是 IR 的取值,以可加载的形式呈现——它是一个查找缓存,绝不是独立的真实来源。
所有修改都必须经过工作表,并通过重新导入验证才能生效。直接编辑烘焙出的 SO 会制造出第二个"真实来源"并绕过验证——产品有意不将其作为一种工作流来支持。
(面板中的"测试编辑"开关是为临时的运行时实验而存在的。它永远不会被写回,并且会在重新导入时被清除。)
为什么这很重要:把 SO 当作真实来源的项目,最终会出现未经验证、与工作表逐渐脱节的数据,且无法调和二者。而在这里,调和是结构性的——始终从工作表重新生成即可。
IR——一次不可变的、经过验证的组装
IR 是验证的产出。对每个标签页而言,它持有一个 SheetTable(架构 + 记录),其单元格已经是带类型的值:int、float、enum 值、记录引用、资源引用、列表、自定义插件类型。
关键特性:
- 不允许部分组装。 只要任何地方存在一个错误,IR 就不会被构建(
ImportResult.Success == false ⇔ Registry == null——一个硬性不变量)。 - 没有 null。 空的可选单元格会立即具化为其类型的默认值,并标记为
IsDefaulted——使用方永远不需要做 null 检查。 - 不可变。 IR 在组装完成后就是只读的;各个出口(代码生成、烘焙、导出)只会读取它,绝不会修改它。
管线
fetch → parse markers/schema → parse cells → validate (keys, references,
@overlap, asset keys, domain rules) → assemble IR → codegen (.cs) → bake (SO)
└──────────────── collect ALL diagnostics ────────────────┘- 验证会收集所有信息。 你会在一次运行中得到完整的问题清单——每个错误都包含在哪里/是什么/为什么/怎么办——而不是每次重新导入只修一个错误。
- 代码生成是最后一个阶段,发生在验证和取值组装之后,因为写入
.cs文件会触发一次域重载。管线的结构保证了这次重载是安全的,并且链条会在重载之后自动继续。 - 错误是结构化对象,以语句形式呈现。 每个错误都携带标签页、从 1 开始计数的行号,以及列字母和字段名。它还携带出问题的值、被违反的规则,以及一条可执行的建议(对拼写错误还会给出近似匹配提议)。同样的对象也会以机器可读坐标的形式呈现,供日志/CI 使用。
自动导入链
当架构是新建或已更改时,一次导入运行内部会做:
- 写入生成的代码 → Unity 编译 → 域重载。
- 重载完成后,链条会自行继续并完成烘焙。
你永远不需要手动重新触发任何东西。如果编译失败(例如你的游戏代码引用了某个刚被重命名改动过的字段),链条会安全中止,并在控制台给出一条可执行的提示语句,而不会陷入循环(尝试次数上限为 3 次,并有继续记录)。
强类型,无运行时解析
代码生成会读取 @name / @type / @desc,并针对每个标签页 Foo 生成:
FooDefinition——一个强类型的记录类,每列对应一个字段;@desc会变成 XML 文档注释和面板中的工具提示。FooDatabase : DefinitionDatabase——该标签页专属的容器 SO,带有Records、惰性 id 查找,以及SchemaFingerprint。
烘焙写入的是真实的带类型字段——零运行时文本解析,零运行时反射,这使它对 IL2CPP 是安全的(没有代码裁剪风险)。
按地址加载——缓存如何保持可共享
烘焙出的 SO 是按机器区分的缓存,带有按机器区分的 GUID。直接的场景引用会在跨机器时失效。取而代之的做法是:
- 导入会将每个 Database SO 自动注册到 Addressables 组
SheetForge下的稳定地址"SheetForge/{tab}"(重新烘焙会把新的 GUID 重新链接到同一个地址;被删除的标签页会被清理掉)。 - 游戏代码按地址加载:
SheetForgeDatabases.LoadAsync<FooDatabase>("Foo")。 - Addressables 组资源已被 Git 忽略,并且可以自我修复(缺失时由导入重新创建)。
Baselines——往返如何保留你的工作表
在导入时,每个标签页结构(标记行、列顺序、注释、人工书写的文本)的一份规范化快照会被存为 baseline。导出时,会把当前 SO 的取值换入这份 baseline 的结构中。
因此"工作表 → 导入 → 导出 → 工作表"这样一次往返会 100% 保留你工作表的结构,并在语义上保留取值:
- 允许
1.0↔1互换,因为其值相同。 - 浮点数使用最短的可往返格式。
- 小数点始终用
.,与语言环境无关。
哪些会被提交,哪些会被重新生成
| 产物 | 策略 |
|---|---|
| 工作表(本地文件 / Google表格) | 真实来源。 需要提交/共享。 |
烘焙出的 Database SO(Assets/SheetForgeBaked) | 已被 Git 忽略的按机器区分缓存——运行一次导入即可重新生成。 |
生成代码(Assets/SheetForgeGenerated) | 推荐将其提交。 它是你项目自己的源码,位于 Assets/SheetForge 之外,因此重新安装本产品不会删除它;提交它意味着新克隆的仓库在任何人运行导入之前就能编译。其输出是确定性的,因此队友的导入会产生完全相同的字节。改用 gitignore 忽略它同样是有效的替代方案,下一次导入会重新生成它。早于这个默认值就存在的项目会持续生成到 Assets/SheetForge/Runtime/Generated,直到该文件夹被清空为止——参见快速上手。 |
Addressables 的 SheetForge 组资源 | 已被 Git 忽略,可自我修复。不要提交它首次创建时产生的那一行设置差异。 |
领域包自己的 Generated 文件夹 | 由该包自行决定。 内置的 SheetForge.PluginDemo 示例会提交其生成代码,这样示例在导入后能立即编译。 |
| 导入设置资源 | 由你自行管理;请让服务账号密钥的路径留在仓库之外(使用 SHEETFORGE_SHEETS_KEY 环境变量)。 |
无需修改即可扩展
注册契约让插件能够以零 Core 改动的方式加入管线:
- 单元格类型解析器(包括包装类型)、领域验证器、边贡献者;
- 自定义结构标记、"创建工作表"模板、导入来源提供方;
- Data Studio 的画布覆盖、代码注册表、部件、操作、单元格部件、色彩预设和 UI 字符串。
Core 永远不会引用某个领域包;这种单向依赖由编译器强制保证。权威列表——及其确切数量——见插件开发。
相关页面
- 表格语法——解析器所读取的标记与类型语法
- Data Studio——构建在此模型之上的创作层
- 数据源、导出与推送——往返机制
- 创作内核——创作窗口底层的引擎