【Unity入門】持ち物リストをプレイヤーに持たせる ― プレイヤーごとのインベントリ

広告

前回の記事「アイテムを拾って持ち物に表示する最小コード」では、持ち物のメモ帳をゲーム全体に1冊だけ置く形(static)で作りました。動かすまでは最短ですが、「持ち物は誰のもの?」を表せないという弱点があります。

この記事では、持ち物リストをプレイヤー自身に持たせる形に作り替えます。プレイヤーが2人になっても、敵や宝箱に持ち物を持たせても、そのまま使える作りです。

この記事で作るもの

  • 前回と同じ動き(木や石に触れると消えて、持ち物画面に「木 ×3」のように並ぶ)
  • ただし、持ち物リストはプレイヤーのオブジェクトに付いている
  • 持ち主が増えても、持ち物が混ざらない

前回との違い

前回(static のメモ帳)今回(プレイヤーが持つ)
持ち物リストの場所ゲーム全体に1冊持ち主ごとに1つ
追加の書き方OwnedItemsData.Instance.Add(type)inventory.Add(type)
持ち主が2人いると持ち物が混ざる別々に持てる
拾える相手の判定Player タグ持ち物リストを持っているか

変えるのは、スクリプトの中のほんの数か所です。「誰の持ち物か」が、コードの形ではっきり表れるようになります。

全体の流れ

役割は前回と同じ「拾う」「覚える」「表示する」です。変わるのは、「覚える」の置き場所だけです。

役割スクリプトやること
覚えるInventory持ち主に付けて、何を何個持っているかを覚える
拾うItem触れた相手の Inventory に足して、自分は消える
表示するItemsDialog / ItemButton指定された Inventory を読んで、マスに並べる

用意しておくもの

  • 前回の記事で作ったプロジェクト(Item、ItemButton、ItemsDialog、シーンの配置まで済んだもの)
  • プレイヤーに CharacterController が付いていること

前回の OwnedItemsData.cs は、この記事の最後で削除します。先に4つのスクリプトを書き換えてから消すと、途中で赤いエラーが出ずに進められます。

Step 1:持ち物リストをプレイヤーに付ける(Inventory)

Inventory.cs というスクリプトを新しく作ります。前回の OwnedItemsData とほとんど同じ中身ですが、ゲームオブジェクトに付けられる形(MonoBehaviour)になっています。

using System.Collections.Generic;
using UnityEngine;

public class Inventory : MonoBehaviour
{
    // 持ち物のリスト(種類と個数)
    public List<OwnedItem> OwnedItems { get; } = new List<OwnedItem>();

    // アイテムを1個追加する
    public void Add(Item.ItemType type)
    {
        // もう持っている種類なら、個数を1つ増やして終わる
        foreach (var item in OwnedItems)
        {
            if (item.Type == type)
            {
                item.Number++;
                return;
            }
        }

        // 見つからなかった=初めて拾った種類なので、新しい行を作る
        var newItem = new OwnedItem(type);
        newItem.Number = 1;
        OwnedItems.Add(newItem);
    }

    // アイテム1種類ぶんの「種類と個数」
    public class OwnedItem
    {
        public OwnedItem(Item.ItemType type)
        {
            Type = type;
        }

        public Item.ItemType Type { get; }

        public int Number { get; set; }
    }
}

前回との違いは3つです。

  • public static OwnedItemsData Instance がなくなりました。「ゲームに1冊」ではなく、付けたオブジェクトごとに1つになります
  • private のコンストラクタがなくなりました。MonoBehaviour は Unity が作るので、自分で new しません
  • MonoBehaviour を継承したので、プレイヤーに「Add Component」で付けられます

※ 持ち主に付けるスクリプトなので、名前は PlayerInventory ではなく Inventory にしました。あとで敵や宝箱に付けても、名前が合わなくなりません。

Step 2:拾う側を書き換える(Item)

