【Unity】UIをドラッグアンドドロップして並べ替える

投稿者: | 2021-01-31

アイテム画像をドラッグアンドドロップして並べ替えてみました。

カーソルが乗っているUIのオブジェクトを取得して、アイテム画像をクリックしている間は画像がマウスカーソルに付いてくるようにします。アイテム画像にはタグとスクリプトが付いていて、カーソルからのレイが当たるようにRaycast Targetのチェックが入っています。

Goods currentImage;
Canvas currentCanvas;
bool isDragging;
Transform previousParent;
Transform currentParent;
Rect currentPixelRect;
Vector2 referenceResolution;

//...
void Update()
{

    // ...

    // メニューが開いているとき
    if (opened)
    {
        List<RaycastResult> results = new List<RaycastResult>();

        pointer.position = Input.mousePosition;
        EventSystem.current.RaycastAll(pointer, results);

        if (results.Count == 0)
        {
            // 何もないところをクリックしたらメニューを閉じる
            if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)))
            {
                opened = false;
                DestroyDropdown();
                CloseAllCanvas();
                ReleasePlayer();

            }
            // 何も無い場所で放したとき
            else if (Input.GetMouseButtonUp(0))
            {
                // もとに戻す
                DropFail();
            }
        }

        foreach (RaycastResult target in results)
        {
            // アイテム画像の時
            if (target.gameObject.tag == "ItemImage")
            {

                Goods goods = target.gameObject.GetComponent<Goods>();
                if (goods != null)
                {

                    // クリック
                    if (Input.GetMouseButtonDown(0))
                    {
                        // 掴んでいる状態にする
                        isDragging = true;

                        // アイテム画像のスクリプトを記憶
                        currentImage = goods;

                        // 掴んでいる画像にレイが当たらないようにする
                        currentImage.SetRaycastTarget(false);

                        // 親オブジェクトを記憶
                        previousParent = goods.transform.parent;

                        // ルートのCanvasを記憶
                        currentParent = goods.transform.root;

                        // Reference Resolutionを記憶
                        referenceResolution = currentParent.GetComponent<CanvasScaler>().referenceResolution;

                        // 画面サイズを記憶
                        currentPixelRect = currentParent.GetComponent<Canvas>().pixelRect;

                        // アイテム画像をCanvasの子にする
                        goods.transform.parent = currentParent;

                    }
                    // アイテム画像をドラッグ中に他のアイテム画像の上でクリックを放した時
                    else if (Input.GetMouseButtonUp(0) && isDragging)
                    {
                        // もとに戻す
                        DropFail();

                        // 順番を変える
                        currentImage.transform.SetSiblingIndex(goods.transform.GetSiblingIndex());
                    }
                }
            }
            else
            {
                // アイテム画像以外の上で放した時
                if (Input.GetMouseButtonUp(0) && isDragging)
                {
                    // もとに戻す
                    DropFail();
                }
            }
        }            
    }
}

アイテムが画像を左クリックすると、そのアイテム画像のスクリプトや親、ルートのトランスフォームを記憶しておきます。親はスクロールビューのContentで、ルートはCanvasです。

画像の位置を変えるときに使うので、CanvasのreferenceResolutionやpixelRectの値も記憶します。そして、アイテム画像の親をContentからCanvasに変えます。

そして、掴んでいる状態の時はアイテム画像の位置を変えてマウスカーソルに追従させます。

// 掴んでいるとき
if(isDragging)
{
    if(currentImage != null)
    {
        Vector3 pos = Input.mousePosition;         
        pos.y -= currentPixelRect.height;
        pos *= referenceResolution.x / currentPixelRect.width;

        // 位置を設定
        currentImage.SetPosition(pos);
    }
}

CanvasはオーバーレイになっているのでpixelRectはスクリーンサイズです。また、UI Scale ModeをScale With Screen Sizeにしているので、スクリーンサイズを変えても、サイズが常にReference Resolutionの値であるかのようにUI要素が伸縮します。

つまり、画面の横サイズが400だとしたら、マウスが画面の右端にあるときのX座標は400ですが、右端のX座標は800であるように画像などが配置されるので、その位置に画像を移動しても、画像のアンカーが左端の場合は、画像はまだ真ん中あたりにあります。

なので、この場合は画像位置を 800 / 400 = 2倍します。つまりreferenceResolution / pixelRect をかけます。アイテム画像のアンカーは左上ですが、マウス位置の値は左下が中心なので、その前にスクリーンの高さをマウス位置から引いています。

アイテム画像のスクリプトのメソッドで画像の位置を変えています。

// 表示位置をセット
public void SetPosition(Vector3 position)
{
    RectTransform rt = GetComponent<RectTransform>();
    Vector2 pos = rt.anchoredPosition;
    pos.x = position.x;
    pos.y = position.y;
    rt.anchoredPosition = pos;
}

Image image;

private void Awake()
{
    image = GetComponent<Image>();
}

public void SetRaycastTarget(bool check)
{
    image.raycastTarget = check;
}

また、ドラッグするときにRaycast Targetのチェックを切っています。そうしないと、掴んでいる間はカーソルが掴んでいるアイテム画像の上にあるので、常にアイテム画像の上で放したときの処理が実行されます。

何もないところやアイテム画像以外の上で放したとき、掴んでいる最中にメニューを閉じたときなどには、記憶しておいた元の親の子オブジェクトに戻して、Raycast Targetのチェックを入れ直します。

void DropFail()
{
    // 掴んでいない状態にする
    isDragging = false;

    // 元の親の子に戻す
    currentImage.transform.parent = previousParent;

    // レイに当たるようにする
    currentImage.SetRaycastTarget(true);
}

他のアイテム画像の上にドロップしたときは、元に戻した後に順番を変えて、掴んでいた画像をその画像の前に移動します。

// アイテム画像をドラッグ中に他のアイテム画像の上でクリックを放した時
else if (Input.GetMouseButtonUp(0) && isDragging)
{
    // もとに戻す
    DropFail();

    // 順番を変える
    currentImage.transform.SetSiblingIndex(goods.transform.GetSiblingIndex());
}

これはTransform.SetSiblingIndexメソッドで簡単に行なえます。これでアイテム画像をドラッグアンドドロップで移動して並び替えられました。

一人称ステルスホラーゲームをitch.ioで公開しました。

https://nekoromorph.itch.io/hatch

コメントを残す

メールアドレスが公開されることはありません。