Hero image

Unity プロジェクトで ScriptableObject ベースの列挙型を使用する

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

このページでは、Unity プロジェクトで ScriptableObject ベースの列挙型を使用する方法について説明します。

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

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

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

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

始める前の重要事項

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

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

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

tab 1

ENUM 値を削除または並べ替えると、予期しない動作が発生する可能性があります。

拡張可能な列挙型

列挙型は、コード内の固定の名前付き値セットを管理するための便利な方法です。ただし、いくつかの制限があります。シリアライズされた列挙型の値はシンボル名ではなく整数として格納されるため、値の削除や並べ替えは、正しくない動作や予期しない動作につながる可能性があります。つまり、特に列挙型が多数ある場合は、Unity 開発において頭痛の種となる可能性があるということです。

標準的なアプローチ

典型的な列挙型は以下のようになります。

[System.Serializable]
public enum HandGestures
{
ロック
紙、
はさみ
}

System.Serializable 属性を使用して列挙型をシリアル化することができ、Inspector に表示されます。

問題

値を並べ替えたり削除したりすると、問題が発生する可能性があります。各値は内部的には整数であるため、それが表す内容は異なるものになる可能性があります。特定の例では、Paper 値を削除すると、Scissors は 1 の値を想定します。

あるいは、以下の例のように値を追加したとします。

選択された列挙値は、削除されたエントリの後になると変更されます。

これは、プロジェクトのメンテナンスや更新時に、特に列挙型に多数の値が含まれている場合に問題を引き起こす可能性があります。この問題は、空白または未使用の要素を残すか、整数値を明示的に設定することで軽減できます。しかし、どちらの解決策も理想的ではありません。

[System.Serializable]
public enum HandGestures 
{
    Rock,
    Paper,
    ThumbsUp,    // inserted value
    Scissors
}
tab3

スクリプタブルオブジェクト間の等価性のチェック

ScriptableObject ベースの列挙型

ScriptableObject ベースの列挙型を使用すると、従来の列挙型の機能を使用できますが、個別のアセットとして保存されます。たとえば、次の例で PaddleBallSO プロジェクトの PlayerIDSO ScriptableObject を見てみましょう。

基本的には、これは空の ScriptableObject です。

これを使用して、プロジェクト内に P1、P2 などの ScriptableObject アセットを多数作成できます。データが含まれていない場合でも、比較のために ScriptableObject を使用できます。プロジェクトに新しい ScriptableObject アセットを作成し、名前を付けるだけです。

プロジェクト内で必要な数だけプレイヤー ID を作成し、簡単に切り替えることができます。GameDataSO スクリプトで割り当てられたアセットを変更するだけです。

等しいかどうかをチェックする場合、これは列挙型と同様に動作します。2 つの変数が同じ ScriptableObject を参照しているか?その場合、同じアイテムタイプです。それ以外は違います。

追加のデータがなくても、ScriptableObject はカテゴリまたはアイテムタイプを表します。

[CreateAssetMenu(fileName = "PlayerID")]
public class PlayerIDSO: ScriptableObject
{
}

PaddleBallSO のプレイヤー ID

PaddleBallSO では、PlayerIDSO がチーム指定になります。GameDataSO の P1 アセットと P2 アセットを使用して、2 つのパドルを区別します。

GameSetup スクリプトは、各パドルにプレイヤー ID を割り当てます。ゲームプレイ中、パドルスクリプトはプレイヤー入力を指定されたチーム ID と比較します。

これには、あらゆるタイプのマルチプレイヤーゲームに対応するアプリケーションがあります。あるいは、Enum が手に届く他の場所での採用を検討してください。

ScriptableObject はインスペクター内で単に割り当てられるだけなので、名前の変更や並べ替えに関して同じ問題が発生することはありません。

識別子の名前をそれぞれ「Player1」または「Player2」に変更しますか?そうすることで、すべてが機能し続けます。ScriptableObject をさらに追加しますか?問題ありません。インスペクターでのアセットの割り当ては変わりません。

tab5

ブロックは割り当てられたチームを比較に使用します。

Patterns デモ

この動作はゲームプレイの作成に役立ちます。Patterns デモで、「Switch Enum」ボタンをクリックしてチームを変更します。DemoBall 上の MonoBehaviour がそれに応じて SpriteRenderer を更新します。