Item.cs の OnTriggerEnter を書き換えます。

using UnityEngine;

public class Item : MonoBehaviour
{
    // アイテムの種類
    public enum ItemType
    {
        Wood,
        Stone
    }

    [SerializeField] private ItemType type;

    // 何かが触れたら、持ち物リストを持っている相手だけが拾える
    private void OnTriggerEnter(Collider other)
    {
        if (!other.TryGetComponent(out Inventory inventory))
        {
            return;
        }

        // 触れた相手の持ち物に追加して、地面のアイテムは消す
        inventory.Add(type);
        Destroy(gameObject);
    }
}
  • TryGetComponent は、触れた相手に Inventory が付いていれば取り出し、付いていなければ false を返します
  • 前回の CompareTag("Player") は要らなくなりました。「Player という名前のタグ」ではなく、「持ち物を持てる相手かどうか」で判断できるからです
  • 追加先が OwnedItemsData.Instance から、触れた相手の inventory に変わりました。これが、持ち主ごとに持ち物が分かれるいちばんのポイントです

※ Inventory は、Collider と同じオブジェクトに付けてください。Collider が子オブジェクトにあるときは、TryGetComponent を GetComponentInParent<Inventory>() に変えると、親に付いた Inventory を探せます。

Step 3:1マスぶんの表示を直す(ItemButton)

ItemButton.cs で変えるのは、型の名前1か所だけです。

// 変更前
public void SetItem(OwnedItemsData.OwnedItem ownedItem)

// 変更後
public void SetItem(Inventory.OwnedItem ownedItem)

残りの中身(画像と個数を表示する処理)は、前回のままで動きます。

Step 4:持ち物画面に「誰の持ち物を見るか」を教える(ItemsDialog)

ItemsDialog.cs には、どの Inventory を表示するかを持たせます。前回は OwnedItemsData.Instance に直接アクセスしていましたが、今回は Inspector で渡してもらいます。

using UnityEngine;

public class ItemsDialog : MonoBehaviour
{
    // 誰の持ち物を表示するか(Inspectorで入れる)
    [SerializeField] private Inventory inventory;

    // 持ち物画面のマス(Inspectorで全部入れる)
    [SerializeField] private ItemButton[] itemButtons;

    // 持ち物画面の表示/非表示を切り替える
    public void Toggle()
    {
        gameObject.SetActive(!gameObject.activeSelf);

        if (gameObject.activeSelf)
        {
            Refresh();
        }
    }

    // 全マスを、指定された Inventory の内容で更新する
    private void Refresh()
    {
        var ownedItems = inventory.OwnedItems;

        for (int i = 0; i < itemButtons.Length; i++)
        {
            if (i < ownedItems.Count)
            {
                // i番目の持ち物があれば表示する
                itemButtons[i].SetItem(ownedItems[i]);
            }
            else
            {
                // なければ空にする
                itemButtons[i].SetItem(null);
            }
        }
    }
}

変わったのは、次の2か所だけです。

  • [SerializeField] private Inventory inventory; を追加しました
  • Refresh の中で、OwnedItemsData.Instance.OwnedItems を inventory.OwnedItems に変えました

これで持ち物画面は、「渡された持ち主の持ち物」を表示する作りになります。持ち主を差し替えるだけで、別の人の持ち物画面にもなります。

Step 5:シーンに配置してつなぐ

  1. プレイヤーに付ける:Hierarchy でプレイヤーを選び、Inspector の「Add Component」から Inventory を追加します
  2. 持ち物画面とつなぐ:ItemsDialog を選び、Inspector の Inventory の欄に、プレイヤーをドラッグして入れます(ItemsDialog が非アクティブでも、Hierarchy から選べば設定できます)
  3. 古いスクリプトを消す:ここまでできたら、前回の OwnedItemsData.cs を削除します。4つのスクリプトからの参照がなくなっているので、エラーは出ません

