mcpField
S
scenario-unity-architecture

scenario-unity-architecture

github.com/scenario-labs开发1.2k257 次安装1 天前更新
代码仓库

关于 scenario-unity-architecture

面向 Unity 游戏玩法程序员和架构师的技能,着重确保架构在多次运行、对象销毁、存档、重构和合并后仍可靠。它为 Unity 6.3 提供经过测试的项目布局、脚本、任务和验证门槛。

主要功能

  1. 01可测试的 Core、Runtime、Editor 和 Tests 布局
  2. 02ScriptableObject 定义与纯 C# 状态
  3. 03带迁移检查的版本化 JSON 存档
  4. 04安全处理异步生命周期与静态状态
  5. 05输入重绑定和序列化数据审计

适用场景

  1. 01搭建 Unity 游戏玩法项目
  2. 02测试玩家重启后的存档持久性
  3. 03防止对象销毁后异步任务残留
  4. 04验证输入重绑定和资源重构

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-unity-architecture

安装后在 Claude Code 中输入/scenario-unity-architecture确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/AgentKit/Architecture/
ArchInputAudit.cs
ArchJobs.cs
ArchPlayMode.cs
scripts/templates/Core/
Game.Core.asmdef
HealthModel.cs
HealthStats.cs
Inventory.cs
SaveData.cs
scripts/templates/Editor/
EventChannelEditor.cs
Game.Editor.asmdef
ItemDatabaseValidator.cs
scripts/templates/Runtime/
Game.Runtime.asmdef
scripts/templates/Runtime/Async/
AwaitableExtensions.cs
Lifetime.cs
Regeneration.cs
scripts/templates/Runtime/Composition/
GameRoot.cs
scripts/templates/Runtime/Events/
EventChannel.cs
FloatEventChannel.cs
StringEventChannel.cs
VoidEventChannel.cs
VoidEventListener.cs
scripts/templates/Runtime/Health/
Health.cs
HealthConfig.cs
scripts/templates/Runtime/Input/
BindingStore.cs
InputContext.cs

详细介绍

Unity 架构(玩法程序员 / 架构师)

Unity 架构的专业水准,要看它能否经受住各种边界情况:第二次 Play 会话、await 期间对象被销毁、已构建的玩家程序重新启动、类重命名、两个分支合并,以及将预制件拖入空场景。数据存放在资源中,状态存放在纯 C# 和文件中,系统通过彼此不拥有的事件交互,而且每项断言都有一个 Agent 无需鼠标即可运行的测试来验证。目标环境:Unity 6000.3.21f1、Input System 1.20.0、Test Framework 1.6.0、macOS。如果此处提到的同级技能不在可用技能中,请让用户安装(npx skills add scenario-labs/skills --skill <name>);若无人值守,则根据工具架构继续操作,并标明这一缺漏。

必备背景: scenario-unity-expert(通道、审查循环、6.3 陷阱)。工具包: ut_env、ut_run、ut_live,以及此技能的 scripts/ut_arch.py、 scripts/AgentKit/Architecture/ 任务和 scripts/templates/(经过测试的 Game.Core / Game.Runtime / Game.Editor / tests 布局)。

状态(2026-09-24): 所有流程均已在这台 Mac 上的 Unity 6000.3.21f1 中运行: tests/code/unity-architecture/run_all.sh --live(结果见 archive/tests/unity-architecture/live_results.jsonl)。

