Version: 2022.3
言語: 日本語
カスタムエディタウィンドウで遷移を作成する
エディターウィンドウ間をドラッグできるドラッグアンドドロップ UI の作成

カスタムエディターウィンドウ内にドラッグアンドドロップ UI を作成する

バージョン: 2021.3 以降

ドラッグアンドドロップは UI デザインにおいて一般的な機能です。UI Toolkit を使用すると、カスタムエディターウィンドウ内または Unity でビルドされたアプリケーション内にドラッグアンドドロップ UI を作成することができます。この例では、カスタムエディターウィンドウの中にドラッグアンドドロップのUIを作成する方法を説明します。

例の概要

この例では、カスタムエディターウィンドウに複数のスロットと 1 つのオブジェクトを加えます。下図のように、オブジェクトを任意のスロットにドラッグすることができます。

ドラッグアンドドロップ UI のプレビュー
ドラッグアンドドロップ UI のプレビュー

この例で作成するすべてのファイルは、GitHub リポジトリ にあります。

要件

このガイドは、Unity エディター、UI Toolkit、および C# スクリプトに精通している開発者を対象としています。始める前に、以下をよく理解してください。

カスタムエディターウィンドウの作成

まず、ドラッグアンドドロップ UI を保持するために、カスタムエディターウィンドウを作成します。

  1. Unity で任意のテンプレートでプロジェクトを作成します。
  2. AssetsDragAndDrop という名前のフォルダーを作成し、すべてのファイルを保存します。
  3. DragAndDrop フォルダーを右クリックし、Create > UI Toolkit > Editor Window の順に選択します。
  4. UI Toolkit Editor Window CreatorDragAndDropWindow と入力します。
  5. Confirm をクリックします。これで、カスタムウィンドウ用の C# スクリプト、UXML、USS ファイルが自動的に作成されます。
  6. 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 コントロールを加えます。

  1. DragAndDrop フォルダーで、DragAndDropWindow.uxml をダブルクリックして、UI Builder を開きます。

  2. StyleSheetAdd Existing USS をクリックし、DragAndDropWindow.uss を選択します。

  3. 以下の VisualElement UI コントロールを加えます。

    • slot_row1slot_row2 という 2 つの子を持つ slots という名のコントロール。各行には、slot1slot2 という名前の2つの子供があります。
    • slot と同じレベルにある object という名前のもの。 objectHierarchyslots の後に来る必要があります。
  4. UI コントロールを以下のようにスタイル設定します。

    • slot1slot2 は、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&amp;guid=3d86870c8637c4a3c979a8b4fe0cba4c&amp;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 クラスを継承し、ロジックを定義します。

  1. DragAndDrop フォルダーに、DragAndDropManipulator.cs という別の C# ファイルを作成します。
  2. DragAndDropManipulator.cs を開きます。
  3. using UnityEngine.UIElements; の宣言を加えます。
  4. DragAndDropManipulator クラスで MonoBehaviour でなく、PointerManipulator を拡張する。
  5. を設定するコンストラクタを書く。 target を設定し、ビジュアルツリーのルートへの参照を格納するコンストラクターを作成します。
  6. PointerDownEventPointerMoveEventPointerUpEventPointerCaptureOutEvent のコールバックとして動作する 4 つのメソッドを書きます。
  7. 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);
    }
}

ドラッグアンドドロップ動作のインスタンス化

カスタムウィンドウでドラッグアンドドロップを有効にするには、ウィンドウを開くときにインスタンス化します。

  1. DragAndDropWindow.cs で、 CreateGUI() メソッドに以下を追加し、DragAndDropManipulator クラスをインスタンス化します。

    DragAndDropManipulator manipulator =
        new(rootVisualElement.Q<VisualElement>("object"));
    
  2. メニューバーから、 Window > UI Toolkit > Drag And Drop を選択します。開いたカスタムエディターウィンドウで、オブジェクトを任意のスロットにドラッグすることができます。

その他の参考資料

カスタムエディタウィンドウで遷移を作成する
エディターウィンドウ間をドラッグできるドラッグアンドドロップ UI の作成