はじめに
今回はバッグの内容をスロットに伝え、スロットにアイコンを表示する機能を追加します。設計図では、下図の「
★」印を付けた箇所に相当します。

作業の概要は、
- ItemSlotクラスの作成
- ItemBagクラスからItemSlotクラスのUpdateItem()メソッドを呼ぶ
の2つです。
今回はバッグ内の情報をもとにスロットの表示を変更させる機能を実装します。 「Inventory/Scripts/」の中に「ItemSlot」スクリプトを作成し、ItemSlotプレハブ(Inventory/Prefabs/ItenSlot)にアタッチしてください。

次に作成したスクリプトを下記のコードで上書きします。
ItemSlot.cs
using UnityEngine;
using UnityEngine.UI;
namespace FlMr_Inventory
{
/// <summary>
/// 所持しているアイテムのアイコンを表示
/// クリックでアイテムに対してアクションを行う
/// 機能を実装するクラス
/// </summary>
internal class ItemSlot : MonoBehaviour
{
/// <summary>
/// UI画像の表示を司るクラス
/// 所持しているアイテムのアイコンを表示する
/// </summary>
[SerializeField] private Image icon;
/// <summary>
/// このスロットに入っているアイテム
/// </summary>
internal ItemBase Item { get; private set; }
/// <summary>
/// アイテムのアイコンを表示する
/// </summary>
/// <param name="item"></param>
/// <param name="number"></param>
internal void UpdateItem(ItemBase item, int number)
{
if (number > 0 && item != null)
{
// アイテムが空ではない場合
Item = item;
icon.sprite = item.Icon;
icon.color = Color.white;
}
else
{
// アイテムが空である場合
Item = null;
icon.sprite = null;
icon.color = new Color(0, 0, 0, 0);
}
}
}
}
17行目の変数には
[SerializeField]属性が付与されており、この変数にはImageコンポーネントを持ったIconオブジェクトを登録します。22行目のプロパティーはこのスロットに入っているアイテムを保持するプロパティーで、ゲームが進行するとともに変動します。この変数のsetterは
privateとなっているため、外部から直接変更することはできません。そこで29行目のメソッドが役に立ちます。第一引数には追加したいアイテム、第二引数には個数を指定できます。ちなみに引数
itemが
nullの場合や個数が0の場合は、このスロットが空と認識されます。
またこのItemSlotクラスは
internalなクラスであるため、現在作成しているInventoryモジュール外からは参照出来ない設計にします。外部からスロットの中身を知る場合、
ItemBagクラスのインスタンスを通して情報を取得することになります。
ではここで作成した
UpdateItem()メソッドを
ItemBagクラスから呼ぶことを考えます。まず
ItemBagクラスに
AllSlotsという名のプロパティーを作成したことを覚えていますか。この変数は全てのItemSlotオブジェクトを保持する変数で、
ItemBagの
Awake()メソッド内でインスタンス化される
ItemSlotインスタンスが入っています。ここで
GameObjectを保持するのではなく
ItemSlotを保持するよう変更します。変更すべきは二か所で、下記コードの4,18行目です。
ItemBag.cs
/// <summary>
/// 全てのスロットオブジェクト
/// </summary>
private List<ItemSlot> AllSlots { get; } = new();
/// <summary>
/// 現在所持しているアイテムの情報
/// </summary>
private ItemBagData Data { get; set; } = new();
void Awake()
{
for (int i = 0; i < slotNumber; i++)
{
//slotNumber の数だけスロットを生成し、ItemBagの子オブジェクトとして配置する
var slot = Instantiate(slotPrefab, this.transform, false);
AllSlots.Add(slot.GetComponent<ItemSlot>());
}
}
4行目ではリストの要素の型を変更しており、18行目ではSlotオブジェクトの
ItemSlotコンポーネントを追加するように修正しています。以上で「どのアイテムをいくつ持っているかのデータ」と「バッグ内に存在する全てのスロットコンポーネント」が手に入りました。次にすることはこの2つを結びつけることです。具体的には、データの変更が行われた際にスロットの表示を変更します。
ItemBagスクリプトに次のコードを挿入してください。
ItemBag.cs
/// <summary>
/// スロットの表示と所持アイテムの情報を一致させる
/// </summary>
private void UpdateItem()
{
// プロジェクトに存在する全アイテム
ItemBase[] allItems = Resources.LoadAll<ItemBase>("");
for (int i = 0; i < Data.Ids.Count; i++)
{
// 追加したいアイテムのid
int itemId = Data.Ids[i];
// 全アイテムからitemIdをもつアイテムを検索する
// ※ 後に修正
ItemBase addingItem = Array.Find(allItems, item=>item.UniqueId == itemId);
// アイテムを表示
AllSlots[i].UpdateItem(addingItem, Data.Qty[i]);
}
for (int i = Data.Ids.Count; i < slotNumber; i++)
{
// 残りは空
AllSlots[i].UpdateItem(null, -1);
}
}
このメソッドで行っていることは大きく二つで、①持っているアイテム分だけスロットを埋め、②残りを空にすることです。簡単に中身を見ていきましょう。
7行目でプロジェクトに存在する全てのアイテム(
ItemBaseクラスのインスタンスファイル)を検索します。前回リンゴのアイテムを生成しInventory/Items/Resourcesに保存しましたが、これが
ItemBaseクラスのインスタンスファイルです。次に
for文があり、ここで所持アイテム数分だけループを回します。12行目で表示したいアイテムのidを確認し、16行目でこのidに一致するアイテムを検索。最後に(先ほど作った)
ItemSlotの
UpdateItem()メソッドを使用します。
2つめの
for文では、余ったスロットすべてを空にします。
UpdateItem()メソッドは、データの変更の度、忘れずに呼ぶ必要があります(Rxを用いた設計ではこのような注意を払う必要はなくなるが、ここでは説明しない)。以下のようにコードの変更を行ってください。
ItemBag.cs
void Awake()
{
for (int i = 0; i < slotNumber; i++)
{
//slotNumber の数だけスロットを生成し、ItemBagの子オブジェクトとして配置する
var slot = Instantiate(slotPrefab, this.transform, false);
AllSlots.Add(slot.GetComponent<ItemSlot>());
}
UpdateItem();
}
ItemBag.cs
/// <summary>
/// アイテムをバッグに追加する
/// </summary>
/// <param name="itemBase">追加したいアイテムのID</param>
/// <param name="number">追加したい個数</param>
/// <returns>バッグへの追加に成功したか</returns>
public bool AddItem(int itemId, int number)
{
if (!Data.Ids.Contains(itemId) && Data.Ids.Count == slotNumber)
{
// スロットが埋まっている状態では、未所持アイテムの追加は出来ない
return false;
}
// アイテムをバッグに追加する
Data.Add(itemId, number);
UpdateItem();
return true;
}
では動作確認をします。前回
InventoryTestクラスに動作確認用のコードを書きましたが、その中の
Start()メソッドを以下のように修正してください
InventoryTest.cs
void Start()
{
Debug.Log(1 + "番目のアイテム:" + bag.AddItem(1, 1));
//カバンに入っているアイテムのデータを表示
Debug.Log(bag.ToJson());
}
全てのスクリプトが保存されていることを確認し、Unityに戻ります。ItemSlotプレハブを開き、インスペクター上からItemSlot.Iconの登録を行ってください。

ゲームを実行すると、1つめのスロットにアイテムが表示されるはずです。
今回は以上です。
ここまでの状況は
Github から確認できます。