mcpField
S
scenario-unity-architecture

scenario-unity-architecture

github.com/scenario-labs開発1.2k257 回インストール1 日前に更新
リポジトリ

scenario-unity-architecture について

ゲームプレイプログラマーやアーキテクト向けの Unity スキルで、複数の Play セッション、オブジェクト破棄、セーブ、リファクタリング、マージに耐える設計を扱います。Unity 6.3 向けに、検証済みの構成、スクリプト、ジョブ、品質ゲートを備えています。

主な機能

  1. 01テスト可能な Core・Runtime・Editor・Tests 構成
  2. 02ScriptableObject の定義とプレーンな C# 状態
  3. 03移行チェック付きバージョン管理 JSON セーブ
  4. 04非同期ライフサイクルと静的状態の管理
  5. 05入力リバインドとシリアライズデータの監査

活用シーン

  1. 01Unity ゲームプレイプロジェクトの土台作成
  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 の高度なアーキテクチャは、境界条件を超えて機能し続けるかどうかで評価されます。2 回目の Play セッション、await 中のオブジェクトの破棄、ビルド済みプレイヤーの再起動、クラス名の変更、2 つのブランチのマージ、空のシーンへのプレハブの配置などです。データはアセットに、状態はプレーンな C# とファイルに保持し、システム間では所有していないイベントを介して通信します。また、すべての主張は、エージェントがマウスなしで実行できるテストによって裏付けます。対象: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. ScriptableObjects は定義であり、セーブデータではありません。 プレイモード中に SO に書き込んだ値は Editor に保持され、ビルドを起動するたびにリセットされます(Code Monkey、5a-ztc5gcFw [00:04:17])。さらに詳しく観察したところ、書き込みは Editor のメモリ内にのみ存在し(IsDirty は false、SaveAssets の後も .asset は変更されません)、ファイルハッシュでは検出できません。そのため、検出できるのはプレイ中のプロセス内にあるメモリ上のガードだけです(so_guard、プレハブ SO テスト)。macOS のプレイヤーを 2 回実行すると、セーブ 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 の新規プロジェクトではデフォルト)、2 回目の Play で static カウンターが 2 を示し、static イベントには 2 つのサブスクライバーが登録されていました(観察済み)。ExitingPlayMode で static ハンドラーの登録を解除してください(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 を 2 回 await してはいけません(プールされるため、AsTask() を使います)。Task をブロックしてはいけません。リリースプレイヤーではメインスレッドチェックが省略されます。開発プレイヤーではメインスレッド以外から persistentDataPath、Time.time、transform、new GameObject にアクセスすると例外が発生しましたが、リリースプレイヤーでは何も発生しませんでした(観察済み)。開発ビルドでスレッド処理をスモークテストしてください。
  4. デザイナーが配線し、コードが判断します。 シリアライズされた SO 参照は、デザイナーが確認できるインジェクターです(Hipple [00:14:39])。チャンネルで送信元と受信先を疎結合にし、リスナーを後ろから反復します([00:33:23])。デバッグ計画も用意します。件数、ログの切り替え、ジョブからの RaiseDebug()、インスペクターボタンです([00:36:07])。UnityEvent はシリアライズされた関数呼び出しです([00:29:26])。デザイナーが配線する応答にのみ、まれに使用してください(6.3 で呼び出しごとに 0 B と測定されましたが、対象はメソッド名で解決されるハード参照です)。コンポジションルートは 1 つだけを最初に実行し([DefaultExecutionOrder(-1000)]、git-amend、PJcBJ60C970 [00:12:01]。これがないとコンシューマーが何も見つけられませんでした)、問題があれば明確に失敗させます。各プレハブは空のシーンで単独実行できるようにします([00:16:29])。static は小規模な個人プロジェクトでのみ適しています(Weimann、hQE8lQk9ikE [00:01:28]、Tarodev [00:03:00])。その場合もリセット処理を用意します。列挙型を使うのは「何らかの規則によって」固定されているセットのみです。デザイナーが拡張するカテゴリには SO アセットを使います([00:46:39])。
  5. リバインドは 6 つの落とし穴があるステートマシンです。 アクションを無効にする必要があります(そうしないとパッケージが例外をスローします)。デフォルトでは Escape を押しても Button のリバインドはキャンセルされません。コンポジットの先頭はリバインドできません。重複は effectivePath で判定され、コンポジットのパーツはそれより前のパーツとのみ比較されます。修飾キーの組み合わせは shortcutKeysConsumeInput に従います(デフォルトではオフのため、Shift+B を押すと B も発火します)。Reset All は永続化されたオーバーライドを削除します。LoadBindingOverridesFromJson は false を渡さない限り、既存のオーバーライドを消去します(samyam、csqVa2Vimao [00:12:51]、[00:27:44]、[00:33:22]。1.20 のマニュアル。すべてテスト済み)。UI には Rebinding UI サンプルをベースにしてください。1.20 では無効化と破棄はすでに実装されていますが、4 つの修正が不足しています(ut_arch.rebind_audit)。生成された入力クラスを new するたびに別のアセットが作られるため、1 つを共有してください。
  6. アセンブリは契約です。 noEngineReferences を設定した Core には、ミリ秒単位でテストできるルールを格納します。ランタイムの asmdef から Editor の asmdef を参照してはいけません(Editor ではコンパイルできますが、プレイヤービルドは失敗します。観察済み)。テスト用 asmdef からは Unity.InputSystem.TestFramework を参照します。6000.3.21f1 では testables は不要でした(観察済み)。
  7. シリアライズされたデータはリファクタリングを越えて保持され、意図した場合にのみマージされます。 フィールドには [FormerlySerializedAs]、名前を変更した [SerializeReference] クラスには [MovedFrom] を使います(これがないとエントリーは null として読み込まれました。観察済み)。すべての [SerializeReference] 型に [Serializable] を付けます(6.3 ではなくても許容されましたが、バージョンノートによると 6.4 では必須です)。UnityYAMLMerge は、-l/-r を指定しても、競合したフィールドがあると終了コード 2 を返します。また、これらを指定しない場合は BASE の値を書き込みます(観察済み)。終了コードを確認してから、Unity で結果を開いてください。

