버전:2021.3+
드래그 앤 드롭은 UI 디자인의 흔한 기능입니다. UI 툴킷을 사용하여 커스텀 에디터 창 또는 Unity에서 빌드한 애플리케이션 내부에 드래그 앤 드롭 UI를 만들 수 있습니다. 다음 예시는 커스텀 에디터 창 내에서 드래그 앤 드롭 UI를 만드는 방법을 보여줍니다.
예시에서는 커스텀 에디터 창에서 여러 슬롯과 하나의 오브젝트를 추가합니다. 아래 보이는 대로 오브젝트를 모든 슬롯으로 드래그할 수 있습니다.
이 예시에서 생성한 완성된 파일은 GitHub 저장소에서 확인할 수 있습니다.
이 가이드는 Unity 에디터, UI 툴킷, C# 스크립팅에 익숙한 개발자용입니다.시작하기 전에 먼저 다음을 숙지하십시오.
시작하려면 메뉴에서 기본 커스텀 에디터 창을 생성합니다. UI를 좀더 사용자 친화적으로 만들기 위해 메뉴 이름과 창 제목을 Drag And Drop
으로 변경하고 기본 레이블에 대한 코드를 제거합니다.
템플릿을 사용하여 Unity에서 프로젝트를 생성합니다.
Assets
폴더를 오른쪽 클릭하고 Create > UI Toolkit > Editor Window를 선택합니다.
UI Toolkit Editor Window Creator에서 DragAndDropWindow
를 입력합니다.
Confirm을 클릭합니다. 이렇게 하면 자동으로 다음 3개의 파일이 생성됩니다. DragAndDropWindow.cs
, DragAndDropWindow.uxml
, DragAndDropWindow.uss
.
DragAndDropWindow.cs
의 콘텐츠를 다음으로 교체합니다.
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
public class DragAndDropWindow : EditorWindow
{
[MenuItem("Window/UI Toolkit/Drag And Drop")]
public static void ShowExample()
{
DragAndDropWindow wnd = GetWindow<DragAndDropWindow>();
wnd.titleContent = new GUIContent("Drag And Drop");
}
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// Import UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/DragAndDropWindow.uxml");
VisualElement labelFromUXML = visualTree.Instantiate();
root.Add(labelFromUXML);
// A stylesheet can be added to a VisualElement.
// The style will be applied to the VisualElement and all of its children.
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/DragAndDropWindow.uss");
}
}
다음으로 커스텀 창에 UI 컨트롤을 추가합니다.
slots
이라는 슬롯 한 개에 slot_row1
과 slot_row2
라는 자식 슬롯이 있습니다. 각 행에는 slot1
과 slot2
라는 자식 슬롯이 각각 있습니다.slots
과 같은 수준에 object
라는 오브젝트가 한 개 있습니다. object
는 계층 구조에서 slots
다음에 와야 합니다.UI 컨트롤을 다음과 같이 스타일 지정합니다.
slot1
과 slot2
의 경우 흰색 배경에 둥근 모서리를 가진 80픽셀 X 80픽셀의 정사각형으로 스타일을 지정합니다. 각 행에 두 개의 슬롯이 있는 두 개의 행으로 슬롯을 정렬합니다.object
의 경우 배경 컬러가 검은색인 50픽셀 X 50픽셀 둥근 점으로 스타일을 지정합니다.팁:프로젝트를 더 재미있게 만들기 위해 오브젝트에 배경 이미지를 사용할 수 있습니다.이미지(Pouch.png)는 GitHub 저장소에서 확인할 수 있습니다.
DragAndDropWindow.uxml
의 콘텐츠를 다음으로 교체합니다.
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="DragAndDropWindow.uss" />
<ui:VisualElement name="slots">
<ui:VisualElement name="slot_row1" class="slot_row">
<ui:VisualElement name="slot1" class="slot" />
<ui:VisualElement name="slot2" class="slot" />
</ui:VisualElement>
<ui:VisualElement name="slot_row2" class="slot_row">
<ui:VisualElement name="slot1" class="slot" />
<ui:VisualElement name="slot2" class="slot" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="object" class="object" />
</ui:UXML>
DragAndDropWindow.uss
의 콘텐츠를 다음으로 교체합니다.
.slot {
width: 80px;
height: 80px;
margin: 5px;
background-color: rgb(255, 255, 255);
border-top-radius: 10px;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.slot_row {
flex-direction: row;
}
.object {
width: 50px;
height: 50px;
position: absolute;
left: 20px;
top: 20px;
border-radius: 30px;
background-color: rgb(0, 0, 0);
}
드래그 앤 드롭 동작을 정의하려면 PointerManipulator
클래스를 확장하고 로직을 정의합니다. target
을 설정하는 생성자를 작성하고 비주얼 트리의 루트에 레퍼런스를 저장합니다. PointerDownEvent
, PointerMoveEvent
, PointerUpEvent
, PointerCaptureOutEvent
에 대한 콜백 역할을 하는 4개의 메서드를 작성합니다. RegisterCallbacksOnTarget()
및 UnregisterCallbacksOnTarget()
을 구현하여 이 4개의 콜백을 target
에 등록 및 등록 해제합니다.
Editor
폴더에서 DragAndDropManipulator.cs
라는 다른 C# 파일을 생성합니다.
DragAndDropManipulator.cs
의 콘텐츠를 다음으로 교체합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class DragAndDropManipulator : PointerManipulator
{
// Write a constructor to set target and store a reference to the
// root of the visual tree.
public DragAndDropManipulator(VisualElement target)
{
this.target = target;
root = target.parent;
}
protected override void RegisterCallbacksOnTarget()
{
// Register the four callbacks on target.
target.RegisterCallback<PointerDownEvent>(PointerDownHandler);
target.RegisterCallback<PointerMoveEvent>(PointerMoveHandler);
target.RegisterCallback<PointerUpEvent>(PointerUpHandler);
target.RegisterCallback<PointerCaptureOutEvent>(PointerCaptureOutHandler);
}
protected override void UnregisterCallbacksFromTarget()
{
// Un-register the four callbacks from target.
target.UnregisterCallback<PointerDownEvent>(PointerDownHandler);
target.UnregisterCallback<PointerMoveEvent>(PointerMoveHandler);
target.UnregisterCallback<PointerUpEvent>(PointerUpHandler);
target.UnregisterCallback<PointerCaptureOutEvent>(PointerCaptureOutHandler);
}
private Vector2 targetStartPosition { get; set; }
private Vector3 pointerStartPosition { get; set; }
private bool enabled { get; set; }
private VisualElement root { get; }
// This method stores the starting position of target and the pointer,
// makes target capture the pointer, and denotes that a drag is now in progress.
private void PointerDownHandler(PointerDownEvent evt)
{
targetStartPosition = target.transform.position;
pointerStartPosition = evt.position;
target.CapturePointer(evt.pointerId);
enabled = true;
}
// This method checks whether a drag is in progress and whether target has captured the pointer.
// If both are true, calculates a new position for target within the bounds of the window.
private void PointerMoveHandler(PointerMoveEvent evt)
{
if (enabled && target.HasPointerCapture(evt.pointerId))
{
Vector3 pointerDelta = evt.position - pointerStartPosition;
target.transform.position = new Vector2(
Mathf.Clamp(targetStartPosition.x + pointerDelta.x, 0, target.panel.visualTree.worldBound.width),
Mathf.Clamp(targetStartPosition.y + pointerDelta.y, 0, target.panel.visualTree.worldBound.height));
}
}
// This method checks whether a drag is in progress and whether target has captured the pointer.
// If both are true, makes target release the pointer.
private void PointerUpHandler(PointerUpEvent evt)
{
if (enabled && target.HasPointerCapture(evt.pointerId))
{
target.ReleasePointer(evt.pointerId);
}
}
// This method checks whether a drag is in progress. If true, queries the root
// of the visual tree to find all slots, decides which slot is the closest one
// that overlaps target, and sets the position of target so that it rests on top
// of that slot. Sets the position of target back to its original position
// if there is no overlapping slot.
private void PointerCaptureOutHandler(PointerCaptureOutEvent evt)
{
if (enabled)
{
VisualElement slotsContainer = root.Q<VisualElement>("slots");
UQueryBuilder<VisualElement> allSlots =
slotsContainer.Query<VisualElement>(className: "slot");
UQueryBuilder<VisualElement> overlappingSlots =
allSlots.Where(OverlapsTarget);
VisualElement closestOverlappingSlot =
FindClosestSlot(overlappingSlots);
Vector3 closestPos = Vector3.zero;
if (closestOverlappingSlot != null)
{
closestPos = RootSpaceOfSlot(closestOverlappingSlot);
closestPos = new Vector2(closestPos.x - 5, closestPos.y - 5);
}
target.transform.position =
closestOverlappingSlot != null ?
closestPos :
targetStartPosition;
enabled = false;
}
}
private bool OverlapsTarget(VisualElement slot)
{
return target.worldBound.Overlaps(slot.worldBound);
}
private VisualElement FindClosestSlot(UQueryBuilder<VisualElement> slots)
{
List<VisualElement> slotsList = slots.ToList();
float bestDistanceSq = float.MaxValue;
VisualElement closest = null;
foreach (VisualElement slot in slotsList)
{
Vector3 displacement =
RootSpaceOfSlot(slot) - target.transform.position;
float distanceSq = displacement.sqrMagnitude;
if (distanceSq < bestDistanceSq)
{
bestDistanceSq = distanceSq;
closest = slot;
}
}
return closest;
}
private Vector3 RootSpaceOfSlot(VisualElement slot)
{
Vector2 slotWorldSpace = slot.parent.LocalToWorld(slot.layout.position);
return root.WorldToLocal(slotWorldSpace);
}
}
커스텀 창에서 드래그 앤 드롭을 활성화하려면 창이 열릴 때 인스턴스화합니다.
DragAndDropWindow.cs
에서 CreateGUI()
메서드에 다음을 추가하여 DragAndDropManipulator
클래스를 인스턴스화합니다.
DragAndDropManipulator manipulator =
new(rootVisualElement.Q<VisualElement>("object"));
메뉴 바에서 Window > UI Toolkit > Drag And Drop을 선택합니다.열린 커스텀 에디터 창에서 오브젝트를 아무 슬롯에나 드래그할 수 있습니다.
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.