【Unity】子オブジェクトを使ったインベントリ

2024年5月7日

柔軟性の高い武器管理システムをUnityの子オブジェクトの構成を使って実現します

実行サンプル

まず、武器管理の様子を見てみましょう
プレイヤーを上下左右矢印で移動できます
武器に触れると持ち物として取得できます
サンプルとして武器は3つを用意しています(幾つでも対応は可能です)
数値の1キーで武器を有効にし、2キーで無効にしています

実行動画

3つのソードを取得したときの様子

プレイヤーのこオブジェクトとして管理されているのがわかります
プレイヤーと共に扱われ、いかにも持ち物!という雰囲気を出しています

このような仕組みを集約と言います

構成サンプル

集約とコンポジションを使った構成とすることで、一見複雑には見えますがアイテムの追加と機能ごとの管理ができ、わかりやすい構成となります

シーン構成

プレイヤー

武器1(ソード1)

武器2(ソード2)

武器3(ソード3)

スクリプト

このコードは、Unityゲームでの武器のインベントリシステムを実装しています。特徴は以下の通りです:

  1. 子オブジェクト管理: ObjectManipulatorクラスは、武器を親オブジェクトの子供として移動させるメソッドを提供しています。これにより、武器の追加や削除が容易になります。
  2. インターフェース: IStatusIWeaponStatusのインターフェースがあります。これにより、異なるタイプの武器を統一的に扱うことができます。
  3. 武器の表示と非表示の制御: WeaponControllerクラスでは、武器の表示と非表示を制御するメソッドが提供されています。これにより、プレイヤーが武器をアクティブにしたり非表示にしたりできます。
  4. 武器の状態の表示: WeaponControllerクラスでは、GUIを使用して武器の状態を表示します。これにより、プレイヤーが現在装備している武器の情報を確認できます。

IStatusインターフェース

あらゆるステータス状態の実装の元になります
武器以外にもライフのアイテムであったり様々なクラスを実装できます

public interface IStatus
{
    string Name { get; set; } // オブジェクトの名前

    void Show(); // オブジェクトを表示するメソッド
    void Hide(); // オブジェクトを非表示にするメソッド

    bool IsActive(); // オブジェクトがアクティブかどうかを返すメソッド
}
  1. Name プロパティ: インターフェースには、オブジェクトの名前を表す Name プロパティが含まれています。このプロパティは、オブジェクトの名前を取得または設定するために使用されます。
  2. Show() メソッド: Show() メソッドは、オブジェクトを表示するためのメソッドです。このメソッドが呼び出されると、対象のオブジェクトが表示されるようになります。
  3. Hide() メソッド: Hide() メソッドは、オブジェクトを非表示にするためのメソッドです。このメソッドが呼び出されると、対象のオブジェクトが非表示になります。
  4. IsActive() メソッド: IsActive() メソッドは、オブジェクトがアクティブであるかどうかを判定するためのメソッドです。このメソッドが呼び出されると、対象のオブジェクトがアクティブであれば true を返し、非アクティブであれば false を返します。

このインターフェースは、オブジェクトが持つ基本的な機能を定義しており、これを実装するクラスは、そのオブジェクトを表示・非表示にすることや、その状態を確認することができます。

IWeaponStatusインターフェース

武器関連のインターフェース定義コードになります
ステータスの基本に武器に特化したメンバーの追加を要求します

public interface IWeaponStatus : IStatus
{
    int Power { get; set; } // 武器の攻撃力
    int Defence { get; set; } // 武器の防御力
}

IWeaponStatus インターフェースは IStatus インターフェースを継承しており、そのため IStatus インターフェースで定義されたプロパティやメソッドを含みます。

  1. Power プロパティ: Power プロパティは、武器の攻撃力を表します。このプロパティは、武器の攻撃力を取得または設定するために使用されます。
  2. Defence プロパティ: Defence プロパティは、武器の防御力を表します。このプロパティは、武器の防御力を取得または設定するために使用されます。

IWeaponStatus インターフェースは、武器の特性やステータスを表すために使用されます。このインターフェースを実装するクラスは、武器の攻撃力や防御力を取得・設定できると同時に、 IStatus インターフェースで定義されたオブジェクトの名前を取得・設定し、オブジェクトを表示・非表示にするメソッドを持つことができます。

SwordStatus スクリプト

