Hero image

ScriptableObjectベースのランタイムセットの使い方

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

開始前の重要な注意

ScriptableObjectのデモ・プロジェクトやこの一連のミニ・ガイドに飛び込む前に、デザイン・パターンの核心は単なるアイデアに過ぎないということを覚えておいてほしい。すべての状況に当てはまるわけではない。これらのテクニックは、UnityとScriptableObjectの新しい使い方を学ぶのに役立ちます。

それぞれのパターンには長所と短所がある。特定のプロジェクトに有益なものだけを選ぶ。デザイナーはUnity Editorを多用していますか?ScriptableObjectベースのパターンは、開発者とのコラボレーションを助ける良い選択かもしれない。

結局のところ、最良のコード・アーキテクチャとは、あなたのプロジェクトとチームに合ったものなのだ。

ランタイムセット

ScriptableObjectは静的データを格納できる。動的なデータを保持するように変更することもできる。これは、特に実行時にGameObjectやコンポーネントのコレクションを参照するのに便利である。

このような "ランタイム・セット "は、開発者がシングルトンを使う理由を再現している。残念ながら、シングルトンには望ましくない依存関係といったお荷物がつきまとう。こうした余分な依存関係はテストを複雑にし、デバッグを難しくする。

別の方法として、頻繁に参照されるコンポーネントにはScriptableObjectベースのランタイム・セットを使用することを検討する。例えば、スポーンした敵、収集物、チェックポイントなどのゲーム要素を追跡するために使用します。

プロジェクト レベルのアセットとして、ScriptableObject は、シーン内のすべてのオブジェクトのデータへのグローバル アクセスを提供します。この機能により、シングルトンに伴う多くの欠点なしに情報を共有することができる。

基本的な実装

GameObjectsのランタイム・セットを作る一つの方法を紹介しよう。これは、要素のパブリック・コレクションを保持するデータ・コンテナのようなものだと考えてほしい。

実行時に、どのMonoBehaviourもパブリックItemsプロパティを参照し、GameObjectの全リストを取得することができる。別のスクリプトやコンポーネントがAddRemove を呼び出してItemsリストを変更することができます。

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "GameObject Runtime Set", fileName
= "GORuntimeSet")]
public class GameObjectRuntimeSetSO: ScriptableObject
{

   private List<GameObject> items = new List<GameObject>();
   public List<GameObject> Items => items;

   public void Add(GameObject thingToAdd)
   {
       if (!items.Contains(thingToAdd))
            items.Add(thingToAdd);
   }

   public void Remove(GameObject thingToRemove)
   {
       if (items.Contains(thingToRemove))
            items.Remove(thingToRemove);
   } 
}

ジェネリック版

特定のタイプのMonoBehaviourだけを追跡するように、ランタイム・セットをより具体的にすることができます。その場合は、タイプごとに特定のセットを作成する(EnemyRuntimeSet、PickupRuntimeSetなど)。

一般的な抽象クラスは、このプロセスを合理化することができる。そして、この基本クラスから他のランタイム・セットを派生させることができる。例えば、カスタムEnemyコンポーネントのランタイム・セットを作りたい場合、以下のコード・スニペットのように、ジェネリックRuntimeSetSO<T>と具象EnemyRuntimeSetSOのクラスを定義することができる。

具象クラスであるEnemyRuntimeSetSOは、CreateAssetMenu属性だけで、実装の詳細を持っていないことに注意してください。

追跡したい新しいコンポーネントには、それを管理するための対応するランタイム・セットが必要なだけだ。具体的なクラスを必要なだけ作る。敵、NPC、インベントリアイテム、クエストなど、すべて独自のランタイムセットを持つことができる。

public abstract class RuntimeSetSO<T>: ScriptableObject
{
   public List<T> Items = new List<T>();

   public void Add(T thing)
   {
       if (!Items.Contains(thing))
            Items.Add(thing);
   }

   public void Remove(T thing)
   {
       if (Items.Contains(thing))
            Items.Remove(thing);
   } 
}

[CreateAssetMenu(menuName = "Enemy Runtime Set", fileName =
"EnemyRuntimeSet")]

public class EnemyRuntimeSetSO: RuntimeSet<Enemy>
{

}
ミスマッチエラー

使用法と制限

ランタイム・セットの管理はあなた次第だ。多くの場合、2つ目のMonoBehaviourを使って、ゲームプレイ中に設定したり更新したりすることができる。

