しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Unity で UI のドラッグドロップ領域をつくる

※ これは 2020/11/06 時点の Unity 2020.1.12f1 の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回の UI のマウスドラッグの続き

今度は下記のように、三色の四角 UI を二つの灰色領域間でドラッグドロップで移動させてみたい

二つの領域間をドラッグ移動

まず前回の Item UI をプレハブ化して3色に色分け、その後、この UI をドロップする領域として Area (A)Area (B)GameObject を作る

DropArea オブジェクト

Area (A)Area (B) は配置位置以外全く同じもので、DropArea.csスクリプトと、HorizontalLayoutGroup、背景画像の Image をこんな感じで設定した

DropArea.cs は単なる目印なので下記の通り空っぽでよし

using UnityEngine;

public class DropArea : MonoBehaviour
{
}

あとは前回の Draggable.cs を変更

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;

[RequireComponent(typeof(CanvasGroup))]
public class Draggable : MonoBehaviour
{
    private Transform root;
    private Transform area;
    private Transform self;
    private CanvasGroup canvasGroup = null;

    public void Awake()
    {
        this.self = this.transform;
        this.area = this.self.parent;
        this.root = this.area.parent;
        this.canvasGroup = this.GetComponent<CanvasGroup>();
    }

    public void OnBeginDrag(BaseEventData eventData)
    {
        // ドラッグできるよういったん DropArea の上位に移動する
        this.self.SetParent(this.root);

        // UI 機能を一時的無効化
        this.canvasGroup.blocksRaycasts = false;
    }

    public void OnDrag(BaseEventData eventData)
    {
        this.self.localPosition = GetLocalPosition(((PointerEventData)eventData).position, this.transform);
    }

    private static Vector3 GetLocalPosition(Vector3 position, Transform transform)
    {
        // 画面上の座標 (Screen Point) を RectTransform 上のローカル座標に変換
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            transform.parent.GetComponent<RectTransform>(),
            position,
            Camera.main,
            out var result);
        return new Vector3(result.x, result.y, 0);
    }

    public void OnEndDrag(BaseEventData eventData)
    {
        // ドロップ地点に DropAra があったらそこに入れる
        var dropArea = GetRaycastArea((PointerEventData)eventData);
        if (dropArea != null)
        {
            this.area = dropArea.transform;
        }
        this.self.SetParent(this.area);

        // UI 機能を復元
        this.canvasGroup.blocksRaycasts = true;
    }

    /// <summary>
    /// イベント発生地点の DropArea を取得する
    /// </summary>
    /// <param name="eventData">イベントデータ</param>
    /// <returns>DropArea</returns>
    private static DropArea GetRaycastArea(PointerEventData eventData)
    {
        var results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, results);

        return results.Select(x => x.gameObject.GetComponent<DropArea>())
            .FirstOrDefault(x => x != null);
    }
}

主な変更点は、ドラッグ開始時にいったん DropArea との親子関係を解消し、終了時にドロップ地点にある DropArea に対して親子関係を設定するという感じ

EventSystem.current.RaycastAll() を使えば、マウス位置の重なってる UI を全部取得できるみたいなので、これを利用すれば DropArea を取得できる

さてお試し

ドラッグでドロップ領域間を移動

思い通りにできたっぽい・・・何もないところにドロップしたら戻る