武器の中でも、ソード(剣)について特化したクラスになります
このシンプルなサンプルでは、特にソード特有のコードは追加していませんが、追記可能です

using UnityEngine;

public class SwordStatus : MonoBehaviour, IWeaponStatus
{
    [field: SerializeField] public string Name { get; set; } // 武器の名前
    [field: SerializeField] public int Power { get; set; } // 武器の攻撃力
    [field: SerializeField] public int Defence { get; set; } // 武器の防御力

    public void Show() => gameObject.SetActive(true); // 武器を表示する
    public void Hide() => gameObject.SetActive(false); // 武器を非表示にする

    public bool IsActive() => gameObject.activeSelf; // 武器がアクティブかどうかを返す
}
  1. Name, Power, Defence プロパティ: クラスには、武器の名前、攻撃力、防御力を表す3つのプロパティがあります。これらのプロパティは SerializeField 属性を使用してシリアライズされ、Unityのインスペクターで編集可能になります。これにより、Unityのエディターで簡単に武器の属性を設定できます。
  2. Show() メソッド: Show() メソッドは、武器を表示するためのメソッドです。これが呼び出されると、gameObject.SetActive(true) を使用して、この武器のゲームオブジェクトをアクティブにします。
  3. Hide() メソッド: Hide() メソッドは、武器を非表示にするためのメソッドです。これが呼び出されると、gameObject.SetActive(false) を使用して、この武器のゲームオブジェクトを非アクティブにします。
  4. IsActitve() メソッド: IsActive() メソッドは、武器がアクティブであるかどうかを判定するためのメソッドです。gameObject.activeSelf を使用して、この武器のゲームオブジェクトがアクティブかどうかを返します。

このクラスは、Unityゲーム内で武器の状態を管理し、表示・非表示を切り替える機能を提供します

ObjectManipulator スクリプト

武器に触れて取得した際、プレイヤーの子オブジェクトとして帯刀する機能を持ちます

using UnityEngine;

public class ObjectManipulator : MonoBehaviour
{
    // 子オブジェクトに移動するメソッド
    public void MoveObjectToChild(GameObject parentObject, GameObject childObject)
    {
        // 渡されたゲームオブジェクトを親オブジェクトの子供として設定する
        childObject.transform.parent = parentObject.transform;

        // 子オブジェクトの位置をリセットする(親オブジェクトのローカル座標系での原点に移動)
        childObject.transform.localPosition = Vector3.zero;
    }
}
  1. MoveObjectToChild(GameObject parentObject, GameObject childObject) メソッド: このメソッドは、親オブジェクトと子オブジェクトを受け取り、子オブジェクトを親オブジェクトの子供として移動させる役割を持ちます。
    • parentObject: 移動先の親オブジェクトを示します。
    • childObject: 移動させる子オブジェクトを示します。
    このメソッドでは、まず childObject の親を parentObject に設定し、次に子オブジェクトの位置を親オブジェクトのローカル座標系での原点に移動します。これにより、子オブジェクトが親オブジェクトに対して正確に配置されます。

このクラスは、ゲームオブジェクトを操作するためのユーティリティ機能を提供するものであり、特に親子関係を操作する際に便利です。

PlayerCommander スクリプト

武器をコントロールするためのコードです
今回は、シンプルに有効・無効について制御するサンプルとしています

using UnityEngine;

public class PlayerCommander : MonoBehaviour
{
    [SerializeField]
    WeaponController weaponController;
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            weaponController.Show("ソード1"); // ソード1を表示する
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            weaponController.Hide("ソード1"); // ソード1を非表示にする
        }
    }
}
  1. weaponController フィールド: WeaponControllerクラスのインスタンスを格納するためのシリアライズされたフィールドです。このフィールドは、インスペクターでPlayerCommanderオブジェクトにWeaponControllerを割り当てることができます。
  2. Update() メソッド: Unityのフレームごとに呼び出されるメソッドです。プレイヤーの入力を監視し、対応する操作を実行します。
  3. キー入力の検出: Input.GetKeyDown(KeyCode.Alpha1)Input.GetKeyDown(KeyCode.Alpha2)によって、キーボードの1キーと2キーの押下を検出します。
  4. weaponController.Show("ソード1"): weaponControllerに対して、Show()メソッドを呼び出しています。これにより、WeaponControllerクラス内の指定された名前の武器("ソード1")が表示されます。
  5. weaponController.Hide("ソード1"): weaponControllerに対して、Hide()メソッドを呼び出しています。これにより、WeaponControllerクラス内の指定された名前の武器("ソード1")が非表示にされます。