アイテムのシーン配置(Cube に Item、Is Trigger オン)と、持ち物ボタンの On Click は、前回のままで大丈夫です。

動かしてみよう

  1. ゲームを再生します
  2. プレイヤーを動かして、木に触れます。木が消えます
  3. 「持ち物」ボタンを押します。「木 ×1」と表示されます
  4. もう1個拾ってから開き直すと、「木 ×2」になります

ここまでは前回と同じ動きです。見た目は変わらず、中身だけが「プレイヤーが持つ」形に変わりました。

もう1人の持ち主で試してみよう

持ち主ごとに持ち物が分かれることを、実際に確かめてみましょう。ここでは、元のプレイヤーを Player1、新しく置く Cube を Player2 と呼びます。

  1. Cube を1つ置き、Rigidbody と Inventory を付けます(名前は「Player2」にします)
  2. 再生して、Scene ビューで Player2 をアイテムに触れさせます。アイテムが消えます
  3. プレイヤーを動かさずに「持ち物」ボタンを押します(いまは Player1 の持ち物が表示されます)。Player1 の持ち物は増えていません
  4. 再生を止めて、ItemsDialog の Inventory の欄を、Player2 に差し替えます
  5. もう一度、Player2 にアイテムを拾わせてから「持ち物」ボタンを押します。今度は Player2 の持ち物が表示され、Player2 が拾った分だけが並びます

前回の static のメモ帳だと、誰が拾っても同じメモ帳に書かれていました。今回は、拾った本人の Inventory にだけ書き込まれます。

うまくいかないときは

  • 拾えない:アイテムの Is Trigger がオンか、触れる側に Inventory が付いているかを確認します。前回と違って、Player タグは関係なくなっています
  • 持ち物画面を開くとエラーが出る:ItemsDialog の Inventory の欄が空になっていないかを確認します
  • プレイヤーをゲーム中に生成している:Inspector でドラッグできないので、生成したあとにコードで探して渡します。たとえば ItemsDialog に次のように書きます
private void Start()
{
    inventory = FindFirstObjectByType<Inventory>();
}

([SerializeField] を付けたままなので、Inspector で入れた場合は上書きされる点に注意してください。生成型の場合だけ使います)

なぜ「持ち主が持つ」形が使われるのか

  • 持ち主が増えても、そのまま使える:プレイヤー、敵、宝箱、ショップの店主に、同じ Inventory を付けるだけです
  • 「誰の」がコードに現れる:inventory.Add(type) と書くと、どの持ち物に足しているかが読めば分かります
  • 複数プレイヤーやオンラインに広げやすい:持ち物が持ち主ごとに独立しているので、同期やセーブの単位も「持ち主ごと」にそろえられます

一方で、持ち主が1人だけの小さなゲームなら、前回の static の形でも十分です。「持ち主が増える可能性があるか」が、どちらを選ぶかの目安になります。

ここまでで省いたこと

前回に引き続き、動かすことを優先して次のものは省きました。次に進むときの課題として、気になるものから試してみてください。

  • セーブ:Inventory の中身(種類と個数)を保存・読み込みする仕組み
  • アイテムの種類を ScriptableObject にする:enum ではなく、アセットとして作ると、コードを触らずにアイテムを増やせます
  • 持ち物が変わったら画面を更新する(イベント):画面を開き直さなくても、拾った瞬間に更新できます
  • データを普通のクラスに分ける:Inventory の中身を MonoBehaviour でないクラスに移すと、セーブやテストがしやすくなります
  • オンライン対戦:持ち物の追加はサーバー側で行い、結果を各プレイヤーに同期する形にします

「なぜデータと画面を分けるのか」という考え方は、【Unity入門】アイテム所持の仕組みを作ろうで解説しています。あわせて読むと、この記事の作りの理由がよりつかめます。

訪問数 3 回, 今日の訪問数 3回

広告

C#

Posted by hidepon