バージョン: 2021.3 以降
ドラッグアンドドロップは UI デザインにおいて一般的な機能です。UI Toolkit を使用すると、カスタムエディターウィンドウ内または Unity でビルドされたアプリケーション内にドラッグアンドドロップ UI を作成することができます。この例では、カスタムエディターウィンドウの中にドラッグアンドドロップのUIを作成する方法を説明します。
この例では、カスタムエディターウィンドウに複数のスロットと 1 つのオブジェクトを加えます。下図のように、オブジェクトを任意のスロットにドラッグすることができます。
この例で作成するすべてのファイルは、GitHub リポジトリ にあります。
This is an advanced example for developers familiar with Unity Editor, UI Toolkit, and C# scripting. You are recommended to have a basic understanding of the following concepts:
まず、ドラッグアンドドロップ UI を保持するために、カスタムエディターウィンドウを作成します。
Assets
に DragAndDrop
という名前のフォルダーを作成し、すべてのファイルを保存します。DragAndDrop
フォルダーを右クリックし、Create > UI Toolkit > Editor Window の順に選択します。DragAndDropWindow
と入力します。DragAndDropWindow.cs
を開き、メニュー名とウィンドウのタイトルを Drag And Drop
に変更し、デフォルトラベルのコードを削除し、より使いやすい UI にします。完成した 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/Drag and Drop/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/Drag and Drop/DragAndDropWindow.uss");
}
}
次に、カスタムウィンドウに UI コントロールを加えます。
DragAndDrop
フォルダーで、DragAndDropWindow.uxml
をダブルクリックして、UI Builder を開きます。
StyleSheet で Add Existing USS をクリックし、DragAndDropWindow.uss
を選択します。
以下の VisualElement
UI コントロールを加えます。
slot_row1
と slot_row2
という 2 つの子を持つ slots
という名のコントロール。各行には、slot1
と slot2
という名前の2つの子供があります。slot
と同じレベルにある object
という名前のもの。 object
は Hierarchy で slots
の後に来る必要があります。UI コントロールを以下のようにスタイル設定します。
slot1
と slot2
は、80px X 80px の正方形で、背景色が白、コーナーが丸いスタイルにします。スロットは 2 行に並べ、各行に 2 つのスロットを配置します。object
の場合、50px X 50px の円形スポットにし、背景色が黒のスタイルにします。ヒント: プロジェクトをもっと楽しくするために、オブジェクトに背景画像を使用できます。その画像 (Pouch.png) は GitHub リポジトリ にあります。
UI コントロールを加えてスタイルを設定する方法については、UI Builder を参照してください。
完成した 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="project://database/Assets/DragAndDrop/DragAndDropWindow.uss?fileID=7433441132597879392&guid=3d86870c8637c4a3c979a8b4fe0cba4c&type=3#DragAndDrop" />
<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;
}
.slot_row {
flex-direction: row;
}
.object {
width: 50px;
height: 50px;
position: absolute;
left: 10px;
top: 10px;
border-radius: 30px;
background-color: rgb(0, 0, 0);
}
ドラッグアンドドロップの動作を定義するには、PointerManipulator
クラスを継承し、ロジックを定義します。
DragAndDrop
フォルダーに、DragAndDropManipulator.cs
という別の C# ファイルを作成します。
DragAndDropManipulator.cs
を開きます。
using UnityEngine.UIElements;
の宣言を加えます。
Make the DragAndDropManipulator
class extend PointerManipulator
rather than MonoBehaviour
, and do the following:
RegisterCallbacksOnTarget()
to register all necessary callbacksUnregisterCallbacksOnTarget()
to un-register those callbackstarget
and store a reference to the root of the visual treeWrite four methods that act as callbacks for PointerDownEvent
s, PointerMoveEvent
s, PointerUpEvent
s, and PointerCaptureOutEvent
s:
PointerDownHandler()
: Stores the starting position of target
and the pointer, makes target
capture the pointer, and denotes that a drag is now in progress.PointerMoveHandler()
: 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.PointerUpHandler()
: Checks whether a drag is in progress and whether target
has captured the pointer. If both are true, makes target
release the pointer.PointerCaptureOutHandler()
: 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.In RegisterCallbacksOnTarget()
, register these four callbacks on target
.
In UnregisterCallbacksOnTarget()
, un-register these four callbacks from target
.
完成した DragAndDropManipulator.cs
は、以下のようになります。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class DragAndDropManipulator : PointerManipulator
{
public DragAndDropManipulator(VisualElement target)
{
this.target = target;
root = target.parent;
}
protected override void RegisterCallbacksOnTarget()
{
target.RegisterCallback<PointerDownEvent>(PointerDownHandler);
target.RegisterCallback<PointerMoveEvent>(PointerMoveHandler);
target.RegisterCallback<PointerUpEvent>(PointerUpHandler);
target.RegisterCallback<PointerCaptureOutEvent>(PointerCaptureOutHandler);
}
protected override void UnregisterCallbacksFromTarget()
{
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; }
private void PointerDownHandler(PointerDownEvent evt)
{
targetStartPosition = target.transform.position;
pointerStartPosition = evt.position;
target.CapturePointer(evt.pointerId);
enabled = true;
}
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));
}
}
private void PointerUpHandler(PointerUpEvent evt)
{
if (enabled && target.HasPointerCapture(evt.pointerId))
{
target.ReleasePointer(evt.pointerId);
}
}
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 を選択します。開いたカスタムエディターウィンドウで、オブジェクトを任意のスロットにドラッグすることができます。