Hero background image

ScriptableObject でゲームデータとロジックを分離

このウェブページは、お客様の便宜のために機械翻訳されたものです。翻訳されたコンテンツの正確性や信頼性は保証いたしかねます。翻訳されたコンテンツの正確性について疑問をお持ちの場合は、ウェブページの公式な英語版をご覧ください。

このページでは、ゲームコード内のロジックからデータを分離するデータコンテナとして ScriptableObject を使用する方法について説明します。

これは、eBook「Create modular game architecture in Unity with ScriptableObjects」に付属するデモで、Unity開発者を支援するために作成された6つのミニガイドシリーズの第2弾です。

このデモは、古典的なボールとパドルを使ったアーケードゲームのメカニクスに着想を得たもので、ScriptableObject がテスト可能でスケーラブル、かつデザイナーにとって使いやすいコンポーネントの作成にどのように役立つかを示しています。

この e ブック、デモプロジェクト、およびこれらのミニガイドは、Unity プロジェクトで ScriptableObject クラスを使用してプログラミングデザインパターンを使用するためのベストプラクティスを提供します。これらのヒントは、コードを簡素化し、メモリ使用量を削減し、コードの再利用性を促進するのに役立ちます。

このシリーズには、次の記事が含まれています。

始める前の重要事項

ScriptableObject デモプロジェクトとこのミニガイドシリーズを掘り下げる前に、その中核をなすデザインパターンは単なるアイデアに過ぎないことを覚えておいてください。すべての状況に当てはまるわけではありません。これらのテクニックは、Unity と ScriptableObject の新しい使い方を学ぶのに役立ちます。

それぞれのパターンには長所と短所があります。特定のプロジェクトにとって意味のあるものだけを選びましょう。デザイナーは Unity エディターに大きく依存していますか?ScriptableObject ベースのパターンは、開発者と共同作業するのに良い選択肢です。

最終的には、プロジェクトやチームに合ったコードアーキテクチャがベストです。

tab2

データコンテナ

ソフトウェア開発者は、多くの場合、アプリケーションを小さな自己完結型のユニットに分割するモジュール性に懸念を持っています。各モジュールは、アプリケーションの機能の特定の側面を担います。

Unity では、ScriptableObject はロジックからデータを分離するのに役立ちます。

ScriptableObject は、特に静的なデータを格納するのに適しています。これにより、ゲームの統計、アイテムや NPC の設定値、キャラクターの会話などに最適です。

ゲームプレイデータをビヘイビアロジックから分離することで、プロジェクトの独立した各パーツのテストとメンテナンスが容易になります。この「懸念の分離」により、必要な変更を加える際に、意図しない副作用や望ましくない副作用を減らすことができます。

tab3

一般的なワークフロー

ScriptableObject ワークフローの復習をご希望の場合は、こちらの Unity Learn 記事が役立ちます。そうでない場合は、以下の簡単な説明をご覧ください。

ScriptableObject を定義します。作成するには、格納するデータのフィールドとプロパティを持つ ScriptableObject 基本クラスを継承する C# クラスを定義します。ScriptableObject は、MonoBehaviour で利用可能な同じデータ型を格納できるため、汎用性の高いデータコンテナとなります。 エディターから CreateAssetMenuAttribute を追加して、プロジェクトでのアセットの作成を容易にします。

アセットの作成:ScriptableObject クラスを定義したら、その ScriptableObject のインスタンスをプロジェクト内に作成できます。これはディスクに保存されたアセットとして表示され、異なるゲームオブジェクトやシーンで再利用できます。

設定値:アセットを作成したら、Inspector でフィールドとプロパティの値を設定することで、アセットにデータを入力します。

アセットの使用:アセットがデータを保持したら、変数またはフィールドからデータを参照します。ScriptableObject アセットに加えられた変更は、プロジェクト全体に反映されます。

ScriptableObject は、ゲームのさまざまな部分でデータコンテナとして再利用できます。例えば、ScriptableObject 内で武器やキャラクターのプロパティを定義し、プロジェクト内のどこからでもそのアセットを参照できます。

注:また、実行時に CreateInstance メソッドを使用して ScriptableObject を生成することもできます。ただし、データストレージについては、通常は CreateAssetMenuAttribute を使用して事前に ScriptableObject アセットを作成します。

tab4

ScriptableObject と MonoBehaviour の比較

