Version: Unity 6.0 (6000.0)
언어 : 한국어
UI 빌더 및 C# 스크립트를 사용하여 간단한 전환 만들기
에디터 창 간에 드래그할 수 있는 드래그 앤 드롭 UI 만들기

커스텀 에디터 창 내부에 드래그 앤 드롭 UI 생성

버전: 2021.3+

드래그 앤 드롭은__ UI__(사용자 인터페이스) 사용자가 애플리케이션과 상호 작용하도록 해 줍니다. Unity는 현재 3개의 UI 시스템을 지원합니다. 자세한 정보
See in Glossary
디자인에서 흔히 사용되는 기능입니다. UI 툴킷을 사용하여 커스텀 에디터 창에서 또는 Unity로 빌드한 애플리케이션에서 드래그 앤 드롭 UI를 생성할 수 있습니다. 이 예시에서는 커스텀 에디터 창에 드래그 앤 드롭 UI를 생성하는 방법을 보여 줍니다.

개요 예시

이 예시에서는 커스텀 에디터 창에 여러 슬롯과 하나의 오브젝트를 추가합니다. 아래와 같이 오브젝트를 아무 슬롯에나 드래그할 수 있습니다.

드래그 앤 드롭 UI 미리 보기
드래그 앤 드롭 UI 미리 보기

이 예시에서 만든 완성된 파일은 이 GitHub 저장소에서 찾을 수 있습니다.

선행 조건

이 가이드는 Unity 에디터, UI 툴킷, C# 스크립팅에 익숙한 개발자를 위한 가이드입니다. 시작하기 전에 먼저 다음을 숙지하십시오.

커스텀 에디터 창 생성

시작하려면 메뉴에서 기본 커스텀 에디터 창을 생성합니다. 메뉴 이름과 창 이름을 Drag And Drop으로 변경하고 기본 레이블에 대한 코드를 제거하여 보다 사용자 친화적인 UI가 되도록 합니다.

  1. 템플릿을 사용하여 Unity에서 프로젝트를 생성합니다.

  2. Assets 폴더를 오른쪽 클릭하고 Create > UI Toolkit > Editor Window를 선택합니다.

  3. UI Toolkit Editor Window CreatorDragAndDropWindow를 입력합니다.

  4. Confirm을 클릭합니다. 이렇게 하면 DragAndDropWindow.cs, DragAndDropWindow.uxml, DragAndDropWindow.uss라는 파일 세 개가 자동으로 생성됩니다.

  5. 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 컨트롤을 추가합니다.

  • 하나는 slot_row1slot_row2라는 자식이 두 개 있는 slots입니다. 각 행에는 slot1slot2라는 두 개의 자식이 있어야 합니다.
  • 하나는 slots와 같은 수준에 있는 object입니다. object계층 구조에서 slots 뒤에 있어야 합니다.

다음과 같이 UI 컨트롤의 스타일을 지정합니다.

  • slot1slot2의 경우 배경이 흰색이고 모서리가 둥근 80x80픽셀의 정사각형으로 스타일을 지정합니다. 슬롯을 두 줄로 정렬하고 각 줄에 두 개의 슬롯을 배치합니다.
  • object의 경우 배경이 검은색인 50x50픽셀의 둥근 점으로 스타일을 지정합니다.

: 프로젝트를 더 재미있게 만들기 위해 오브젝트에 배경 이미지를 사용할 수 있습니다. 이미지(Pouch.png)는 GitHub 저장소에서 찾을 수 있습니다.

  1. 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>
    
  2. 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에 대한 콜백으로 작동하는 네 가지 메서드를 작성합니다. target에서 이 네 개의 콜백을 등록하고 등록 취소하려면 RegisterCallbacksOnTarget()UnregisterCallbacksOnTarget()을 구현합니다.

  1. Editor 폴더에 DragAndDropManipulator.cs라는 다른 C# 파일을 만듭니다.

  2. 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);
        }
    }
    

드래그 앤 드롭 동작 인스턴스화

커스텀 창에서 드래그 앤 드롭을 활성화하려면 창이 열릴 때 인스턴스화합니다.

  1. DragAndDropWindow.cs에서 다음을 CreateGUI() 메서드에 추가하여 DragAndDropManipulator 클래스를 인스턴스화합니다.

    DragAndDropManipulator manipulator =
        new(rootVisualElement.Q<VisualElement>("object"));
    
  2. 메뉴 바에서 Window > UI Toolkit > Drag And Drop을 선택합니다. 열려 있는 커스텀 에디터 창에서 오브젝트를 아무 슬롯에나 드래그할 수 있습니다.

추가 리소스

UI 빌더 및 C# 스크립트를 사용하여 간단한 전환 만들기
에디터 창 간에 드래그할 수 있는 드래그 앤 드롭 UI 만들기