しっぽを追いかけて

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

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

Unity でドロップ位置に応じて UI を並び替える

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

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

前回の UI のドラッグ移動の続き

2つのドロップエリア間の移動はできたが、どの位置にドロップしても必ず最後の右端に挿入されるのが気に入らない

というわけで、ドロップ位置によって挿入位置が入れ替わるようにしてみた

挿入位置の入れ替え

Hierarchy は一切手を付けず 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;
        }

        // 挿入位置を x 座標から計算
        var index = this.area.childCount;
        for (var i = 0; i < this.area.childCount; i++)
        {
            if (this.self.position.x < this.area.GetChild(i).position.x)
            {
                index = i;
                if (this.self.transform.GetSiblingIndex() < index)
                    index--;

                break;
            }
        }
        
        this.self.SetParent(this.area);
        this.self.SetSiblingIndex(index);

        // 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);
    }
}

変更したのは OnEndDrag() の「挿入位置を x 座標から計算」のところあたり

ドロップエリアの子オブジェクトの x 座標を順に見ていって、ちょうといい位置に挿入されるように SetSiblingIndex() に指定するインデックスを調整している感じ

お試し実行

ドロップ位置で並び替え

よしよし並び替えできた