ScriptableObject が MonoBehaviour よりもデータストレージに適している理由をよりよく理解するために、それぞれの空のバージョンを比較します。「Asset Serialization」を「Mode」に設定します。「Project Settings」で「Force Text」を選択すると、YAML マークアップがテキストとして表示されます。

それ以外の場合は空の MonoBehaviour で新しいゲームオブジェクトを作成します。次に、空の ScriptableObject アセットと比較します。これらを横に並べると、上の画像のような見た目になります。

ScriptableObject は MonoBehaviour と比較して軽量で、Transform コンポーネントのような MonoBehaviour に伴うオーバーヘッドを持ちません。これにより、ScriptableObject のメモリフットプリントが小さくなり、データストレージ用に最適化されます。

tab5

Patterns デモ

ScriptableObject はアセットとして保存されるため、再生モード以外でも保持されるので便利です。例えば、ScriptableObject のデータは、新しいシーンをロードした場合でもどこからでも利用できます。

Patterns デモの例には、自分でテストできる基本的なクレジット画面があります。Credits_Data ScriptableObject を変更し、Update を押して、保存されているテキストを確認します。

大量のダイアログを含む RPG や、あらかじめ用意されたスクリプトを含むチュートリアルシーンがある場合、これは大量のデータを保存する一般的な方法です。

ScriptableObject 内のデータは変更されると即座に更新されますが、私たちのプロジェクトでは、手動で画面を更新するには Update ボタンが必要です。UI Toolkit ベースの画面は 1 回のみビルドされ、データが変更されたときに通知を受ける必要があります。

更新を自動的に同期したい場合は、ScriptableObject 内にイベントを作成します。たとえば、この ExampleSO スクリプトは、ExampleValue が変更されるたびに OnValueChanged イベントを呼び出します。以下のコード例をご覧ください。

次に、リッスンしている UI オブジェクトに OnValueChanged をサブスクライブさせ、適宜更新します。

tab6

フライウェイトパターンによるゲームデータの保存

ScriptableObject は、多くのオブジェクトが同じデータを共有するときに輝きます。例えば、同じ攻撃速度と最大ライフを持つユニットが多数あるストラテジーゲームを制作している場合、それらの値をすべてのゲームオブジェクトに個別に保存するのは非効率的です。

代わりに、共有データを 1 か所に統合し、各オブジェクトがその共有場所を参照するようにできます。ソフトウェア設計では、これはフライウェイトパターンと呼ばれる最適化です。このようにコードを再構築することで、大量の値をコピーすることがなくなり、メモリフットプリントを削減できます。

PaddleBallSO では、GameDataSO ScriptableObject は共有データストレージとして機能します。

Paddle スクリプトと Ball スクリプトは、一般的な設定(速度、質量、物理演算の跳ね返りなど)のコピーを別に保持するのではなく、可能な限り同じ GameDataSO インスタンスを参照します。各ゲーム要素は位置や入力イベントなどの一意のデータを保持しますが、可能な場合はデフォルトで共有データに設定されます。

オブジェクトが 2 つか 3 つあるだけでメモリの節約は目立たないかもしれませんが、共有データを編集する方が、手動で編集するよりも速く、エラーも少なくなります。

例えば、パドルの速度を変更する必要がある場合、1 か所で調整すると、すべてのシーンで両方のパドルが更新されます。MonoBehaviour に一意のフィールドとして格納している場合、1 回のクリックで 2 つの値が簡単に同期しなくなります。

ScriptableObject にデータをオフロードすることで、バージョン管理にも役立ち、チームメイトが同じシーンやプレハブで作業する際のマージ競合を防ぐことができます。

tab7

PaddleBallSO ゲームデータ

GameDataSO は、ScriptableObject をデータコンテナとして使用する方法を示しています。PaddleBallSO には、ゲームプレイを構成するさまざまな設定が含まれています。

  • パドルデータ:パドルスピード、ドラッグ、質量などの属性によって、ゲームプレイ中のパドルの動きと物理演算が決まります。
  • ボールデータ:これは、ボールの現在の速度、最大速度、バウンス乗数を保持し、シミュレーションと相互作用するときのボールの動作を制御します。
  • マッチデータ:GameDataSO には、試合中のポイント間の遅延に関する情報が含まれており、ゲームのペースを制御するのに役立ちます。
  • プレイヤー ID:PlayerIDSO ScriptableObject は、各プレイヤーのチーム ID として機能します(例:Player1 と Player2)。
  • プレイヤースプライト:これらのオプションのスプライトにより、プレイヤーのアバターをカスタマイズできます。
  • レベルレイアウト:LevelLayoutSO オブジェクトは、プレイヤーの開始位置とゴールや壁などのゲーム要素を定義します。

