バージョン: 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 を選択します。開いたカスタムエディターウィンドウで、オブジェクトを任意のスロットにドラッグすることができます。
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.