最初に確定すること

チームの構成(動作をつなぐのはデザイナーかプログラマーか)、プロジェクトの期間、対象プラットフォーム(Web には管理対象スレッドがありません)、Enter Play Mode の設定、入力デバイス(キーボードとマウス、ゲームパッド、タッチ、ローカルマルチプレイヤー)と修飾キーショートカット、セーブの要件(スロット、クラウド、移行)、VCS(デフォルトは Git + LFS)です。デフォルト設定:プロジェクト全体のアクション、Game.Core/Runtime/Editor/Tests の asmdef、SO 定義 + プレーンな C# 状態、システム間のイベントチャネル、レジストリ用ランタイムセット、GameRoot は 1 つ、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:2 回目の実行ではすべて 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 の移行を確認すること。2 つのエディタープロセス間で CrossProcessSave を実行すること。永続化に関する主張を確認するため、プレイヤーを 2 回実行すること(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:Destroy 後にティックが発生しないこと。プレハブの分離とプレハブ SO ガードを通過すること。
  7. プレイモードを堅牢化します。 目標:セッション 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 sこのプロジェクトのバッチジョブ 1 回(エディターの起動を含む)実測
約 11 s/16 sEditMode スイート(33 テスト)/PlayMode スイート(17 テスト)、最終実行実測
13~14 ティックDestroy 後 0.3 s の間の、トークンなしの 0.02 s 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 s;falsePerformInteractiveRebinding の OnMatchWaitForAnother;shortcutKeysConsumeInput のデフォルト値パッケージソース 1.20;実測
次の Update(30 fps で 33 ms)メインスレッドで開始された Task の継続処理6.3 Manual
約 500 KBGit LFS の導入に見合うバイナリサイズPettersen 2016
exit 0/2UnityYAMLMerge の競合なし/競合するフィールドあり(-l、-r を指定した場合も同様)実測
114.7 MB/311.2 MBプローブシーンの macOS Mono リリース用/開発用プレイヤー実測

品質ゲート

  • 測定可能な項目: コンパイルエラーと警告がゼロであること;AssemblyGraph.violations == [] であること;単体テストだけでなく、完全なスイートで EditMode と PlayMode の failed == 0 であること;ランタイムコードで code_lint に警告もエラーもないこと;so_guard とプレハブ 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)