これらの設定とデータがすべて 1 か所にあるため、GameDataSO ではあらゆるオブジェクトがこの共有データにアクセスできます。これにより、これらのオブジェクトの管理方法が簡素化され、プロジェクト全体で一貫性が高まります。パドルの物理演算を変更しますか?複数のスクリプトを調整する代わりに、ここで 1 つの変更を加えます。

tab10

デュアルシリアライズの例:レベルレイアウト

時には、ケーキを食べて食べることもできます。デュアルシリアライズでは、データを ScriptableObject に格納しつつ、別の形式で維持することができます。

LevelLayoutSO スクリプトはこの概念を示しています。パドルとボールの開始位置を保持するだけでなく、壁とゴールのトランスフォームデータをカスタム構造体に格納します。

これらの値は、ExportToJson メソッドを介してディスクに書き込むことができます。JSON ファイルは人間が読めるテキストであり、Unity の外部で直接修正できます。これにより、エディターで ScriptableObject を操作し、JSON ファイルや XML ファイルなどの別の場所にデータを保存できます。

JSON や XML のようなファイル形式は、エディターでの作業が難しい場合がありますが、Unity の外部ではテキストエディターで簡単に変更できます。これにより、カスタムまたはユーザーが修正したレベルの可能性が広がります。

その後、GameSetup スクリプトは LevelLayout ScriptableObject または外部 JSON ファイルを使用してゲームレベルを生成できます。

カスタム修正したレベルを読み込むために、セットアップスクリプトは CreateInstance を使用してランタイムに ScriptableObject を生成します。次に、JSON ファイルからテキストを読み取り、ScriptableObject にデータを入力します。

カスタムデータによって ScriptableObject の内容が置き換えられ、この外部で変更されたレベルを他のレベルと同じように使用できるようになります。アプリケーションの他の部分は、スイッチを意識することなく正常に機能します。

tab11

ScriptableObject データコンテナのその他の用途

パドルボールを使ったミニゲームでは、ScriptableObject データコンテナのすべてのユースケースを実演することはできませんが、ご自身のアプリケーションでは、以下のことを考慮してください。

  • ゲーム設定:定数やゲームルールなど、ゲームプレイ中に変更する必要のない設定パラメーターを考えましょう。これにより、他のコンポーネントはハードコードされた値を使用せずにこの設定データを参照できます。
  • キャラクターと敵の属性:ScriptableObject を使用して、体力、攻撃力、速度などの属性を定義します。これにより、デザイナーは開発者なしでゲームプレイ要素のバランスを調整できます。
  • インベントリおよびアイテムシステム:項目の定義や名前、説明、アイコンなどのプロパティは、ScriptableObject に最適です。また、インベントリ管理システムの一部として使用して、プレイヤーが収集、使用、装備するアイテムを追跡することもできます。
  • ダイアログおよびナラティブシステム:ScriptableObject には、ダイアログテキスト、キャラクター名、分岐するダイアログパス、その他のナラティブ関連のデータを保存できます。複雑な対話システムの基礎を築くことができます。
  • レベルと進捗データ:ScriptableObject を使用して、レベルのレイアウト、敵のスポーンポイント、目標、その他のレベル関連情報を定義できます。
  • オーディオクリップ:PaddleBallSO プロジェクトに示すように、ScriptableObject には 1 つ以上のオーディオクリップを保存できます。これらは、ゲームの複数の部分にわたってオーディオエフェクトや音楽を定義できます。
  • アニメーションクリップ:ScriptableObject はアニメーションクリップの保存に使用でき、複数のゲームオブジェクトやキャラクターで共有される一般的なアニメーションを定義するのに便利です。

ScriptableObject を深く掘り下げ、独自のプロジェクトに合わせて調整することで、さらに多くの応用例が見つかります。特にデータ管理に役立ち、さまざまなゲーム要素にわたって一貫性を維持しやすくなります。

スクリプタブルアウトロ

その他の ScriptableObject リソース

ScriptableObject によるデザインパターンの詳細については、e ブック「Create modular game architecture in Unity with ScriptableObjects」を参照してください。また、「ゲームプログラミングパターンによるコードのレベルアップ」では、Unity 開発の一般的なデザインパターンについて詳しく知ることができます。