このクラスは、プレイヤーの入力に応じて武器を表示または非表示にする役割を果たします。

PlayerMovement スクリプト

プレイヤーの移動に特化したコードです
このように機能を切り分けることにより、あらゆる機能が入り混じったコードになることを防ぎ、保守性を高めることができます

using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float moveSpeed = 5f; // プレイヤーの移動速度

    void Update()
    {
        Vector2 movement = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized; // 入力された方向を正規化して移動ベクトルを作成
        Move(movement); // プレイヤーを移動させる
    }

    private void Move(Vector2 direction)
    {
        transform.Translate(direction * moveSpeed * Time.deltaTime); // 方向に応じてプレイヤーを移動させる
    }
}
  1. moveSpeed パブリック変数: プレイヤーの移動速度を表します。UnityのInspectorパネルで調整できます。
  2. Update() メソッド: Unityがフレームごとに呼び出す関数で、プレイヤーの移動を制御します。このメソッド内では、プレイヤーの入力を検出し、移動ベクトルを計算しています。
  3. Vector2 movement: プレイヤーの移動ベクトルを格納する変数です。Input.GetAxisRaw() を使用して水平方向と垂直方向の入力を取得し、それらを正規化して移動ベクトルを作成しています。
  4. Move(Vector2 direction) メソッド: プライベートなメソッドで、プレイヤーの移動を処理します。引数 direction には、プレイヤーの移動方向が渡されます。transform.Translate() を使用して、プレイヤーを指定された方向に移動させます。Time.deltaTime を乗算することで、フレームレートに依存しない移動を実現しています。

このスクリプトをアタッチしたゲームオブジェクトは、プレイヤーの移動を制御することができます

WeaponController スクリプト

武器を実際にコントロールするためにメソッド群を記述します
今回は、サンプルのため、有効・無効の機能のみを実装しています
また、デバッグのためにOnGUIライフメソッドを使い、画面上に情報表示をしています

using System.Linq;
using UnityEngine;

public class WeaponController : MonoBehaviour
{
    [SerializeField] private ObjectManipulator objectManipulator; // ゲームオブジェクトを操作するクラスの参照
    private IWeaponStatus[] weaponsStatus; // ウェポンステータスの配列

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (objectManipulator == null) return;
        HandleCollision(other);
    }

    private void HandleCollision(Collider2D collider)
    {
        objectManipulator.MoveObjectToChild(gameObject, collider.gameObject); // ウェポンを親オブジェクトの子供にする
        weaponsStatus = GetComponentsInChildren<IWeaponStatus>(true); // 子供オブジェクトからウェポンステータスを取得
        FindWeaponStatus(collider.GetComponent<IWeaponStatus>()?.Name)?.Hide(); // 衝突したウェポンを非表示にする
    }

    public void Show(string weaponName) => FindWeaponStatus(weaponName)?.Show(); // ウェポンを表示する
    public void Hide(string weaponName) => FindWeaponStatus(weaponName)?.Hide(); // ウェポンを非表示にする

    private IWeaponStatus FindWeaponStatus(string name) => weaponsStatus?.FirstOrDefault(status => status.Name == name); // 指定した名前のウェポンステータスを検索する

    private void OnGUI()
    {
        if (weaponsStatus == null) return;
        foreach (var status in weaponsStatus) DisplayWeaponStatus(status); // ウェポンステータスを表示する
    }

    private void DisplayWeaponStatus(IWeaponStatus status)
    {
        GUILayout.Label($"Name: {status.Name}");
        GUILayout.Label($"     Power: {status.Power}");
        GUILayout.Label($"     Defence: {status.Defence}");
        GUILayout.Label($"     Active: {status.IsActive()}");
    }
}
  1. objectManipulator フィールド: ObjectManipulator クラスのインスタンスを参照するフィールドです。このクラスは、ゲームオブジェクトを操作するための便利なメソッドを提供します。
  2. weaponsStatus フィールド: IWeaponStatus インターフェースの配列です。この配列には、ウェポンのステータスを持つオブジェクトが格納されます。
  3. OnTriggerEnter2D(Collider2D other) メソッド: 2Dトリガーコライダーが他のコライダーと接触したときに呼び出されるメソッドです。接触したコライダーを処理するために HandleCollision() メソッドを呼び出します。
  4. HandleCollision(Collider2D collider) メソッド: 衝突したコライダーを処理するメソッドです。コライダーを子オブジェクトに移動させ、衝突したウェポンを非表示にします。
  5. Show(string weaponName) メソッド: 指定された名前のウェポンを表示するメソッドです。
  6. Hide(string weaponName) メソッド: 指定された名前のウェポンを非表示にするメソッドです。
  7. FindWeaponStatus(string name) メソッド: 指定された名前のウェポンステータスを検索するメソッドです。
  8. OnGUI() メソッド: GUIイベントが発生したときに呼び出されるメソッドです。このメソッドは、ウェポンのステータスを表示するために DisplayWeaponStatus() メソッドを呼び出します。
  9. DisplayWeaponStatus(IWeaponStatus status) メソッド: ウェポンのステータスを表示するメソッドです。ウェポンの名前、攻撃力、防御力、アクティブ状態が表示されます。