ファイルハッシュで SO をガードするPlay 中に SO を書き込んでも検出されません再生中のプロセスで so_guard/プレハブ SO テストを行います(A11)
参照されていない SO にセッション状態を格納するシーンのロード後、ビルドでは値がベイク時の状態に戻ります参照を維持するか、C# で保持します
ドメインリロード無効時の static2 回目の Play が 2 から始まり、ハンドラーが 2 回実行されますExitingPlayMode で登録解除し、SubsystemRegistration でリセットします
トークンなしで await するオブジェクトの破棄後や Play の終了後にもロジックが実行されますdestroyCancellationToken、サービスには Lifetime.App を使います
Awaitable を 2 回 await する例外またはハングが発生しますAsTask() を 1 回だけ使います
リリースビルドだけでスレッドをチェックする今は問題なくても、後で動作が未定義になります開発用プレーヤーでスモークテストを行います(A12)
リスナーを順方向に反復する自身を削除する応答が次のリスナーをスキップします逆方向に反復します
実行順序が設定されていないコンポジションルートコンシューマーの Awake でサービスが見つかりません[DefaultExecutionOrder(-1000)]
有効なアクションを再バインドするInvalidOperationException が発生しますRebindFlow が無効化し、完了時とキャンセル時に再度有効化します
WithCancelingThrough なしで Button を再バインドするEscape がバインドになります.WithCancelingThrough("<Keyboard>/escape")
Reset All でファイルが残り、読み込みがレイヤー化されない古い再バインドが戻り、以前のオーバーライドが消えますBindingStore.ResetAll、Load(..., removeExisting: false) を使います
テスト中または PlayerInput 使用時に InputSystem.actions を使うフィクスチャーテスト後に null になり、ペアリングが壊れますEditorBuildSettings.TryGetConfigObject(...)、playerInput.actions を使います
コンシューマーごとに生成済み入力クラスを用意する再バインドが 1 つの画面にしか反映されません1 つの共有インスタンスを使います
ランタイム asmdef がエディター asmdef を参照するEditor では問題ありませんが、プレーヤービルドに失敗しますAssemblyGraph で明示的な参照チェックを行います
[SerializeReference] の背後にあるクラス名を変更するエントリーが null として読み込まれます同じ変更内で [MovedFrom] を指定します
マージ出力をそのまま信頼する競合したフィールドが警告なくベースの値に戻ります終了コード 2 を確認し、解決してから InspectAsset を実行します

引き継ぎ

  • 受け取るもの:scenario-unity-expert から、概要、チャンネル、プロジェクトの事実。scenario-unity-gameplay から、どのシステムにヘルス、ダメージ、ステートマシンが必要か(FSM パターンはそこにあります)。
  • 引き渡すもの:scenario-unity-ui には、HUD と設定画面用の RebindFlow、BindingStore、InputContext、チャンネル(RaiseDebug を含む)とテストを渡します。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 を渡します。パケットには、書き込んだファイル、合計値付きのテスト、件数付きの監査、Verified と Assumed を含めます。

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)。[SerializeReference] の型は 6.4 から [Serializable] である必要があります(バージョンノートには記載されていますが、そこでの検証は未実施です)。
  • テンプレートでは Input System のみが実行されます(レガシーの Input.* は例外をスローします)。Input System 1.20 がバンドルされています。OnMouseDown がこれと連携するのは 6.4 からです。
  • Object.Destroy:6000.3.21f1 で実動作を検証した結果(LifecycleTests、2026-09-24)、OnDisable はすべての子孫(4 階層)に到達します。6.4 アップグレードガイドとバージョンノートでは、6.4 より前のエディターは「直接の子のみ」と説明されているため、古いエディター向けには冪等な OnDestroy の登録解除を維持してください。
  • Force Text と Visible Meta Files は 6.3 の既定値です(観測済み)。6.6 では YAML のワードラップがオフになり、「Reduce Version Control Noise」が削除されます。

参考資料

  • references/procedures.md:A1 から A15 までの完全なコード、実動作テスト、記録された結果。
  • references/expert-notes.md:エキスパート別の原則、タイムスタンプ、判断表、すべての観測結果。
  • references/critique.md:自己レビューの評価基準。
  • references/gui-paths.md:同じタスクに使用するメニューとウィンドウ。
  • references/sources.md:ソース、認証情報、URL、最適なタイムスタンプ、改訂履歴。
  • scripts/ut_arch.py、scripts/AgentKit/Architecture/*.cs、scripts/templates/:テスト済みのコード。

コメント