
このページから得られるもの:ゲームコードを簡単に変更し、デバッグできるように、スクリプタブルオブジェクトを使って設計するためのヒント。
これらのヒントは、ライアン・ヒップル、シェル・ゲームズの主任エンジニアからのもので、スクリプタブルオブジェクトを使用してゲームを設計する上級者向けの経験があります。ライアンのスクリプタブルオブジェクトに関するユナイトトークはこちらで視聴できます。また、スクリプタブルオブジェクトの素晴らしい入門セッションを見ていただくことをお勧めします。
スクリプタブルオブジェクトは、スクリプトインスタンスとは独立して、大量の共有データを保存できるシリアライズ可能なUnityクラスです。ScriptableObject を使用すると、変更やデバッグの管理が容易になります。ゲーム内の異なるシステム間で柔軟なコミュニケーションを構築できるため、制作中にそれらを変更・適応しやすく、コンポーネントを再利用することができます。
モジュール設計を使用する:
パーツを簡単に変更・編集できるようにする:
デバッグを簡単にする:
これは最初の二つのサブピラーです。ゲームをモジュール化するほど、ひとつひとつのモジュールを検証しやすくなります。ゲームがより編集しやすければ(つまり、独自のインスペクタービューを備えている機能が多ければ)、デバッグが楽になります。Inspectorでデバッグ状態を表示できることを確認し、デバッグ方法の計画がない限り、機能が完了したと考えないでください。
ScriptableObjectsを使って構築できる最もシンプルなものの一つは、自己完結型のアセットベースの変数です。以下はFloatVariableの例ですが、これは他のシリアライズ可能な型にも拡張されます。
チームの誰もが、どれだけ技術的であっても、新しいFloatVariableアセットを作成することで新しいゲーム変数を定義できます。任意のMonoBehaviourまたはScriptableObjectは、この新しい共有値を参照するためにpublic floatの代わりにpublic FloatVariableを使用できます。
さらに良いことに、あるMonoBehaviourがFloatVariableの値を変更すると、他のMonoBehaviourもその変更を見ることができます。これにより、互いに参照を必要としないシステム間のメッセージングレイヤーが作成されます。

これの使用例は、プレイヤーのヒットポイント(HP)です。ローカルプレイヤーが 1 人のゲームで、PlayerHP という名前の FloatVariable をプレイヤーの HP として設定できます。プレイヤーがダメージを受けるとPlayerHPから減算され、プレイヤーが回復するとPlayerHPに加算されます。
シーン内に健康バーのPrefabを想像してみてください。HP バーは PlayerHP 変数を監視し、その表示を更新します。コードを変えることなく、簡単に別の変数(PlayerMP 変数など)を監視するよう設定することも可能です。健康バーはシーン内のプレイヤーについて何も知りません。ただ、プレイヤーが書き込むのと同じ変数から読み取るだけです。
このように設定されると、PlayerHPを監視するためにさらに多くのものを追加するのが簡単です。音楽システムは、PlayerHPが低下すると変化し、敵はプレイヤーが弱いと知ると攻撃パターンを変えることができ、画面空間効果は次の攻撃の危険性を強調することができます。ここで鍵となるのは、Player スクリプトはこれらのシステムにはメッセージを送信していないということで、言うなればこれらのシステムはプレイヤーゲームオブジェクトを認識している必要がない、ということになります。ゲームが実行中のときにInspectorに入ってPlayerHPの値を変更してテストすることもできます。
FloatVariableの値を編集する際には、ScriptableObjectに保存されている値を変更しないために、データをランタイム値にコピーするのが良いアイデアかもしれません。これを行うと、MonoBehaviourはディスクに保存されているInitialValueを編集しないようにRuntimeValueにアクセスする必要があります。
Ryanのお気に入りの機能の1つは、ScriptableObjectの上に構築するイベントシステムです。イベントアーキテクチャを使うと、互いを直接認識しないシステム間でメッセージを送信させることで、コードをモジュール化しやすくなります。これにより、更新ループで常に監視することなく、状態の変化に応じて反応することができます。
以下のコード例は、GameEvent ScriptableObjectとGameEventListener MonoBehaviourの2つの部分からなるイベントシステムに由来しています。デザイナーは、重要な送信可能メッセージを表す GameEvent をプロジェクト内にいくつでも作成できます。GameEventListenerは特定のGameEventが発生するのを待ち、UnityEventを呼び出すことで応答します(これは真のイベントではなく、シリアライズされた関数呼び出しのようなものです)。
GameEvent ScriptableObject:
GameEventListener:

これの例は、ゲームにおけるプレイヤーの死亡を処理することです。この部分には実行内容の大幅な変更が伴う可能性がありますが、すべてのロジックをコーディングする場所を決めるのが難しい場合があります。Player スクリプトでゲームオーバーの UI をトリガーして音楽を変更すべき?プレイヤーがまだ生きているかどうかを敵に毎フレームチェックさせたほうが良いか?イベントシステムは、このような問題のある依存関係を回避することを可能にします。
プレイヤーが死亡すると、PlayerスクリプトはOnPlayerDiedイベントでRaiseを呼び出します。Player スクリプトは単純なブロードキャストであるため、どのシステムが Player スクリプトを待ち受けているかは認識しません。Game Over UI は OnPlayerDied イベントをリッスンし、アニメーション化を開始します。カメラスクリプトは OnPlayerDied をリッスンして黒へのフェードを開始し、音楽システムは音楽変更のレスポンスを返します。各敵もOnPlayerDiedをリスニングし、挑発アニメーションをトリガーしたり、アイドル動作に戻るための状態変更を行うことができます。
このパターンにより、プレイヤーの死亡に対する新しい反応を追加するのが非常に簡単になります。さらに、InspectorのボタンやテストコードからイベントでRaiseを呼び出すことで、プレイヤーの死亡に対する反応をテストするのが簡単です。
Schell Gamesで構築されたイベントシステムは、はるかに複雑なものに成長し、データを渡したり、自動生成された型を許可する機能を持っています。この例は、彼らが今日使用しているものの出発点でした。
Scriptable Objectsは単なるデータである必要はありません。MonoBehaviour に実装しているシステムを例にとり、その実装を ScriptableObject に移すことができるか検討してみてください。DontDestroyOnLoad MonoBehaviourにInventoryManagerを持つ代わりに、ScriptableObjectに置いてみてください。
シーンに結びついていないため、Transformはなく、Update関数も取得しませんが、特別な初期化なしでシーンの読み込み間で状態を維持します。インベントリシステムのオブジェクトへのアクセスにスクリプトが必要な場合は、シングルトンの代わりに、public な参照を使用してください。これにより、シングルトンを使用している場合よりも、テストインベントリやチュートリアルインベントリを簡単にスワップできます。
ここでは、プレイヤースクリプトがインベントリシステムへの参照を取得することを想像できます。プレイヤーがスポーンされるタイミングで、Player スクリプトで所有しているすべてのオブジェクトを Inventory に要求し、装備をスポーンできます。装備UIもインベントリを参照し、アイテムをループして描画するものを決定できます。