インベントリ / クリックでアイテム情報を表示

はじめに

今回行う作業は2つです。まず初めにアイテムスロットをクリックされた際の挙動を自在に設定でいるよう、ItemDetailクラスを作成します。次にバッグから所持アイテムの取得メソッドの作成を行います。設計図では、下図の「」印を付けた箇所に相当します。





アイテムスロットのクリック

初めに作業の流れを説明します。まずはItemDetail抽象クラスを作成し、中でOnClickCallback()抽象メソッドを定義します。アイテムスロットのクリックがされたタイミングで、この抽象メソッドを呼ぶように設定できれば完成です。ところで、アイテムをクリックした際の挙動はItemBagクラスの次の部分で指定していました。
ItemBag.cs
1void Awake()
2{
3    for (int i = 0; i < slotNumber; i++)
4    {
5        //slotNumber の数だけスロットを生成し、ItemBagの子オブジェクトとして配置する
6        var slot = Instantiate(slotPrefab, this.transform, false)
7            .GetComponent<ItemSlot>();
8
9        // ItemSlotの初期化
10        slot.Initialize((item, number, slotObj) => Debug.Log("クリックされました"));
11
12        AllSlots.Add(slot);
13    }
14
15    UpdateItem();
16}
つまり10行目のInitialize()メソッドの引数にコールバクメソッドを渡すことで、目標の機能を実現できそうです。


作業をはじめます。まずはUnityの「Inventory/Scripts/」フォルダに「ItemDetailBase」スクリプトを作成してください。抽象クラスとなるため「Base」というワードを追加しています。


次に、スクリプトの中身を下記コードで上書きしてください。
ItemDetailBase.cs
1using UnityEngine;
2
3namespace FlMr_Inventory
4{
5    /// <summary>
6    /// スロットがクリックされたときの挙動を制御する抽象クラス
7    /// </summary>
8    public abstract class ItemDetailBase : MonoBehaviour
9    {
10        /// <summary>
11        /// スロットがクリックされた際に呼ばれるコールバックメソッド
12        /// </summary>
13        /// <param name="itemBag">アイテムバッグ</param>
14        /// <param name="item">スロットに入っているアイテム</param>
15        /// <param name="number">そのアイテムの所持数</param>
16        /// <param name="slotObj">スロットのゲームオブジェクト</param>
17        protected internal abstract void OnClickCallback
18            (ItemBag itemBag, ItemBase item, int number, GameObject slotObj);
19    }
20}
上で説明した通り、抽象メソッドを一つ持ったシンプルなクラスになっています。このような場合インターフェースを用いるのが普通ですし、事実、後にインターフェースによる実装も解説予定です。にもかかわらずここでは抽象クラスを用いた理由は、インターフェースをシリアル化し、インスペクター上で登録するためには工夫を要するためです。

ではItemBagクラス内でOnClickCallback()メソッドを参照しましょう。まずはインスペクター上からItemDetailBaseクラス(のサブクラス)のインスタンスを登録できるようにします。ItemBagクラス内に次のコードを追加してください。
ItemBag.cs
1/// <summary>
2/// スロットがクリックされた際の挙動
3/// </summary>
4[SerializeField] private ItemDetailBase itemDetail;
続けてAwake()メソッドを次のように編集します。
ItemBag.cs
1void Awake()
2{
3    for (int i = 0; i < slotNumber; i++)
4    {
5        //slotNumber の数だけスロットを生成し、ItemBagの子オブジェクトとして配置する
6        var slot = Instantiate(slotPrefab, this.transform, false)
7            .GetComponent<ItemSlot>();
8
9        // ItemSlotの初期化
10        slot.Initialize(
11            // スロットがクリックされた際に呼ばれる関数
12            (item, number, slotObj) => itemDetail.OnClickCallback(this,item,number,slotObj)
13        );
14
15        AllSlots.Add(slot);
16    }
17
18    UpdateItem();
19}
この変更により、スロットがクリックされた際に12行で作成している関数が呼ばれ、OnClickCallback()が実行されようになりました。

一度動作確認をしたいのですが、ItemDetailBaseクラスは抽象クラスであるためインスタンスを生成することができません。そこでUnityに戻り「Inventory/Demo/Scripts/」フォルダにItemDetailTestスクリプトを作成してください。その中を次のコードを上書きします。
ItemDetailDemo.cs
1using UnityEngine;
2
3namespace FlMr_Inventory.Demo
4{
5    /// <summary>
6    /// ItemDetailの動作確認のためのクラス
7    /// </summary>
8    public class ItemDetailDemo : ItemDetailBase
9    {
10        protected internal override void OnClickCallback
11            (ItemBag itemBag, ItemBase item, int number, GameObject slotObj)
12        {
13            Debug.Log($"{itemBag}内の{item.ItemName}がクリックされました。現在{number}個持っています。");
14        }
15    }
16}
全てのスクリプトが保存されていることを確認し、Unityに戻ってください。そこでシーン上に空のオブジェクトを作成し、ItemDetailと名付けてください。さらに先ほど作成した「ItemDetailDemo」スクリプトをアタッチしてください。


さらにItemBagオブジェクトのコンポーネントに、ItemDetailオブジェクトを登録します。


ではゲームを実行し、スロットをクリックしてみてください。下図のように、正しくアイテム等の情報が表示されることを確認してください。




バッグアイテムの情報取得メソッド

次にバッグから所持アイテムの取得メソッドの作成を行います。次のコードをItemBagクラスに追加してください。
ItemBag.cs
1/// <summary>
2/// バッグ内の全てのアイテムとその個数を取得する
3/// </summary>
4/// <returns></returns>
5public Dictionary<ItemBase, int> GetAllItems()
6{
7    return Data.Ids
8        .ToDictionary(id => ItemUtility.Instance.ItemIdTable[id], id=>Data.GetQty(id));
9
10    /***** Linqを使わない記述 ******
11    Dictionary<ItemBase, int> result = new Dictionary<ItemBase, int>();
12    foreach (var id in Data.Ids)
13    {
14        ItemBase item = ItemUtility.Instance.ItemIdTable[id];
15        result.Add(item,Data.GetQty(id));
16    }
17    return result;
18    *****************************/
19
20}
21
22/// <summary>
23/// idを指定して個数を取得
24/// </summary>
25/// <param name="id"></param>
26/// <returns></returns>
27public int Find(int id)
28{
29    return Data.GetQty(id);
30}
31
32/// <summary>
33/// アイテムを指定して個数を取得
34/// </summary>
35/// <param name="item"></param>
36/// <returns></returns>
37public int Find(ItemBase item) => Find(item.UniqueId);
2つの便利メソッドを追加しました。まず一つ目はバッグの全情報を返すGetAllItems()メソッドです。KeyをアイテムValueを個数とした辞書を作成し返しています。Linqが苦手な方は、コメントアウトしたコードを見ると分かりやすいと思います。
次の二つは(引数は異なりますが)同じメソッドです。アイテム(又はアイテムid)を指定して個数を受け取るメソッドです。

今回は以上です。
ここまでの状況は  Github から確認できます。