インベントリ / スロットクリックの挙動

はじめに

今回の作業は大きく2つです。一つはスロットにアイテム数を表示すること、もう一つはスロットをクリックした際の簡単な挙動です。設計図では、下図の「」印を付けた箇所に相当します。



準備

本題に入る前に、素材を1つダウンロード(又は自前で用意)していただく必要があります。ここではアイテムスロットをクリックしたときの挙動を実装していくのですが、その際に表示するメニューの背景とボタンを下のボタンからダウンロードし、解凍してください。



中に2つのpngファイルがあるため、これらをUnityの「Inventory/Textures」フォルダに入れて下さい。これら2つを選択し、インスペクター上からTextureTypeをSpriteに変更します。さらにSpriteModeをMultipleに、MeshTypeをFullRect、PixelPerUnitを300に変更します。


変更後は忘れずにApplyボタンをクリックしてください。次に、今インポートしたmenuFrameを選択し、インスペクター上のSpriteEditorボタンをクリックします。表示される画面の左上のSliceメニューを選択し、(特に設定は変えず)Sliceボタンをクリックしてください。


画像をクリックすると、画像の四辺上に小さな緑の四角マークが表示されます。緑マークをドラッグして緑の境界を4つ角の曲線が終わるあたりに移動させてください。間違って青色の境界を移動させる事故が後を絶ちません。言葉での説明が難しいので、下の画像の通りに調節していただければと思います。


忘れずにApplyボタンをクリックしてSprite Editorを閉じます。menuButtonの画像でも同様の操作を行い、下図のように編集してください。図を見ても分かる通り、ボタンの場合は上下の緑マークをいじる必要はありません。しつこいようですが、青色の境界は画像の境界から動かさないでください(下図の通り)。


操作が終わったら、Applyボタンを押してSprite Editorを閉じてください。

準備は以上です。



スロットにアイテム数を表示する

今回は、まずアイテムスロットに個数を表示するテキストを追加します。まずはItemSlotクラス内にTextMeshProUGUIコンポーネントを登録する変数を記述します。
ItemSlot.cs
1/// <summary>
2/// このスロットに入っているアイテムの個数を表示するテキスト
3/// </summary>
4[SerializeField] private TextMeshProUGUI numberText;
5
6/// <summary>
7/// 数量
8/// </summary>
9private int Number { get; set; }
TextMeshProUGUIにエラーが発生する方は、スクリプト先頭にusing TMPro;の記述があることを確認してください。次にUpdateItem()メソッドが呼ばれた際に、テキストの値も更新するように修正しましょう。変更点は緑に着色された行です。
ItemSlot.cs
1/// <summary>
2/// アイテムのアイコンを表示する
3/// </summary>
4/// <param name="item"></param>
5/// <param name="number"></param>
6internal void UpdateItem(ItemBase item, int number)
7{
8    if (number > 0 && item != null)
9    {
10        // アイテムが空ではない場合
11        Item = item;
12        Number = number;
13
14        // アイコンの表示
15        icon.sprite = item.Icon;
16        icon.color = Color.white;
17
18        // 数量の表示
19        numberText.gameObject.SetActive(number > 1);
20        numberText.text = number.ToString();
21    }
22    else
23    {
24        // アイテムが空である場合
25        Item = null;
26        Number = 0;
27        icon.sprite = null;
28        icon.color = new Color(0, 0, 0, 0);
29
30        numberText.gameObject.SetActive(false);
31    }
32}
15,16行目では、アイテムが存在するスロットの挙動を記述しています。アイテムを2個以上保持している際にのみテキストは表示します。
一方空のスロットではテキストを表示しません。



スロットをクリックしたときの動作

次にアイテムスロットをクリックしたときの動作を作成します。

ItemSlotクラスに次のコードを追記してください
ItemSlot.cs
1/// <summary>
2/// スロットがクリックされた際に実行するメソッド
3/// [ 引数 ]
4/// ItemBase : スロットに入っているアイテム
5/// int : アイテムの個数
6/// GameObject : このスロットのオブジェクト
7/// </summary>
8private Action<ItemBase,int,GameObject> OnClickCallback { get; set; }
コメントの通りですが、この変数はスロットがクリックされた際に実行するコールバックメソッドを保持します。このメソッドの具体的機能はインベントリの種類によって様々であり、スロットが決定できる内容ではありません。そのためこのメソッドは「ItemBagがItemSlotを生成する際に受け取る」ことにします。ItemSlotクラスに次のInitialize()メソッドを追加してください。
ItemSlot.cs
1/// <summary>
2/// このクラスのインスタンスが生成された際に呼ぶメソッド
3/// </summary>
4/// <param name="onClickCallback"></param>
5internal void Initialize(Action<ItemBase, int, GameObject> onClickCallback)
6{
7    OnClickCallback = onClickCallback;
8}
受け取ったメソッドを変数にしまうだけの、簡単な内容です。忘れないうちにItemBagクラス内でこのメソッドを使用しておきます。ItemBagAwake()メソッドを次のように変更してください。
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行目)。 ではItemSlotクラスに戻り、スロットがクリックされた際にコールバックメソッドを呼びましょう。ItemSlotクラスに次のメソッドを記述してください。
ItemSlot.cs
1/// <summary>
2/// スロットがクリックされたときに呼ばれるメソッド
3/// </summary>
4public void OnClicked()
5{
6    //このスロットにアイテムが存在している場合
7    if (Item != null)
8    {
9        // コールバックメソッドを実行
10        OnClickCallback(Item, Number, this.gameObject);
11    }
12}
このスロットにアイテムが存在している場合に限り、クリック時にコールバックメソッドを呼びます。

次にUnityで数量表示を作ります。ItemSlotプレハブを編集します。ItemSlotオブジェクト直下に「TextMeshProUGUI」を追加し、numberという名を付けてください。
仮にTextMeshProのオブジェクトを追加した際に以下のようなメニューが出てきた場合「Import TMP Essentials」を選択してください。

ここまで進めると、プレハブは次の図のようになります。


ここでプレハブが表示されずに、赤いバツ印が表示されている場合、ItemSlotオブジェクトの幅と高さをともに100にしてください。



ではnumberオブジェクトの調節を行いましょう。アイテムの数はスロットの右下に配置しようと思います。作業の順番としては、先にTextMeshProの設定から行うとやりやすいです。フォントサイズとフォントの色を調節し、Alignmentを右寄せにします。テキストの内容は数字にしておくと良いです。この次に位置を定めます。変更点は下図の赤枠内です。設定順に注意してください。


ここで追加したnumberオブジェクトをItemSlotコンポーネントの変数に登録します。

次にスロットをクリックできるよう、ItemSlotオブジェクトにButtonコンポーネントを追加します。AddComponentボタンをクリックし、コンポーネント名で検索します(下図)。


ボタンが押された際にItemSlotクラスのOnClick()メソッドが呼ばれるように設定します。ButtonコンポーネントのOnClickの欄にある「+」ボタンを1度押し、要素を一つ追加します。次にItemSlotコンポーネントを参照先設定欄にD&Dし、呼び出すメソッドとしてOnClick()メソッドを選択します。


動作確認をするためInventoryTestクラスのStart()メソッドを次のように変更してください。
InventoryTest
1void Start()
2{
3    // id1のアイテムを2つ追加
4    bag.AddItem(1, 2);
5}
全てのスクリプトが保存されていることを確認し、Unityでゲームを実行してみてください。スロットをクリックするとログに「クリックされました」と表示されるはずです。


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