このクラスは、トリガーコライダーとの衝突を処理し、それに応じてウェポンを表示または非表示にする機能を提供します。また、ウェポンのステータスを表示するGUIも提供します。

クラス図

全体を俯瞰するためのクラス図になります
各ファイルごとに機能を集約し、また、依存性を低くすることにより、メンテナンス性の向上を実現しています

アイコンの見方

全体クラス図

上記、クラス図を作成するための、PlantUMLソースコードになります
参考にために掲載しておきます

@startuml

class Legend {
    + "+" : Publicなメンバーを表します
    - "-" : Privateなメンバーを表します
    # "#" : Protectedなメンバーを表します
    <<use>> : 依存関係を示します
    <<uses>> : 使用関係を示します
    --|> : 継承関係を示します
}

@enduml
@startuml

interface IStatus {
    + Name: string  // オブジェクトの名前
    + Show(): void  // オブジェクトを表示するメソッド
    + Hide(): void  // オブジェクトを非表示にするメソッド
    + IsActive(): bool  // オブジェクトがアクティブかどうかを返すメソッド
}

interface IWeaponStatus {
    + Power: int  // 武器の攻撃力
    + Defence: int  // 武器の防御力
}

class ObjectManipulator {
    + MoveObjectToChild(parentObject: GameObject, childObject: GameObject): void  // 親オブジェクトから子オブジェクトへの移動を行うメソッド
}

class PlayerCommander {
    - weaponController: WeaponController  // 武器コントローラーへの参照
    + Update(): void  // 更新メソッド
}

class PlayerMovement {
    - moveSpeed: float  // 移動速度
    + Update(): void  // 更新メソッド
    - Move(direction: Vector2): void  // 移動を行うメソッド
}

class SwordStatus {
    - Name: string  // 武器の名前
    - Power: int  // 武器の攻撃力
    - Defence: int  // 武器の防御力
    + Show(): void  // 武器を表示するメソッド
    + Hide(): void  // 武器を非表示にするメソッド
    + IsActive(): bool  // 武器がアクティブかどうかを返すメソッド
}

class WeaponController {
    - objectManipulator: ObjectManipulator  // ゲームオブジェクトを操作するクラスの参照
    - weaponsStatus: IWeaponStatus[]  // ウェポンステータスの配列
    + OnTriggerEnter2D(other: Collider2D): void  // 2Dトリガーの衝突時に呼び出されるメソッド
    - HandleCollision(collider: Collider2D): void  // 衝突時の処理を行うメソッド
    + Show(weaponName: string): void  // 指定した名前のウェポンを表示するメソッド
    + Hide(weaponName: string): void  // 指定した名前のウェポンを非表示にするメソッド
    - FindWeaponStatus(name: string): IWeaponStatus  // 指定した名前のウェポンステータスを検索するメソッド
    + OnGUI(): void  // GUIの更新を行うメソッド
    + DisplayWeaponStatus(status: IWeaponStatus): void  // ウェポンステータスを表示するメソッド
}

IStatus <|.. IWeaponStatus
IWeaponStatus <|.. SwordStatus
ObjectManipulator --> "1" GameObject: <<use>>
PlayerCommander --> "1" WeaponController: <<uses>>
WeaponController --> "1" ObjectManipulator: <<uses>>
PlayerMovement --|> MonoBehaviour
SwordStatus --|> MonoBehaviour
WeaponController --|> MonoBehaviour

@enduml

Unity

Posted by hidepon