立场(专业能力的差异)

  1. ScriptableObject 是定义,绝不是存档。 在 Play 模式下对 SO 的写入会保留在 Editor 中,并在每次启动构建版本时重置(Code Monkey,5a-ztc5gcFw [00:04:17])。观察到的情况 更为明确:写入只存在于 Editor 内存中(IsDirty 为 false,即使调用 SaveAssets,.asset 也不会改变),因此文件哈希检测不到;只有播放时进程中的内存防护 能检测到(so_guard,prefab SO 测试)。macOS 玩家版本连续运行两次,都是从烘焙值启动, 而存档 FILE 会保留。运行时修饰符作用于结构体副本(config.Stats, Tarodev,tE1qH8OxO2Y [00:11:18]);实时注册表是运行时集合,绝不搜索场景(Hipple, raQ3iHhE_Kk [00:40:06])。存档:带版本号的 id JSON、原子写入、备份、迁移。
  2. 在信任任何 Play 测试前,先了解 Enter Play Mode。 关闭域重载后(6.3 中的一个选项, 新建 6.6 项目的默认设置),静态计数器在第二次 Play 时读到 2,静态事件也有 2 个订阅者 (观察所得)。在 ExitingPlayMode 时注销静态处理程序(Manual 的最佳实践;单独使用时 观察到运行正常),并在 SubsystemRegistration 时重置值;DoublePlay 证明了这两点。 关闭场景重载会使 Editor 启动情况不具代表性,而脚本变更仍会重载域(6.3 Manual)。
  3. 每个 await 都必须有所有者。 await 会在 Destroy 和退出 Play 后继续执行(Unity, eXkFPoEp3BA [00:13:28]):观察到没有令牌的循环在 Destroy 后 0.3 秒内又执行了 13 到 14 次。MonoBehaviour 传入 destroyCancellationToken(IO 也必须在退出时停止时使用 Lifetime.Linked(this));纯 C# 服务传入 Application.exitCancellationToken。绝不要 对同一个 Awaitable await 两次(池化对象:AsTask()),也绝不要阻塞等待 Task。发布版玩家 会跳过主线程检查:开发版玩家在非主线程调用 persistentDataPath、Time.time、 transform 和 new GameObject 时会抛出异常,发布版玩家则不会(观察所得)。在线程相关 代码上进行冒烟测试时,使用开发构建版本。
  4. 设计师负责连线,代码负责决策。 序列化的 SO 引用是设计师可见的注入方式 (Hipple [00:14:39])。通道用于解耦发送方和接收方,反向遍历监听器 ([00:33:23]),并配套提供调试方案:计数、日志开关、从作业调用 RaiseDebug()、Inspector 按钮([00:36:07])。UnityEvent 是序列化的函数调用([00:29:26]):仅用于少数由设计师连线 的响应(在 6.3 中测得每次调用为 0 B,但目标是按方法名称解析的硬引用)。由一个组合根 最先运行([DefaultExecutionOrder(-1000)],git-amend, PJcBJ60C970 [00:12:01];没有它时,观察到使用方找不到任何内容),并在失败时明确报错。 每个 prefab 都能在空场景中单独运行([00:16:29])。静态变量只适合小型个人项目 (Weimann,hQE8lQk9ikE [00:01:28];Tarodev [00:03:00]),并须重置。枚举仅用于按“某种 规则”固定的集合;可由设计师扩展的类别应使用 SO 资源([00:46:39])。
  5. 重绑定是一个有六个陷阱的状态机。 必须禁用操作(否则该包会抛出异常);默认情况下, Escape 不会取消 Button 重绑定;复合绑定的头部不能重绑定;重复项根据 effectivePath 判断,复合绑定部分只与之前的部分比较;修饰键组合由 shortcutKeysConsumeInput 控制 (默认关闭:Shift+B 也会触发 B);Reset All 会删除已持久化的覆盖项;除非传入 false, 否则 LoadBindingOverridesFromJson 会清除现有覆盖项(samyam,csqVa2Vimao [00:12:51]、[00:27:44]、[00:33:22];1.20 手册;均已测试)。 UI 应从 Rebinding UI sample 开始:1.20 版已包含禁用和释放操作,但还缺少四项修复 (ut_arch.rebind_audit)。每次 new 一个生成的输入类都会创建独立资源:应共享同一个。
  6. 程序集是一份契约。 不引用引擎的 Core 中包含可在毫秒级测试的规则;运行时 asmdef 绝不 引用 Editor 程序集(Editor 可以编译,但玩家构建会失败,观察所得)。测试 asmdef 引用 Unity.InputSystem.TestFramework;在 6000.3.21f1 中不需要 testables(观察所得)。
  7. 序列化数据会跨重构保留,合并则只在有意为之时进行。 字段使用 [FormerlySerializedAs],重命名的 [SerializeReference] 类使用 [MovedFrom](不使用时, 条目会加载为 null,观察所得);每个 [SerializeReference] 类型都要添加 [Serializable] (6.3 中缺少它也能运行;根据版本说明,6.4 要求添加)。即使使用 -l/-r,UnityYAMLMerge 在任何字段发生冲突时也会以状态码 2 退出;未使用这些参数时则会写入 BASE 值(观察所得): 检查退出码,然后在 Unity 中打开结果。

