1. 드래그 앤 드롭 Drag and Drop
가끔, 사용자의 편의성을 고려하지 않은 게임이나 프로그램을 사용하다 보면, 불편함을 느끼는 경우가 있다. 유니티는 사용자에게 편의를 제공할 수 있는 이벤트를 구현하기 위한 다양한 인터페이스를 제공하고 있다. (링크 참조)
그중 이벤트 인터페이스를 활용하여 구현할 수 있는 UI의 대표적 기능 중 하나인 드래그앤드롭이 있다. 게임에서 많이 사용하는 편의 기능이다.
드래그 앤 드롭은 인벤토리 시스템, 퀵슬롯 시스템 등에서 대부분 제공하고 있다. 많은 프로그램이나 게임을 접한 사용자들은 일상적으로 당연하게 생각하는 기능으로 거의 필수적으로 여러 곳에서 사용된다.
2. 드래그 앤 드롭 인터페이스
유니티에서 드래그앤 드롭은 해당 UI에 스크립트를 추가하고, 이벤트 인터페이스를 등록하여 구현한다.
이벤트 인터페이스를 사용하기 위해서는 이벤트 시스템 네임 스페이스를 추가해야 한다.
using UnityEngine.EventSystems;
우선, 알아야할 기본적인 사항이 있다.
첫 번째, 드래그 앤 드롭에서 드래그하는 오브젝트와 드롭하는 오브젝트는 다르다는 것이다. A에서 드래그가 발생하고, B에서 드롭이 발생한다.
두 번째, 드래그하는 오브젝트와 드롭하는 오브젝트가 사용하는 스크립트가 동일할 수도 있으며, 스크립트가 다를 수도 있다는 점이다. 예로 인벤토리에서 퀵 슬롯으로 드래그 앤 드롭을 하는 경우와, 퀵 슬롯에서 퀵 슬롯으로 드래그 앤 드롭을 하는 경우이다.
드래그하는 오브젝트에는 다음의 세 가지 인터페이스를 구현한다.
드래그가 시작할 때, 드래그 중일 때, 드래그가 종료되면 발생하는 인터페이스이다.
using UnityEngine;
using UnityEngine.EventSystems;
public class DragExample : MonoBehaviour,IDragHandler,IBeginDragHandler, IEndDragHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log("Start");
//throw new System.NotImplementedException();
}
public void OnDrag(PointerEventData eventData)
{
Debug.Log("Draging");
//throw new System.NotImplementedException();
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log("EndDrag");
// throw new System.NotImplementedException();
}
}
드롭이 발생하는 오브젝트에는 다음의 인터페이스를 구현한다.
using UnityEngine;
using UnityEngine.EventSystems;
public class DropExample : MonoBehaviour, IDropHandler
{
public void OnDrop(PointerEventData eventData)
{
Debug.Log("Drop")
// throw new System.NotImplementedException();
}
}
3. 드래그 앤 드롭 구현
본 글에서는 동일한 스크립트를 사용하는 슬롯 간의 드래그앤드롭을 구현한다.
3.1 UI 구성
우선 아래의 이미지와 같은 간단한 UI를 구현한다.
3.1.1 슬롯( Slot ) 구성
드래그 앤 드롭을 구현 할 슬롯을 구성한다. Image를 가지는 메인 슬롯과 하위 요소로 Image 컴포넌트를 생성한다.
Slot을 프리팹화하고 10개의 슬롯을 만든다.
3.1.2 Slot 정렬
Slot의 부모 패널(Panel)에 GridRayoutGroup을 추가한다.
3.1.2 Container UI 구성
부모 패널과 동일한 계층구조에 Image 컴포넌트를 가지는 컨테이너( Container )를 만든다. 컨테이너는 부모 패널보다 늦게 그려지도록 계층 구조를 구성한다. 이러한 구성을 하게 되면, 추가적으로 SetSiblingIndex(), 부모 변경 등과 같은 추가적인 노력없이 슬롯들 위에 위치하게 된다.
※ 컨테이너를 만들고, 컨테이너의 이미지의 Raycast Target 속성을 해제(√ 해제)한다. 이는 컨테이너의 이미지에 의해 슬롯이 가려져 마우스 이벤트를 막게 되는 결과를 만든다. Raycast Target 속성을 해제하지 않으면, Drop 이벤트가 발생하지 않는다.
컨테이너는 드래그 중인 오브젝트의 정보를 가지는 오브젝트이다.
드래그 앤 드롭 UI는 아래 이미지와 같다.
3.2 스크립트
본 글에서는 Data를 만들지 않았으며, 대신 이미지 컴포넌트의 Sprite로 Null 체크를 하고, Sprite 이미지를 사용하여 드래그 앤 드롭을 구성한다.
본 글의 내용을 기반으로 프로젝트에 맞는 Data를 구성하고 추가하여 사용하면 된다.
3.2.1 DragAndDropContainer
드래그 앤 드롭 컨테이너는 드래그 중인 상태를 보여주며, 슬롯에 있는 데이터( ex: 인벤토리의 아이템이나 스킬) 정보를
담는 역할을 한다. 앞서 언급하였듯이, 드래그 오브젝트와 드롭 오브젝트는 다르기 때문에, 두 오브젝트 간의 데이터를 주고받기 위한 공유 데이터가 필요하다.
드래그 오브젝트에서 드롭 오브젝트로 컨테이너를 넘겨주며, 드롭 오브젝트는 컨테이너에 담긴 데이터를 받고, 다시 드롭 오브젝트가 가지고 있던 데이터를 컨테이너 오브젝트를 사용해 드래그 오브젝트로 넘겨준다.
컨테이너의 스크립트는 매우 간단하다.
이미지 컴포넌트는 이동 중인 오브젝트를 시각적으로 보여주는 데 사용한다. 처음 활성화되면 보이지 않도록 구성한다.
using UnityEngine;
using UnityEngine.UI;
public class DragAndDropContainer : MonoBehaviour
{
public Image image;
// public MyData data;
// Start is called before the first frame update
void Start()
{
gameObject.SetActive(false);
}
}
3.2.2 DragAndDropExample
UI 구성이 완료가 되었으면, 스크립트를 생성하여 DragAndDropExample 클래스를 만든다.
생성한 스크립트를 Slot에 추가한다.
드래그 오브젝트의 OnBeginDrag(PointEventerData eventData)에서는 해당 슬롯에 데이터가 있다면, 드래그할 데이터를 컨테이너에 담는다.
컨테이너 오브젝트를 활성화하고 드래그 중임을 설정한다.
데이터가 없다면, 어떠한 동작도 하지 않도록 예외 설정을 한다.
public void OnBeginDrag(PointerEventData eventData)
{
if (data.sprite == null)
{
return;
}
// Activate Container
dragAndDropContainer.gameObject.SetActive(true);
// Set Data
dragAndDropContainer.image.sprite = data.sprite;
isDragging = true;
}
드래그 오브젝트의 OnDrag(PointerEventData eventData)에서는 마우스의 위치에 드래그 중인 아이템의 이미지를 보여주며, 마우스의 이동에 따라 이미지를 표시한다.
PointerEventData는 이벤트와 관계된 데이터들을 가지고 있으며, 데이터 중 이벤트가 발생한 위치(마우스의 포지션)를 포함하고 있다.
public void OnDrag(PointerEventData eventData)
{
if (isDragging)
{
dragAndDropContainer.transform.position = eventData.position;
}
}
드롭 오브젝트의 OnDrop(PointerEventDat eventData)에서는 컨테이너의 데이터를 확인하고, 컨테이너에 데이터가 NULL이 아니라면, 자신의 데이터를 새로운 객체에 임시 저장을 한다. 그리고 자신의 데이터를 컨테이너의 데이터로 정보를 변경한다. 이후 임시 저장한 데이터를 컨테이너에 담아, 드래그 오브젝트로 전송한다.
가지고 있던 데이터를 임시 저장하지 않고 변경을 하게 되면, 가지고 있던 데이터(참조 값)가 새로운 데이터로 변경되므로, 드래그 오브젝트로 전송을 할 수 없다.
OnDrop은 OnEndDrag보다 먼저 발생한다.
public void OnDrop(PointerEventData eventData)
{
if (dragAndDropContainer.image.sprite != null)
{
// keep data instance for swap
Sprite tempSprite = data.sprite;
// set data from drag object on Container
data.sprite = dragAndDropContainer.image.sprite;
// put data from drop object to Container.
dragAndDropContainer.image.sprite = tempSprite;
}
else
{
dragAndDropContainer.image.sprite = null;
}
}
드래그 오브젝트의 OnEndDrag(PointerEventData eventData)에서는 컨테이너 데이터를 확인하고, 데이터가 없다면(NULL 확인) 자신의 데이터를 초기화한다. 컨테이너의 데이터가 있다면, 컨테이너의 데이터로 자신의 데이터를 수정한다.
public void OnEndDrag(PointerEventData eventData)
{
if (isDragging)
{
if (dragAndDropContainer.image.sprite != null)
{
// set data from dropped object
data.sprite = dragAndDropContainer.image.sprite;
}
else
{
// Clear Data
data.sprite = null;
}
}
isDragging = false;
// Reset Contatiner
dragAndDropContainer.image.sprite = null;
dragAndDropContainer.gameObject.SetActive(false);
}
DragAndDropExample full script
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class DragAndDropExample : MonoBehaviour,IDragHandler,IBeginDragHandler ,IDropHandler, IEndDragHandler
{
public Image data;
public DragAndDropContainer dragAndDropContainer;
bool isDragging = false;
// 드래그 오브젝트에서 발생
public void OnBeginDrag(PointerEventData eventData)
{
if (data.sprite == null)
{
return;
}
// Activate Container
dragAndDropContainer.gameObject.SetActive(true);
// Set Data
dragAndDropContainer.image.sprite = data.sprite;
isDragging = true;
}
// 드래그 오브젝트에서 발생
public void OnDrag(PointerEventData eventData)
{
if (isDragging)
{
dragAndDropContainer.transform.position = eventData.position;
}
}
// 드래그 오브젝트에서 발생
public void OnEndDrag(PointerEventData eventData)
{
if (isDragging)
{
if (dragAndDropContainer.image.sprite != null)
{
// set data from dropped object
data.sprite = dragAndDropContainer.image.sprite;
}
else
{
// Clear Data
data.sprite = null;
}
}
isDragging = false;
// Reset Contatiner
dragAndDropContainer.image.sprite = null;
dragAndDropContainer.gameObject.SetActive(false);
}
// 드롭 오브젝트에서 발생
public void OnDrop(PointerEventData eventData)
{
if (dragAndDropContainer.image.sprite != null)
{
// keep data instance for swap
Sprite tempSprite = data.sprite;
// set data from drag object on Container
data.sprite = dragAndDropContainer.image.sprite;
// put data from drop object to Container.
dragAndDropContainer.image.sprite = tempSprite;
}
else
{
dragAndDropContainer.image.sprite = null;
}
}
}
'유니티 > UI(User Interface)' 카테고리의 다른 글
유니티 UI 렉트 트랜스폼 (RectTransform) (0) | 2020.11.24 |
---|---|
유니티 UI 레이아웃 그룹 ( Layout Group) (0) | 2020.11.24 |
유니티 UI 속성 (Scroll bar, Scroll View) (1) | 2020.11.17 |
유니티 UI 속성(Slider, Dropdown, Toggle, Toggle Group) (0) | 2020.11.16 |
유니티 UI 속성 (Image & RawImage) (0) | 2020.11.15 |