UnityのUI Toolkitでコピー&ペーストなどのショートカットをハンドリングするCommand Eventの使い方についてまとめました。
- Command Eventとは
- コピー&ペーストをハンドリングする
- 動作確認
- 対応しているコマンド
- 参考
UnityのUI Toolkitでコピー&ペーストなどのショートカットをハンドリングするCommand Eventの使い方についてまとめました。
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を使うことでイベントに関する処理を切り出すことができます。
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を作ることもできます。
以下はドラッグにより対象の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の動作確認を行います。
まず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を使ってドラッグで移動できていることを確認できました。
UnityのUI ToolkitでVisual Elementのマウスによるクリック判定・範囲を制御する方法です。
Unity 2022.2.19
VisualElement.pickingMode
をPickingMode.Ignore
に設定すると、そのVisualElementはマウスイベントに反応しなくなります。
この挙動を確かめるために以下のように、マウスイベントに反応した場合にログ出力を行う、PickingMode
がIgnore
なVisualElement
を作成します。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { public ExampleElement() { // マウスイベントに反応しないようにする pickingMode = PickingMode.Ignore; } protected override void ExecuteDefaultActionAtTarget(EventBase evt) { base.ExecuteDefaultActionAtTarget(evt); // クリックイベントが来たらログを出力 if (evt.eventTypeId == MouseDownEvent.TypeId()) Debug.Log("Mouse Down"); } }
これを以下のように適当なEditorWindowに表示します。
わかりやすいように赤い色をつけています。
using UnityEditor; using UnityEngine; public sealed class ExampleWindow : EditorWindow { private void OnEnable() { var element = new ExampleElement(); element.style.backgroundColor = Color.red; element.style.width = 100; element.style.height = 100; rootVisualElement.Add(element); } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
Window > Exampleからウィンドウを開き、赤いVisual Elementをクリックすると、クリックイベントに反応しないことを確認できます。
VisualElement.ContainsPoint
をオーバーライドすると、マウスイベントに反応する範囲をカスタムすることができます。
以下は、Paddingを除いた領域(ContentRect)のみマウスイベントに反応するVisualElementの例です。
using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleElement : VisualElement { public ExampleElement() { style.paddingLeft = 25; style.paddingRight = 25; style.paddingTop = 25; style.paddingBottom = 25; } protected override void ExecuteDefaultActionAtTarget(EventBase evt) { base.ExecuteDefaultActionAtTarget(evt); // クリックイベントが来たらログを出力 if (evt.eventTypeId == MouseDownEvent.TypeId()) Debug.Log("Mouse Down"); } public override bool ContainsPoint(Vector2 localPoint) { // contentRect (paddingを除いた領域) のみをクリック判定に含める return contentRect.Contains(localPoint); } }
Window > Exampleからウィンドウを開き、赤いVisual Elementをクリックすると、Paddingを除いた中央に近い部分のみクリックに反応することを確認できます。
いずれかのCancellationTokenがキャンセルされたらキャンセル扱いにするCancellationTokenSource.CreateLinkedTokenSource
の使い方です。