首先确定

团队构成(由设计师还是程序员编写行为逻辑)、项目生命周期、目标平台(Web 没有托管线程)、Enter Play Mode 设置、输入设备(键盘和鼠标、手柄、触控、 本地多人游戏)和修饰键快捷键、存档需求(存档槽、云端、迁移)、VCS(默认使用 Git + LFS)。默认设置:项目范围的操作、Game.Core/Runtime/Editor/Tests asmdef、SO 定义 + 纯 C# 状态、系统间使用事件通道、使用运行时集合管理注册表、一个 GameRoot、在 persistentDataPath 下保存 JSON 存档、使用带 UnityYAMLMerge 驱动程序的 Git。运行 ArchJobs.ProjectFacts,并将其答案写入计划。

工作流

  1. 搭建框架并编译。 目标:使用纯 C# 实现规则。方法:ut_arch.install(P)、 ut_arch.scaffold(P)(A1),然后运行 ArchJobs.ProjectFacts (Unity -batchmode -nographics -quit -executeMethod AgentKit.Architecture.ArchJobs.ProjectFacts)。 GATE:编译错误和警告为零;ArchJobs.AssemblyGraph:Game.Core 的 engine_refs == 0, violations == []。
  2. 将数据作为资源。 目标:定义可由设计师编辑。方法:使用 ut_arch.item_specs() 运行 ArchJobs.CreateAssets (编辑 SerializedObject、为 [SerializeReference] 列表使用 managed_refs,仅在发生更改时调用 SetDirty)(A2、A13)。GATE:第二次运行时全部为 unchanged; ItemDatabaseValidator 检查通过。
  3. 构建带测试的核心系统。 目标:物品栏、生命值、事件、集合。方法:使用模板; ut_run.run_tests(P, "EditMode", assemblies="Game.Tests.EditMode")。GATE:全部通过; ut_arch.code_lint 对运行时代码无警告或错误(A10)。
  4. 保存和加载。 目标:重启后状态仍然保留。方法:使用 SaveService(A3)。GATE:EditMode 往返测试、损坏文件回退、v1 迁移;在两个编辑器进程之间运行 CrossProcessSave; 为持久性声明运行两次玩家程序(A4);当存档涉及线程时运行开发版玩家程序(A12)。
  5. 输入。 目标:支持所有设备,重绑定设置可持久保存并可重置。方法:项目范围的操作、 InputContext、RebindFlow、BindingStore,将 .inputactions 作为 JSON 编辑; ArchInputAudit.Audit(A5、A15)。GATE:使用 InputTestFixture 针对每个陷阱编写测试;审计结果中没有 missing_scheme 或 duplicate_binding;rebind_audit 没有缺失项。
  6. 异步和生命周期。 目标:不遗留后台工作。方法:使用带令牌的 async Awaitable Start()、 AsTask、PlayMode 套件;异步测试使用返回内部 Awaitable 的 [UnityTest],或使用 async Task(A6、A7、A14)。GATE:销毁后不再有 tick;Prefab 隔离和 Prefab SO 防护检查通过。
  7. 强化 Play 模式。 目标:第 2 次会话与第 1 次会话一致,事件能触发响应。方法: ArchJobs.StaticStateAudit,然后在项目模式下使用 so_guard 和 invoke (对每个通道调用 RaiseDebug)运行 ArchPlayMode.DoublePlay(A8、A11)。GATE:自有程序集没有 arch.static_without_reset;监视值在各会话间相等; so_changed_in_memory == [];每次 invoke 均为 ok,且其响应已计数。
  8. 版本控制。 目标:合并和移动资源时引用不会损坏。方法: ArchJobs.SetVcsSettings、ut_arch.write_vcs_files、git_setup_unity_merge、meta_audit、 yaml_merge,然后运行 ArchJobs.InspectAsset(A9)。GATE:meta_audit.ok;.meta 不在 LFS 中;干净合并以 0 退出且能反序列化; 冲突保持未合并状态并附带报告。
  9. 交接并提供证据(采用 scenario-unity-expert 报告格式):每个步骤的 Unity 调用、Verified、Assumed。

