バージョン: 2021.3 以降
ドラッグアンドドロップは UI デザインにおいて一般的な機能です。UI Toolkit を使用すると、カスタムエディターウィンドウ内または Unity でビルドされたアプリケーション内にドラッグアンドドロップ UI を作成することができます。この例では、カスタムエディターウィンドウの中にドラッグアンドドロップのUIを作成する方法を説明します。
この例では、カスタムエディターウィンドウに複数のスロットと 1 つのオブジェクトを加えます。下図のように、オブジェクトを任意のスロットにドラッグすることができます。
この例で作成するすべてのファイルは、GitHub リポジトリ にあります。
このガイドは、Unity エディター、UI Toolkit、および C# スクリプトに精通している開発者を対象としています。始める前に、以下をよく理解してください。
まず、ドラッグアンドドロップ 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;
の宣言を加えます。DragAndDropManipulator
クラスで MonoBehaviour
でなく、PointerManipulator
を拡張する。target
を設定し、ビジュアルツリーのルートへの参照を格納するコンストラクターを作成します。PointerDownEvent
、PointerMoveEvent
、PointerUpEvent
、 PointerCaptureOutEvent
のコールバックとして動作する 4 つのメソッドを書きます。RegisterCallbacksOnTarget()
と UnregisterCallbacksOnTarget()
を実装して、これら 4 つのコールバックを target
から登録および登録解除します。完成した 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 を選択します。開いたカスタムエディターウィンドウで、オブジェクトを任意のスロットにドラッグすることができます。