コンポーネントが自動的にセットに含まれるようにしたい場合は、MonoBehaviourのOnEnableと OnDisableからランタイムセットのAddまたはRemoveメソッドを呼び出すこともできます。

例えば、架空のEnemyクラスは以下のようなコードになる。

ここで、各エネミー・コンポーネントはOnEnableまたはOnDisableメソッドを使用して、自身を追加または削除することができる。Inspectorにm_RuntimeSetフィールドが設定されていれば、Enemyコンポーネントは自動的にEnemyRuntimeSetSOに表示されます。これは、プレハブでエネミー・コンポーネントを使う場合に特に便利だ。

このテクニックの制限事項として、実行時にScriptableObjectを検査する場合、デフォルトではInspectorのItemsリストの内容を見ることができません。

代わりに、各要素フィールドに "Type mismatch "と表示される。設計上、ScriptableObjectはシーンオブジェクトをシリアライズすることはできません。リストは実際に機能しているが、データが正しく表示されない。

混乱を避け、インスペクタにリストが表示されないようにするには、public プロパティまたは HideInInspector 属性を使用します。

この問題は、カスタムエディタースクリプトとインスペクターで修正することもできます。良い例としては、Unity Asset StoreのSoap - ScriptableObject Architecture Patternや、以下のPatternsのデモ例があります。

public class Enemy: MonoBehaviour
{
     [SerializeField] private EnemyRuntimeSetSO m_RuntimeSet;

     private void OnEnable()
     {
         m_RuntimeSet.Add(this);
     }

     private void OnDisable()
     {
         m_RuntimeSet.Remove(this);
     }
}
ランタイムセット

パターン・デモ

PaddleBallSOにはランタイム・セットのユースケースはないが、プロジェクトのPatternsデモには、このデザイン・パターンが機能していることを示す小さなサンプルが含まれている。

Spawn Blockボタンをクリックすると、ブロックの壁が少しずつ埋まっていきます。ボールは接触するとブロックを破壊する。

Select Set を選択して、BlockRuntimeSet_Dataをインスペクタに表示します。これは、シーン内のすべてのブロックのリストを含む。

ボールがブロックを消していくのと同じくらい早く、ブロックをグリッドに入れ続けることができる。同時に、ランタイム・セットは、アイテム・リストでアクティブに変化するブロック数を追跡する。

Patternsのデモでは、ランタイム・セットを扱う際のQOL(クオリティ・オブ・ライフ)の向上をいくつか示している:

  • カスタムインスペクターScriptableObjectは、そのままではシーンオブジェクトをシリアライズできないので、GameObjectRuntimeSetEditorスクリプトは、デフォルトのタイプミスマッチエラーをオーバーライドするカスタムインスペクタを提供します。インスペクタ(Inspector)内の任意のブロックをクリックして、シーン階層(Scene Hierarchy)内でハイライトします。
  • イベントを更新する:ランタイム・セットにはItemsChangedというSystem.Actionがあります。これは、Items リストからメンバーを追加または削除するときに、Editor スクリプトに通知します。これにより、[アイテム] 一覧が変更されると、[インスペクタ] が更新されます。イベントチャネルは、カスタムRuntimeSetCounterコンポーネントに現在のアイテム数を同期します。これは、オンスクリーンUIに総ブロック数を表示します。
カスタム検査官

ランタイムセットはどのように役立つか

次にGameObjectやコンポーネントの一元的なリストが必要になったら、ランタイム・セットを考えてみよう。これらは、検索操作(Object.FindObjectOfTypeや GameObject.FindWithTag)よりも安価で、シングルトンよりも問題が少ない。

ScriptableObjectとして、シーン内の個々のGameObjectから切り離されます。実行時に頻繁に追跡したいものについては、必要な数だけ実行時セットを保持する。

ランタイム・セットは、不必要な依存関係を発生させることなく、ゲームプレイ・オブジェクトをモニターする方法を簡素化することができる。デバッグやテストで節約した時間を、プレーヤーのための新しいゲームプレイ体験の開発に費やすことができます。

スクリプト可能なアウトロ

その他のScriptableObjectリソース

私たちは、ランタイム・セットがあなたの今後のプロジェクトに役立つことを願っています。

ScriptableObjectsを使ったデザインパターンについては、テクニカル電子ブックをご覧ください、 UnityでScriptableObjectsを使用してモジュラーゲームアーキテクチャを作成する.また、一般的なUnity開発のデザインパターンについては、以下をご覧ください。 ゲームプログラミングパターンでコードをレベルアップ.

このコンテンツにご満足いただけましたか?