数字

数值相对于来源
11 到 13 秒此项目的一次批处理作业(包括启动编辑器)观测值
约 11 秒 / 16 秒EditMode 套件(33 个测试)/ PlayMode 套件(17 个测试),最终运行观测值
13 到 14 个 tickDestroy 后 0.3 秒内,不带令牌的 0.02 秒 Awaitable 循环观测值
2 / 2关闭域重新加载时,Play 会话 2 中的静态计数器 / 静态事件订阅者观测值
4 次(共 6 次)/ 0 次(共 6 次)在开发版 / 发布版 macOS 播放器中,从主线程外调用 Unity 时引发异常的次数观测值
0 B预热后 10,000 次通道触发或持久化 UnityEvent 调用的堆增长量观测值
约 37,000 到 61,000 fps批处理 -nographics Play 模式(不限帧率):按时间等待,而非按帧等待观测值
0.05 秒;falsePerformInteractiveRebinding OnMatchWaitForAnother;shortcutKeysConsumeInput 默认值软件包源码 1.20;观测值
下一个 Update(30 fps 时为 33 ms)在主线程上启动的 Task 延续6.3 手册
约 500 KB超过此二进制文件大小后,Git LFS 才有收益Pettersen 2016
退出码 0 / 2UnityYAMLMerge 无冲突 / 存在任何冲突字段(使用 -l、-r 时也一样)观测值
114.7 MB / 311.2 MB探测场景的 macOS Mono 发布版 / 开发版播放器观测值

质量门槛

  • 可度量: 编译错误和警告均为零;AssemblyGraph.violations == [];完整套件中的 EditMode 和 PlayMode failed == 0,而非仅单个测试;运行时代码中的 code_lint 无警告或错误;so_guard 和 prefab SO 测试均通过(SnapshotAssets 的 changed_on_disk == [] 只能证明编辑器工具没有保存任何内容); StaticStateAudit 通过;DoublePlay 会话数相同;ArchInputAudit 警告数为 0;rebind_audit 完整;meta_audit.ok;检查合并退出码;若声称支持持久化或线程功能,则提供播放器重启报告(A4)和 开发版播放器报告(A12)。
  • 视觉(少量): 场景合并后,提供合并前后的相机截图(scenario-unity-expert AgentCapture);重绑定界面显示新的绑定文本(scenario-unity-ui)。

常见错误

