スクロールビューのアイテムをドラッグ&ドロップできるようにする

この記事は2017年06月18日にqiitaに投稿した内容です。

環境

Unity5.6.1f1

概要

水平スクロールビューのアイテム(の子供のイメージ等)をドラッグ&ドロップできるようにします

プログラム

シーンにはドロップ対象のエリアとなるImage等のGameObjectを配置しておく必要があります ドロップ時にEventSystem.current.RaycastAllでドロップ対象のGameObjectを判定しています

Assets/DragItem.cs

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

public class DragItem : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public enum EventState
    {
        None,
        BeginDrag,
        Drag,
        Drop,
    }
    public Action<DragItem,Transform> onUpdateState = null;
    public PointerEventData pointerEventData { get{ return _pointerEventData; } }
    public EventState eventState { get{ return _eventState; } }
    public Transform originalParent { get{ return _originalParent; } }

    [SerializeField] private Transform _draggingParent = null; //ドラッグ中はここの子になる
    [SerializeField] private float _upBasedBeginDragDegree = 70.0f; //ドラッグ選択判定の角度
    [SerializeField] private float _upBasedBeginDragDistance = 10.0f; //ドラッグ選択判定の距離

    private ScrollRect _scrollRectParent = null;
    private Transform _originalParent = null;
    private Vector3 _originalLocalPos;
    private PointerEventData _upDragPointerEventData = null;
    private PointerEventData _pointerEventData = null;
    private EventState _eventState = EventState.None;

    private void Start()
    {
        _scrollRectParent = GetComponentInParent<ScrollRect>();
    }

    void IPointerDownHandler.OnPointerDown( PointerEventData eventData )
    {
        _upDragPointerEventData = eventData;
    }

    void IPointerUpHandler.OnPointerUp( PointerEventData eventData )
    {
        if( _upDragPointerEventData != null && _upDragPointerEventData.pointerId == eventData.pointerId )
            _upDragPointerEventData = null;
        EndDrag( eventData );
    }

    void IBeginDragHandler.OnBeginDrag( PointerEventData eventData )
    {
        if( _scrollRectParent != null )
            _scrollRectParent.OnBeginDrag( eventData );
    }

    void IDragHandler.OnDrag( PointerEventData eventData )
    {
        if( _upDragPointerEventData != null && _upDragPointerEventData.pointerId == eventData.pointerId )
        {
            var vec = _upDragPointerEventData.position - _upDragPointerEventData.pressPosition;
            var lenSq = vec.sqrMagnitude;
            var dot = Vector2.Dot( vec.normalized, Vector2.up );
            var ac = Mathf.Acos( dot ) * Mathf.Rad2Deg;
            if( ac < _upBasedBeginDragDegree )
            {
                if( lenSq > ( _upBasedBeginDragDistance * _upBasedBeginDragDistance ) )
                {
                    StartDrag( _upDragPointerEventData );
                    _upDragPointerEventData = null;
                }
            }
        }

        var isUpdate = UpdateDrag( eventData );
        if( isUpdate == false )
        {
            if( _scrollRectParent != null )
                _scrollRectParent.OnDrag( eventData );
        }
    }

    void IEndDragHandler.OnEndDrag( PointerEventData eventData )
    {
        if( _scrollRectParent != null )
            _scrollRectParent.OnEndDrag( eventData );
    }

    public bool Reset()
    {
        _eventState = EventState.None;
        transform.SetParent( _originalParent, false );
        transform.localPosition = _originalLocalPos;
        return true;
    }

    private void StartDrag( PointerEventData eventData )
    {
        _eventState = EventState.BeginDrag;
        _pointerEventData = eventData;
        _originalParent = transform.parent;
        _originalLocalPos = transform.localPosition;
        transform.SetParent( _draggingParent, true );

        if( onUpdateState != null )
            onUpdateState.Invoke( this, null );
    }

    private bool UpdateDrag( PointerEventData eventData )
    {
        if( _pointerEventData == null || _pointerEventData.pointerId != eventData.pointerId )
            return false;

        _eventState = EventState.Drag;
//      transform.SetParent( _draggingParent );
//      transform.position = eventData.pressEventCamera.ScreenToWorldPoint( eventData.position );
        transform.SetParent( _draggingParent, false );
        Vector2 pos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle( _draggingParent as RectTransform, eventData.position, eventData.pressEventCamera, out pos );
        transform.localPosition = new Vector3( pos.x, pos.y, transform.position.z );

        var local = transform.localPosition;
        local.z = 0.0f;
        transform.localPosition = local;

        if( onUpdateState != null )
            onUpdateState.Invoke( this, null );
        return true;
    }

    private void EndDrag( PointerEventData eventData )
    {
        if( _pointerEventData == null || _pointerEventData.pointerId != eventData.pointerId )
            return;

        _eventState = EventState.Drop;
        var pointer = new PointerEventData( EventSystem.current );
        pointer.position = eventData.position;
        var results = new List<RaycastResult>();
        EventSystem.current.RaycastAll( pointer, results );
        Transform dropTarget = null;
        if( results.Count >= 2 )
        {
            for( int i = 0; i < results.Count; i++ )
            {
                var result = results[ i ];
                if( gameObject == result.gameObject )
                {
                    var targetIndex = i + 1;
                    if( targetIndex < results.Count )
                    {
                        Debug.Log( "targetIndex:" + targetIndex );
                        dropTarget = results[ targetIndex ].gameObject.transform;
                        break;
                    }
                    break;
                }
            }
        }

        if( onUpdateState != null )
            onUpdateState.Invoke( this, dropTarget );
        _pointerEventData = null;
    }
}

Assets/TestDragItem.cs

using UnityEngine;
using UnityEngine.UI;

public class DragItemTest : MonoBehaviour
{
    [SerializeField] private ScrollRect _scrollRect = null;
    [SerializeField] private GameObject _itemPrefab = null;

    private void Start()
    {
        var content = _scrollRect.content;
        for( int i = 0; i < 20; i++ )
        {
            var itemObj = Instantiate<GameObject>( _itemPrefab );
            itemObj.SetActive( true );

            itemObj.name = i.ToString();
            var text = itemObj.GetComponentInChildren<Text>();
            text.text = "Drag&Drop" + i;

            //itemObjの子供にImageのGameObjectがあり、そこにDragItemもセットされている想定
            var dragItem = itemObj.GetComponentInChildren<DragItem>();
            dragItem.onUpdateState = OnDragItemUpdateState;

            itemObj.transform.SetParent( content, false );
        }
        _itemPrefab.gameObject.SetActive( false );
    }

    private void OnDragItemUpdateState( DragItem dragItem, Transform dropTarget )
    {
        var state = dragItem.eventState;
        switch( state )
        {
            case DragItem.EventState.BeginDrag:
                Debug.Log( "BeginDrag:" + dragItem.name );
                break;
            case DragItem.EventState.Drag:
                break;
            case DragItem.EventState.Drop:
                if( dropTarget != null )
                {
                    Debug.Log( "Drop:" + dragItem.originalParent.name + " dropTarget:" + dropTarget.name );
                    var content = _scrollRect.content;
                    Destroy( dragItem.originalParent.gameObject );
                    Destroy( dragItem.gameObject );
                }
                else
                {
                    Debug.LogWarning( "Item None" );
                }
                break;
        }
    }

}