simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

CubeWalk:マウス操作でインベントリのアイテムを移動できる機能

概要

半月前に構想した内容がこちら
simplestar-tech.hatenablog.com

前々回、前回で進んだ内容が以下
1.R Ctrl でキューブ、SideA, SideB キューブと配置形状が切り替わる
2.オンラインのインベントリ情報を受け取って表示する機能

今回は 3/7 ステップとして
3.マウス操作でインベントリのアイテムを移動できる機能

を作ります。

構想した手順

スロットをクリックして
アイテムがあればアイテムをホールド、なければ何も起きない

クリックを取得

まずは、スロット側にクリックイベントで何か関数呼び出しできるようにしてみます

using UnityEngine;
using UnityEngine.UI;

public class ItemSlot : MonoBehaviour
{
    void Start()
    {
        this.button = GetComponent<Button>();
        this.button.onClick.AddListener(this.OnClick);
    }

    public void OnClick()
    {
        Debug.Log("clicked " + this.gameObject.name);
    }

    Button button;
}

Image UI に Button コンポーネントをつける必要あり

ところでキューブが配置されても、呼ばれる?→はい、大丈夫

ホールドとは

クリックしたスロットにアイテムがあるか見て
ある場合は、そのアイテムをホールド
特別な入れ物にスロットのアイテムを全部入れて、元のスロットを空にする

実装のイメージがわいたのですが
slot の uGUI の slot に Button コンポーネントを静的にアタッチしておき、名前で解決している部分で、OnClickイベントハンドラをセットします。
そうすれば、すべてのスロットのクリックを取得することができ、スロットが空なのかどうか判定できる要素にアクセスできます。

以下が、具現化したコード 期待通り動きました。

        // Start is called before the first frame update
        void Start()
        {
            // パネルに並ぶslot名からimage配列を作成
            foreach (Transform slotImage in this.panelInventory)
            {
                var match = Regex.Match(slotImage.name, "slot([0-9]{2})");
                if(match.Success && 2 == match.Groups.Count)
                {
                    if(int.TryParse(match.Groups[1].Value, out int slotIndex))
                    {
                        this.slotTransforms[slotIndex] = slotImage;

                        // クリックイベントハンドラを登録
                        var button = slotImage.GetComponent<Button>();
                        if (null != button)
                        {
                            button.onClick.AddListener(() => { this.OnClickSlot(slotIndex); });
                        }
                    }
                }
            }
        }

        private void OnClickSlot(int slotIndex)
        {
            Debug.Log("clicked " + slotIndex);
        }

スロットが空とは?
null でしょうか? → はい

このように見分けます。

        private void OnClickSlot(int slotIndex)
        {
            Debug.Log("clicked " + slotIndex);
            var viewItem = this.viewItems[slotIndex];
            if (null != viewItem && 0 < viewItem.count)
            {
                Debug.Log("itemName " + viewItem.item.DisplayName);
            }
        }

ホルダー

ホールドするときは特別なホルダーと呼ばれる ItemView 保管庫に ItemView が格納されます
それだけ

アイテムがあるスロットをクリックしたときに発生し
ホルダーにアイテムがあるときは、交換するようにホールドします

ホルダーは具体的には ItemView クラス変数
nullable です

ビューを更新するには?

render 関数を作って、データに更新があったら render するというのを考えたのですが、それだとキューブメッシュ作成が 81 回も行われてしまうので
変化が生まれるクリックしたスロットにのみ、キューブオブジェクトを除去したり、生成して設置したりすることにします。

f:id:simplestar_tech:20191212221921p:plain
クリックによってホールド、配置が行えるようになりました

デバッグしていて思ったことは、サーバーから情報を取得したときにアイテムslotの状況が残ってしまい、図のようにローカルでアイテムが複製されてしまいました。
サーバーから情報を引いてくるときにクリアします。

ホールド中はアイテムがカーソルに付随

ホールドしたアイテムが null じゃないなら

f:id:simplestar_tech:20191213002125p:plain
できた

こんな感じのコードでマウスカーソルについてくる形です。

        private void OnClickSlot(int slotIndex)
        {
            Debug.Log("clicked " + slotIndex);
            var item = this.items[slotIndex];
            this.items[slotIndex] = this.itemHolder;
            this.itemHolder = item;

            // スロットの CubeObject を再作成
            Destroy(this.slotCubes[slotIndex]);
            var cubeObject = this.CreateSlotCubeObject(this.items[slotIndex]);
            if (null != cubeObject)
            {
                cubeObject.transform.SetParent(this.slotTransforms[slotIndex]);
                cubeObject.transform.localPosition = Vector3.zero;
                cubeObject.transform.localScale = new Vector3(50, 50, 1);
            }
            this.slotCubes[slotIndex] = cubeObject;

            Destroy(this.holderCube);
            this.holderCube = CreateSlotCubeObject(this.itemHolder);
            if (null != this.holderCube)
            {
                this.holderCube.transform.SetParent(this.panelInventory);
                this.holderCube.transform.localScale = new Vector3(25, 25, 1);
            }
        }

        void OnGUI()
        {
            Event currentEvent = Event.current;
            Vector2 mousePos = new Vector2();

            // Get the mouse position from Event.
            // Note that the y position from Event is inverted.
            mousePos.x = currentEvent.mousePosition.x;
            mousePos.y = cam.pixelHeight - currentEvent.mousePosition.y;

            var point = cam.ScreenToWorldPoint(new Vector3(mousePos.x + 14, mousePos.y + 14, cam.nearClipPlane + 0.008f));
            if (null != this.holderCube)
            {
                this.holderCube.transform.position = point;
            }
        }

まとめ

アイテムビューを開いたとき、マウスカーソルが出てくるモードがあるので、そのときにクリックして
アイテムがあればアイテムをホールド、なければ何も起きない
ホールドしていた状態でクリックしたとき、スロットだったらスロットに配置する
スロットでなければ…の一歩手前まで動作確認できました。

3.マウス操作でインベントリのアイテムを移動できる機能
が動きました。

これで 3/7 の進捗ですね。
残りも頑張ります