错误表现修复
将存档保存在 SO 中在 Editor 中正常,构建版本重新启动后丢失使用 SaveService 文件;SO 只读(A3、A4)
通过文件哈希保护 SOPlay 写入 SO 时仍会通过在运行中的进程里使用 so_guard / prefab SO 测试(A11)
将会话状态保存在未引用的 SO 中加载场景后,构建版本中的值恢复为烘焙值保持引用,或将其保存在 C# 中
关闭域重载时使用静态变量第二次 Play 从 2 开始,处理程序运行两次在 ExitingPlayMode 时注销,并在 SubsystemRegistration 时重置
不带令牌的 await对象销毁或 Play 结束后,逻辑仍会运行使用 destroyCancellationToken;服务使用 Lifetime.App
对 Awaitable 等待两次异常或卡住仅调用一次 AsTask()
仅在发布版本中检查线程现在正常,之后行为未定义在开发版播放器中进行冒烟测试(A12)
正向遍历监听器自行移除的响应会跳过下一个监听器反向遍历
组合根没有执行顺序消费方的 Awake 找不到服务[DefaultExecutionOrder(-1000)]
重新绑定已启用的操作InvalidOperationExceptionRebindFlow 会在完成和取消时禁用并重新启用
Button 重新绑定时未使用 WithCancelingThroughEscape 变成绑定键.WithCancelingThrough("<Keyboard>/escape")
Reset All 保留文件;加载时未分层旧的重新绑定设置恢复;较早的覆盖项消失BindingStore.ResetAll;使用 Load(..., removeExisting: false)
在测试中或搭配 PlayerInput 使用 InputSystem.actionsfixture 测试后变为 null;配对失败EditorBuildSettings.TryGetConfigObject(...);playerInput.actions
每个消费方都生成一个输入类重新绑定只在一个屏幕上显示使用一个共享实例
运行时 asmdef 引用 editor asmdefEditor 正常,播放器构建失败使用 AssemblyGraph 检查显式引用
在 [SerializeReference] 背后重命名类条目加载为 null在同一项更改中使用 [MovedFrom]
信任合并输出冲突字段悄然恢复为基础值检查退出码 2,解决冲突,使用 InspectAsset

交接

  • 接收自 scenario-unity-expert:简报、渠道、项目事实。接收自 scenario-unity-gameplay:哪些系统需要生命值、伤害、状态机(FSM 模式位于那里)。
  • 交付给 scenario-unity-ui:RebindFlow、BindingStore、InputContext、渠道(带有 RaiseDebug),供 HUD 和设置界面使用,并附带测试。交付给 scenario-unity-gameplay:IDamageable、 Health、Services、GameRoot、运行时集合。交付给 scenario-unity-pipeline-automation:asmdef 布局、run_tests(..., assemblies=...)、开发版本构建、.gitignore/.gitattributes、合并 驱动程序。交付给 scenario-unity-performance:每帧 GC(scenario-unity-expert AgentProfile)、 每个对象的 Awaitable 循环。交付给 scenario-unity-web:不使用托管线程(SaveAsync 保持 同步,不使用 Task.Delay)。交付给 scenario-unity-mobile:触控方案、persistentDataPath。 交接包:写入的文件、含总数的测试、含计数的审计、已验证项和假设项。

Unity 6.3 说明

  • Enter Play Mode 是一个下拉选项("When entering Play Mode");6.6 的新项目默认设置为 "Reload Scene only";6.5 新增了代码生命周期属性和 AutoStaticsCleanup。
  • FindObjectsByType<T>(FindObjectsSortMode.None)(排序模式从 6.4 起已弃用); [SerializeField] 仅用于字段;GetInstanceID 在 6.5 中会成为编译错误(EntityId); 从 6.4 起,[SerializeReference] 类型必须标记为 [Serializable](版本说明中有记录,但未经验证)。
  • 模板仅运行 Input System(旧版 Input.* 会抛出异常);捆绑 Input System 1.20; OnMouseDown 从 6.4 起才可与其配合使用。
  • Object.Destroy:已在 6000.3.21f1 上验证(LifecycleTests,2026-09-24),OnDisable 会触发到每个后代对象(共四层);6.4 升级指南和版本说明称 6.4 之前的编辑器只处理 “直接子对象”,因此旧版编辑器仍应保留幂等的 OnDestroy 注销逻辑。
  • Force Text 和 Visible Meta Files 是 6.3 的默认设置(已观察到);6.6 会关闭 YAML 自动换行,并移除 "Reduce Version Control Noise"。

参考资料

评论

支持 Markdown