scenario-unity-gameplay について
Unity 6.3 のゲームプレイシステム開発向けスキルで、物理、ナビゲーション、AI、弾丸、オーディオ、ネットワークを扱います。状態の書き込み元を一つにし、測定とテストで挙動や性能を検証します。
主な機能
- 01物理と衝突検出の設定
- 02NavMesh ナビゲーションの構築と検証
- 03AI の行動と更新スケジュールの構成
- 04弾丸クエリとプーリングの実装
- 05ゲームオーディオのルーティングと上限制御
- 06拡張性とネットワーク要件の評価
活用シーン
- 01高速弾丸のコライダー貫通をテスト
- 02ドアを通る NPC の経路と状態遷移を確認
- 03多数のエージェントによるフレーム時間を測定
- 04マルチプレイヤーの予測と権限モデルを検討
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-unity-gameplayインストール後、Claude Code で/scenario-unity-gameplayを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/AgentKit/Gameplay/GameplayAudio.csGameplayAudit.csGameplayScenes.csGameplaySetup.csscripts/Runtime/AgentKit.Gameplay.Runtime.asmdefAgentLocomotionSync.csAIBudget.csAIBudgetMeter.csCombatAudio.csEnemyBrain.csEnemyDirector.csFloatingCapsule.csHealth.csJobStallProbe.csJobWaitMeter.csLineOfSightBatch.csMixerControl.csNetSim.csPerception.csPerceptionBenchmark.csProjectile.csProjectilePool.csProximityBenchmark.csQueries.csSoundEmitter.cs詳細
Unity のゲームプレイシステム(ゲームプレイシステムプログラマー)
Unity 6.3 でのエキスパートレベルのゲームプレイ開発では、各システムが状態の唯一の所有者を持ち、誰かが選んだ頻度で実行され、測定によって実証されます。フレームあたりのステップ数、実際の速度でのトンネリングテスト、実際のドアを通る経路クエリ、時間を計測した遷移、実際のエージェント数での最悪フレームなどです。このスキルでは、6.3 のマニュアル、AI Navigation 2、NGO、リリース済みゲーム(Survival Kids、Very Very Valet)から得られるエキスパート向けの知見を扱い、ジョブとテストは 6000.3.21f1 で実行します。ここに記載された関連スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください
(npx skills add scenario-labs/skills --skill <name>)。無人で進める場合は、ツールのスキーマに基づいて作業を続け、不足している情報を明示してください。
必須の事前知識: scenario-unity-expert(チャネル、レビューの反復プロセス、6.3 の落とし穴)。インストール:
ut_gameplay.install(P)(コア AgentKit + AgentKit.Gameplay ジョブ + AgentKit.Gameplay.Runtime
アセンブリ)。
立場(専門家としての差分)
- コライダーより先に固定更新の頻度を調整します。 物理演算のコストは、フレームごとの固定ステップ数に応じて増加します。「呼び出し回数が10近くになる場合は問題の兆候かもしれません」(6.3 Manual)。50 Hzで測定したところ、400 msのフレームではデフォルトのMaximum Allowed Timestepである0.333 sにより、追いつくためのステップが17回実行されました。上限を0.1 sにすると5ステップになり、ワールドの速度は0.25倍になりました。上限を設けると、負荷の雪だるま式の増大を抑えられますが、スローモーションになります。
- 観測されたトンネリングに対してオブジェクトごとにCCDを段階的に適用し、その後は剛体に任せます。 Discrete、Continuous Speculative(「多くの場合、最適な選択肢」)、Continuous、最後にContinuous Dynamicの順です(6.3 Manual)。測定では、どのCCDモードでも静的な厚さ5 cmの壁に対して300 m/sまで防げました。一方、薄い動的なプレートに対しては、Continuousでは50 m/s、Continuous Dynamicでは100 m/sからトンネリングが発生しました。飛翔体自体の形状をスイープする(
SweepProjectile)と、300 m/sの弾を止められました。また、半径0.25 mの球体スイープは、中心を通るレイでは見逃した柱に当たりました。キャスト形状はコライダーに合わせてください(LlamAcademy, fJyi7l2tWKo [00:09:54])。 - 状態の各部分には、書き込み元を1つだけ設けます。 エージェントと非キネマティックなRigidbodyを併用すると競合が起こります。エージェントと有効なObstacleを併用するとエージェントが自分自身を回避します。エージェントとアニメーションの制御は一方向に流す必要があります(AI Navigation manual)。測定では、実行中のTransformAccessArrayジョブが保持するTransformにメインスレッドから書き込むと、ジョブ全体が終わるまで待機しました(ジョブ外では0.003 msなのに対し、22~38 ms)(Survival Kids, ZkvK0mX-id4 [00:26:42])。
- NavMeshはクエリで検証し、アセットとして保存します。 エージェントの種類ごとにNavMesh Surfaceを1つ用意します(SMWxCpLvrcc [00:06:35])。ボクセルサイズはエージェントの半径の3倍、屋内では4~6倍にします(manual)。測定では、エージェントの直径の1.2倍のドアは、半径あたり3ボクセルでは通れず、6ボクセルでは通れました。HeightMeshなしの階段では踏み面から0.17~0.26 mずれましたが、HeightMeshを使うとずれはありませんでした。Carveの結果がクエリに反映されるのは1フレーム後でした。
- 最も軽量なAI構造を選び、1か所から更新します。 振る舞いが1~2個なら構造化せず、遷移が少ないうちはFSMを使います(遷移が増えるとNの2乗に近づきます)。優先順位が必要ならビヘイビアツリー、目標を連鎖させるならGOAPを使います(LlamAcademy, CZvfuNfdc1M [00:07:13])。ブレインはディレクターから更新し、異なる種類のオブジェクトは1つの
UpdateHubに登録して、種類ごとにソートします(Survival Kids:コードは同じで、平均-12%、最悪のフレームは2.18 msから1.43 msに短縮 [00:23:59]。ここで1,000個および10,000個のオブジェクトを測定したところ、平均で7~14%低下)。 - DOTSは包括的な枠組みです。採用する前に測定してください。 更新順序を整え、Transformを小さな読み取り専用ジョブでコピーしながら、NativeArrays上でBurstジョブを実行します。次に、メインスレッドのジョブ待ち時間をBurstの
.Run()で埋めます(測定では、21~26 msのうち6~10 msを削減)。その後で初めてEntitiesを導入します(Survival Kids [00:08:49]、[00:39:15])。CPU主導の大規模な処理数、競技性の高いネットコード、または決定論性が必要な場合は、早い段階でECSを採用するか判断してください(Turbo Makes Games, Bz24Jp30nkM [00:21:58])。 - NGO 2.xには、完全なクライアント側予測とリコンシリエーション(ロールバックと再実行のループ)がなく、サーバー巻き戻しにも対応していません。 クライアント予測(
AnticipatedNetworkVariable、AnticipatedNetworkTransform、OnReanticipate)は、手動で予測を実装するための基盤です(NGO 2.7 manual。同じ説明は同梱の2.13.0ドキュメントにもあります)。デフォルトではサーバーが権限を持ち、IsServerよりHasAuthorityを使います。「後から参加するクライアントに必要か?」を基準に、NetworkVariableとRPCのどちらを使うか判断します。 - すべてのサウンドをミキサーグループ経由にし、距離による処理はソース側で行います。 出力がNone(デフォルト)の場合、ミキサーを経由しません。減衰と距離によるローパス処理は、グループに送られる前にAudioSourceで行われます(manual)。測定では、対数ロールオフによりMax Distanceを超えても減衰が続きました(40 mで1/d)。スナップショットパラメーターのスライダーは、
ClearFloatを呼び出すまでそのグループのムードを固定しました。また、上限なしのループする武器音を60個鳴らすと、デフォルトの優先度では小音量のBGMがかき消されました。頻繁に鳴るサウンドは10~15個に制限し、最も古いものを奪い合いで置き換えてください(git-amend, BgpqoRFCNOs [00:17:56])。
最初に確認すること
一度だけ確認します。ジャンルと対戦プレイかどうか、プラットフォームとフレーム予算(デフォルトはデスクトップで 16.67 ms、モバイルでは x 0.65、scenario-unity-mobile)、ピーク時のエージェント数、弾数、サウンド数、プレイヤーが物理演算で押す側・押される側になるか、アニメーション駆動かエージェント駆動かの移動方式、マルチプレイヤー(プレイヤー数、ホスト離脱、ロールバックまたは被弾巻き戻し)、既存のレイヤー、ミキサー、シーン、2D か 3D か(scenario-unity-2d)。デフォルトは、3D URP、50 Hz、レイヤーは Environment 8、Player 9、Enemy 10、Projectile 11(Projectile x Projectile と Enemy x Enemy はオフ)、Humanoid エージェントタイプ(半径 0.5)、10 Hz のコード FSM 敵、ミキサー 1 つ(Master > Music、SFX、UI)です。
ワークフロー
- プロジェクトの物理設定。
GameplaySetup.ConfigurePhysics(レイヤー、Physics.IgnoreLayerCollision、SerializedObjectを介した Time と Physics の設定)。ゲート:別プロセスでReadPhysicsSettingsを実行し、マトリックス、0.02 s、上限値が表示されること(実測:Time.*セッターとSaveAssetsでは設定が永続化されませんでした)。 - ナビゲーション。
GameplayScenes.BuildNavArenaのパターンに従います。サーフェスのlayerMask= Environment、ボクセル = 半径/3、BuildNavMesh()を実行してから、Inspector の Bake ボタンと同様にAssetDatabase.CreateAsset(surface.navMeshData, ...)を実行し、保存します。エージェントが階段を使う場合は Height Mesh をビルドします。植生には NavMesh Modifier の Remove Object を設定します。収集対象は、範囲を限定するために Volume または Modifier-only にします。ゲート:すべてのドアを通るCalculatePathが PathComplete を返すこと、スポーン地点と踏み面でSamplePositionが機能すること、開き直した後もデータが存在すること、シーンがテキスト形式のままであること、オーバーレイのキャプチャを開けること。 - キャラクターと移動。 物理演算との相互作用を設計に含めない場合は CharacterController(skin >= max(0.01, 半径の 10%)を使い、含める場合は
FloatingCapsuleを使います。AgentLocomotionSyncでエージェント移動を行います。エージェント駆動(ルートモーションをオフにし、deltaPositionから速度を取得)またはアニメーション駆動(updatePosition = false、半径を超えた場合に 0.9 の引き戻し)を選びます。ゲート:cc.*の監査エラーが 0 件であること、ControllerTestsの数値を確認すること、ドリフトテストに合格すること。 - AI。
EnemyBrainとEnemyDirectorを使います。負荷の低い知覚処理から始め、次にオクルーダーマスクを使った 1 回のレイをQueryTriggerInteraction.Ignoreで実行します。規模が大きい場合はLineOfSightBatch(QueryParametersを使った RaycastCommand)でバッチ処理します。死亡時は障害物を有効にする前にエージェントをオフにします。ゲート:EnemyLoopTestsの時間を Patrol > Chase > Attack > Search > Patrol > Dead の順で測定します。 - 弾、クエリ、プール。 剛体の弾は、実際の速度と厚さでトンネリングテストを行い、CCD を確認します。高速または太い弾には
SweepProjectileを使います。Queriesを介して NonAlloc を使います(件数分ループし、バッファーが満杯ならフラグを立て、順序が重要な場合はソートします)。Transform で移動させたコライダーをクエリする前にPhysics.SyncTransforms()を呼び出します。collectionCheckを有効にしたProjectilePoolを使います。ゲート:実際の数値でCcdLadderMeasuredとQueryTestsに合格し、CountActiveが 0 に戻ること。 - オーディオ。
GameplayAudio.CreateCombatMixerまたはテンプレートからミキサーを作成し、すべてのソースをルーティングします。CombatAudioの同時発音数に上限を設定します。スライダーは、どのスナップショットからも駆動されないパラメーターに接続します。音楽の優先度は 0 にし、AudioLowPassFilterのカーブで距離に応じたこもりを設定します。ゲート:AudioTestsとAudioSpatialTests(スパム再生中の音楽帯域)に合格し、audio.*のエラーが 0 件であること。上限を 30、15、5 として負荷をかけながら人間が試聴し、十分な音の厚みがある最小値を選びます(BgpqoRFCNOs [00:17:23])。 - スケール。 実際の数で
BuildCrowdSceneを実行し、AgentProfile.PlayModeTimings、AIBudgetMeter、JobWaitMeterを確認します。DOTS の導入を提案する前に、PerceptionBenchmark、ProximityBenchmark、dots_gate、proximity_gateを実行します。ゲート:p95 と最悪フレームの両方を確認します。エディターでの数値を、Profile Analyzer とネイティブデバイスプロファイラーを使って開発用プレイヤーで検証します(Survival Kids [00:10:27]; scenario-unity-performance)。 - ネットコードの適用範囲(マルチプレイヤーのみ)。
ut_gameplay.netcode_fit、オーソリティとティックのシートを確認し、NGO を接続する前に Edit Mode でNetSimを使って予測を検証します。ゲート:リコンシリエーションのテストがすべて成功し、手作業で構築する必要があるものが設計に明記されていること。
数値
| 値 | 相対項目 | 出典 |
|---|---|---|
| 0.02 s(50 Hz);60 fps のフレームごとに 1 ステップなら 1/60 | 固定タイムステップ | マニュアル;pTz3LMQpvfA [00:01:22] |
| デフォルトで 0.333 s(50 Hz で約 16 ステップ);0.1 s = 5 ステップ | 最大許容タイムステップ | 実測 |
| 1 フレームあたり約 10 回の呼び出しで問題 | Physics.Processing 呼び出し | マニュアル |
| 約(厚さ + 2r + 0.02)/ dt を超えるとディスクリート方式ですり抜ける | 50 Hz | discrete_tunnel_speed;実測値 20 m/s、壁厚 5 cm |
| スキン >= max(0.01, 半径 x 0.1);ステップ 0.1~0.4 m;最小移動量 0 | 2 m の CharacterController | マニュアル |
| たわみ = m x g / k;反転時の加速度は 2 倍 | 浮遊カプセル | 実測;Toyful [00:05:32] |
| 半径あたり 3 ボクセル;内部は 4~6;8 を超えてもほとんど効果なし;ドアはエージェント直径の約 1.5 倍以上 | エージェントの半径 | マニュアル;実測 |
| ドリフトがエージェントの半径を超えた場合、ドリフトを delta の 0.9 倍引き戻す(実測では r 0.4 で最大 0.41~0.45 m、補正なしでは 1.84 m) | アニメーション駆動エージェント | AI Navigation マニュアル |
| Carve は次のフレームに反映;Carve Only Stationary は 0.5 s 静止後に再カービング(実測で 0.50~0.61 s) | NavMesh Obstacle | マニュアル;実測 |
| AI の処理時間は 0.10~0.15 ms(Update)対 約 0.01 ms(10 Hz ディレクター) | 200 エージェント、Editor | 実測 |
| 200 エージェントでは Burst 並列処理はマネージド処理より遅く、2,000 からは高速 | 知覚の事前フィルタリング | 実測 |
| 総当たりはアイテムとクエリの組み合わせあたりマネージドで 7~8 ns、Burst で 1.2~1.5 ns;グリッドは 10,000 x 200 で 0.3~1.3 ms 対 14~15 ms | 近接クエリ | 実測(Survival Kids の KD tree:1,000 アイテムに対する 20 クエリで 5 ms から 0.08 ms) |
| 200 ms で認識される;FPS は 100 ms 未満;30 Hz ティック;更新レート > 発射レート | ネットコード | NGO マニュアル |
| 実ボイス 32 / 仮想ボイス 512;優先度 0~256、デフォルト 128 | オーディオ設定 | 実測;マニュアル |
| Max Distance を超えるとゲイン = min/d(対数);Max Distance で 0(線形) | 3D ロールオフ | 実測 |
| 低域通過のカットオフ曲線は 22000~10 Hz、Audio Low Pass Filter が必要 | 距離によるこもり | マニュアル;実測では 8 m で 8 kHz:0.03x |
| スライダー 0.0001..1 -> 20 log10(v);頻繁に鳴るサウンドは 10~15 インスタンス | ミキサー、ボイス | DU7cgVsU2rM [00:14:30];BgpqoRFCNOs [00:17:56] |
品質ゲート
- 測定可能:
compile_errors == [];新しいプロセスで設定を読み戻せること;AuditGameplaySceneでエラーが 0 件(ネガティブコントロールですべての仕込まれたトラップを検出すること);GAMEPLAY_METRICSを解析したうえでテストスイートがすべて成功すること;実際の速度での CCD およびスイープテスト;プールがベースラインに戻ること;実際の件数での群衆 CSV に p95、最悪フレーム、AI スライスが含まれること;code_auditのエラーが 0 件であること。 - 視覚: NavMesh オーバーレイと群衆キャプチャが
ut_review.image_checksに合格し、実際に開いて確認されていること; 概要で操作感が求められている場合は、フレームからコントローラーと移動の操作感を評価すること。
よくある間違い
| 間違い | 発生する問題 | 修正 |
|---|---|---|
| 高速な剛体すべてで常時 Dynamic を使う | CPU 負荷が増え、動的な板をすり抜ける | まずは Speculative を使い、必要なら SweepProjectile を使用します |
| 太い弾丸に細いレイを使う | 半径が触れているものを検出できない | コライダーの形状をスイープします(Queries.SweepShape) |
| バッファーを使って NonAlloc ループを行う | 古いヒットが残る、ヒットが通知なく欠落する、順序が不定になる | 件数分だけループし、件数 == バッファー長の場合はフラグを立て、ソートします |
Physics.autoSyncTransforms = true | クエリの前に毎回、隠れた同期が発生し、CS0618 が出る | クエリの前に Physics.SyncTransforms() を1回呼び出します |
エディタージョブで Time.maximumDeltaTime = x を設定する | 次回起動時に 0.333 に戻る | TimeManager.asset に対して SerializedObject を使用します |
BuildNavMesh() の後にシーンを保存する | データが埋め込まれ、シーンファイルがバイナリになる | 先に AssetDatabase.CreateAsset(surface.navMeshData) を呼び出します |
| 既存のシーンに上書き保存した後でクエリを実行する | NavMesh が空になり、パスが無効になる | GameplayScenes.EnsureLoaded を使用します |
| HeightMesh のない階段 | エージェントがランプを滑るように上る | Height Mesh をビルドして再ベイクします |
| 障害物が切り抜かれるフレームで経路を再計算する | 新しい障害物を通る経路になる | 次のフレームでクエリを実行します |
| エージェントとアニメーションの両方がキャラクターを動かす | 足滑りやジッターが発生する | AgentLocomotionSync を使い、どちらか一方で動かします |
| ドメインリロードを無効にした状態で静的シングルトンを使う | 前回セッションのハブが残り、更新が二重に実行される | SubsystemRegistration でリセットし、SafeUnregister を使用します |
FindObjectsByType<Transform> から TransformAccessArray を作成する | シーンに応じてフレームコストが増加する | 独自のリストを管理し、変更時に再構築します |
| ジョブの実行中に TAA のトランスフォームを書き込む | メインスレッドがジョブの完了を待って停止する | 読み取り専用としてコピーし、Complete() の後に書き込みます |
| 大規模な環境で「あらゆる近くのもの」をトリガーで検出する | 移動するオブジェクトごとにトランスフォーム同期が発生する | 独自のリストと Burst を使い、その後グリッドを使います(proximity_gate) |
| 別の名前のファイルに MonoBehaviour を記述する | シーンの保存後に通知なく存在しなくなる | 1ファイルにつき MonoBehaviour を1つにします |
戦闘音に PlayClipAtPoint を使う | スライダーが機能しない | グループにルーティングするプール済みエミッターを使用します |
| SFX グループに「距離」効果としてローパスをかける | すべてのサウンドが同じようにこもる | ソースごとに AudioLowPassFilter のカーブを設定します |
| Logarithmic ソースを無音にするために Max Distance を信頼する | 遠くでも音が聞こえる(1/d) | Linear またはカスタムカーブを使うか、距離でカリングします |
| 計画に「NGO にはクライアント予測がある」と記載する | 約束したループが存在しない | anticipation と手作業で構築した reconciliation を組み合わせるか、Netcode for Entities を使用します |
引き継ぎ
- 受け取るもの: scenario-unity-architecture からコード構造、サービス、Input System アクション、scenario-unity-world-building から Environment レイヤー上のレベル、scenario-unity-animation からアニメーション付きキャラクター、scenario-unity-performance からフレーム予算を受け取ります。
- 引き渡すもの: locomotion の速度、状態パラメーター、選択した結合方式を scenario-unity-animation に、ヒットイベントを scenario-unity-vfx に、ヘルスとスライダー用のフックを scenario-unity-ui(
MixerControl.SetUserVolume)に、群衆 CSV、AI スライス、ジョブの待機を scenario-unity-performance に、2D 物理を scenario-unity-2d に、オーディオの制限を scenario-unity-mobile と scenario-unity-web に引き渡します。パケット:シーン、ジョブ ID、監査 JSON、テスト指標、CSV 判定、Verified、Assumed。
Unity 6.3 に関する注意事項
- 3D 物理:
linearVelocity、linearDamping、PhysicsMaterial、Physics.simulationMode。Physics.autoSyncTransformsは非推奨です(単にオフにするのではありません)。RaycastCommandはQueryParametersを受け取ります。Physics.BakeMesh(int...)は旧式です。 - AI Navigation 2.0.14:NavMesh Surface ごとにベイクします(Navigation ウィンドウには Bake はありません)。OffMesh Link は非推奨です。
- Entities 1.4.8(コアは 6.4 から);6.5 では
Entities.ForEachが削除されます。6.6 では Burst が組み込みになり、新規プロジェクトでは Play 時のドメインリロードがスキップされます。 - NGO 2.13.0 がバンドルされ、1.x は非推奨です。
[Rpc(SendTo.Server)]を使用します。NetworkTransform.UpdateではなくOnUpdateをオーバーライドします。Multiplayer Services は Lobby と Relay に取って代わります。
参考資料
references/expert-notes.md:情報源ごとの原則、タイムスタンプ、測定値。references/procedures.md:ライブテストと結果を含むコードとしての手順(G1 から G16)。references/critique.md:自己レビューの評価基準。references/gui-paths.md:同じ手順に対応するウィンドウとメニュー。references/sources.md:情報源、認証情報、タイムスタンプ、改訂履歴。scripts/ut_gameplay.py:インストール、数値、ゲート(DOTS、近接性、ネットコード、AI 構造)、ロールオフモデル、指標、コード監査。scripts/AgentKit/Gameplay/:エディタージョブ。scripts/Runtime/:ブレイン、ディレクター、ハブ、クエリ、スイープ、結合、Burst ジョブ、メーター、プール、オーディオ、コントローラー、ネットコードモデル。