ボールは衝突時にブロックにダメージを与えますか?平等性を簡単にテストして見つけましょう。以下のコード例でこれらを比較する方法の 1 つを紹介します。

このメソッドは、2 つのゲームオブジェクトが同じチームに属しているかどうかを判断でき、味方と敵のインタラクションをチェックするときに便利です。この単純な比較は、アイテムの拾得やダメージなど、「チーム」や「アラインメント」があるすべての要素に当てはまります。

public static bool AreEqual(GameObject a, GameObject b)
{
    TeamID teamA = a.GetComponent<TeamID>();
    TeamID teamB = b.GetComponent<TeamID>();

    // If both are not null and have matching IDs
    if (teamA != null && teamB != null)
    {
        return teamA.ID == teamB.ID;
    }

    // Otherwise, return false
    return false;
}

動作の拡張

面白いのは、ScriptableObject にロジックを追加することです。従来の列挙型とは異なり、ScriptableObject はデータを保持するだけでなく、フィールドやメソッドを持つことができます。

各 ScriptableObject が特殊な比較ロジックを持つことができるように、これらを使用します。例えば、特殊なダメージ効果(冷気、熱、電気、魔法など)を定義する ScriptableObject などです。

アプリケーションがゲームプレイアイテムを装備するためにインベントリシステムを必要とする場合、ScriptableObject はアイテムタイプや武器スロットを表すことができます。特定のキャラクターは特定のアイテムを持つことができませんか?一部のアイテムは魔法や特殊能力を持っていますか?ScriptableObject ベースの列挙型では、それをチェックするメソッドを追加できます。

前の例の MonoBehaviour DemoBall には、ScriptableObject を比較する AreEqual メソッドが含まれています。動作を拡張する場合、ScriptableObject 自体に比較ロジックを組み込むことができます。

Patterns デモでは、ボールがオブジェクトに衝突する際の選択性を高めるために修正を加えることができます。以下のコード例で、衝突用の汎用アイテムを見てみましょう。

これにより、現在のデモと同様の結果が得られますが、m_Weakness フィールドが追加されています。これにより、各 ScriptableObject は衝突時に破棄する別の ScriptableObject を定義できます。

AreEqual メソッドを呼び出すのではなく、各 ScriptableObject が独自の比較ロジックを管理するだけです。

その結果、柔軟性と拡張性が高まりました。ボールが別のチームのブロックを破壊する代わりに、具体的に記述することができます。シーン内の複数のボールは、個々の CollisionItem に応じて、異なるブロックを破壊する可能性があります。

これにより、より複雑で多様なインタラクションが可能になります。じゃんけんシステムを作る場合、3 つの ScriptableObject を定義できます。じゃんけん、紙はさみ。それぞれが固有の m_Weakness を持ち、IsWinner メソッドを使用してインタラクションを処理できます。

列挙型とは異なり、ScriptableObject はこのプロセスをモジュール化し、適応性を持たせます。別のデータセットと同期するために、余分なデータ構造に依存したり、ロジックを追加したりする必要はありません。ロジックを処理するフィールドやメソッドを追加するだけです。

public class CollisionItem: ScriptableObject
{

   [SerializeField] private CollisionItem m_Weakness;

   public bool IsWinner(CollisionItem other)
   {
       return other.m_Weakness == this;
   }
}

ScriptableObject ベースの列挙型の利点

ScriptableObject ベースの列挙型に慣れると、特にチームメイトと作業するときにワークフローを改善できることがわかります。これらはアセットであるため、更新することでマージ競合が減り、データ損失のリスクが軽減されます。

新しい ScriptableObject ベースの列挙型を追加することは、別のアセットを作成するのとまったく同じです。従来の列挙型とは異なり、新しい値を追加しても既存のコードは壊れません。さらに、Unity には他のアセットと同様に、検索、フィルタリング、整理を行うためのツールがすでに組み込まれています。

おまけに、エディターのドラッグアンドドロップ UI を使用すると、デザイナーはソフトウェア開発者から余分なサポートを受けることなくゲームプレイデータを拡張できます。最初にフィールドを設定する方法を調整する必要がありますが、その後デザイナーが独自にそれらのフィールドにデータを入力できます。

スクリプタブルアウトロ

その他の ScriptableObject リソース

ScriptableObject ベースの列挙型は、チームがコラボレーションと効率を向上させるために使用できるもう 1 つのリソースです。

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