UnityのUI Toolkitでマウス操作やジェスチャに関する処理を切り離して実装できるManipulatorの使い方についてまとめました。
Unity 2022.2.19
はじめに
UI Toolkitでは、以下のようにマウスなどのイベントのコールバックをUIに登録することで、イベントに応じて任意の処理を実行できます。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { public ExampleElement() { RegisterCallback<ClickEvent>(OnClick, TrickleDown.TrickleDown); } private void OnClick(ClickEvent evt) { if (evt.propagationPhase != PropagationPhase.AtTarget) return; Debug.Log("Clicked"); } }
しかしながら、たとえばドラッグ&ドロップなどのように、イベントに対して複雑なハンドリングを行う必要がある場合、これを全てUIのコードに書くと煩雑になり可読性が低下しがちです。
このようなケースでは、Manipulatorを使うことでイベントに関する処理を切り出すことができます。
Clickableマニピュレータ
UI Toolkitでは、このManipulatorがあらかじめいくつか用意されています。
代表的なManipulatorにClickable
マニピュレータがあります。
これを使うと、クリックに関する処理を以下のように書けるようになります。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { public ExampleElement() { var clickable = new Clickable(() => Debug.Log("Clicked")); this.AddManipulator(clickable); // Manipulatorを削除する場合は以下のようにする //this.RemoveManipulator(); } }
独自のManipulatorを作る
独自のManipulatorを作ることもできます。
以下はドラッグにより対象のUIを移動するManipulatorです。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleManipulator : PointerManipulator // PointerManipulatorを継承する { private Vector3 _elementStartPosition; private Vector3 _pointerStartPosition; public ExampleManipulator(VisualElement target) { // 操作対象のVisualElementがあればtargetに入れる this.target = target; // マウスの左ボタンだけ反応するようにManipulatorActivationFilterを追加する activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse }); } protected override void RegisterCallbacksOnTarget() { // 各種ポインターイベントを登録 target.RegisterCallback<PointerDownEvent>(OnPointerDown); target.RegisterCallback<PointerMoveEvent>(OnPointerMove); target.RegisterCallback<PointerUpEvent>(OnPointerUp); } protected override void UnregisterCallbacksFromTarget() { target.UnregisterCallback<PointerDownEvent>(OnPointerDown); target.UnregisterCallback<PointerMoveEvent>(OnPointerMove); target.UnregisterCallback<PointerUpEvent>(OnPointerUp); } // ポインター押下時の処理 private void OnPointerDown(PointerDownEvent evt) { // コンストラクタで登録したactivatorsにイベントがマッチすれば(今回の場合左クリックだったら)処理を通す if (!CanStartManipulation(evt)) return; _pointerStartPosition = evt.position; _elementStartPosition = target.transform.position; target.CapturePointer(evt.pointerId); } // ポインター移動時の処理 private void OnPointerMove(PointerMoveEvent evt) { if (!target.HasPointerCapture(evt.pointerId)) return; // ポインターの位置にUIを移動する var pointerDelta = evt.position - _pointerStartPosition; target.transform.position = _elementStartPosition + pointerDelta; } // ポインターを話した時の理 private void OnPointerUp(PointerUpEvent evt) { if (!CanStopManipulation(evt)) return; if (target.HasPointerCapture(evt.pointerId)) target.ReleasePointer(evt.pointerId); } }
説明はコメントに書いた通りです。
対象のUIをドラッグした場所に移動できるようにしています。
独自のManipulatorの動作確認
最後に、前節で作成した独自のManipulatorの動作確認を行います。
まずUI Builderで、下図のように一つだけUIを配置したレイアウトを作成します。
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="True"> <ui:VisualElement name="circle" style="flex-grow: 0; background-color: rgb(255, 0, 0); width: 50px; height: 50px; border-top-left-radius: 25px; border-bottom-left-radius: 25px; border-top-right-radius: 25px; border-bottom-right-radius: 25px;" /> </ui:UXML>
次にこれを表示するためのEditorWindowを作成します。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleWindow : EditorWindow { [SerializeField] private VisualTreeAsset visualTreeAsset; private void CreateGUI() { visualTreeAsset.CloneTree(rootVisualElement); var circle = rootVisualElement.Q<VisualElement>("circle"); // circleにExampleManipulatorを追加する var _ = new ExampleManipulator(circle); } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
レイアウトのcircleオブジェクトに対して、前節で作成したExampleManipulator
を追加しています。
あとはこのEditorWindowのスクリプトのInspectorから、visualTreeAsset
に先ほどのUXMLファイルをアサインし、Window > Exampleからウィンドウを開くと以下の結果が得られます。
独自のManipulatorを使ってドラッグで移動できていることを確認できました。