【Unity】UI ToolkitのPointerEventBaseのpositionは何を指すのか?
前回執筆したコチラの続きでUI Toolkitでドラッグアンドドロップの実装をして生きます。普通にやると落ちる落とし穴付きで解説しています。
ドラッグする対象にイベントを登録
var document = GetComponent<UIDocument>();
var rootVisualElement = document.rootVisualElement;
// ドラッグターゲットとなるVisualElementを取得
var dragTarget = rootVisualElement.Q<VisualElement>("dragTarget");
dragTarget.RegisterCallback<PointerDownEvent>(OnPointerDown);
dragTarget.RegisterCallback<PointerMoveEvent>(OnPointerMove);
dragTarget.RegisterCallback<PointerUpEvent>(OnPointerUp);
- PointerDownEvent
- PointerMoveEvent
- PointerUpEvent
3種のイベントを登録してコールバック関数をセットしています。
PointerMoveEventはMouseDown・MouseUp関わらず、マウスが動くたびに処理が走ってしまうので、_isPointerDown
変数を用意してMouseDown or MouseUpの状態を保持するようにしました。
普通に書くと以下のようなコードになりましたが、これは失敗です。
private bool _isPointerDown = false;
private void OnPointerDown(PointerDownEvent evt)
{
// ドラッグ状態にする
_isPointerDown = true;
}
private void OnPointerMove(PointerMoveEvent evt)
{
if (_isPointerDown)
{
var pos = new Vector2(evt.position.x, evt.position.y);
var draggedTarget = (VisualElement) evt.target;
// ドラッグターゲットの座標を更新
draggedTarget.style.left = pos.x;
draggedTarget.style.top = pos.y;
}
}
private void OnPointerUp(PointerUpEvent evt)
{
// ドラッグ状態解除
_isPointerDown = false;
}
出来たもの(失敗作)
なんか挙動がおかしい...
- ドラッグ対象の基準点
- イベントの未発火
上記2点問題があるのです。
1.ドラッグ対象の基準点
VisualElementの基準点は左上です。
private bool _isPointerDown = false;
private Vector2 _dragOffset;
private void OnPointerDown(PointerDownEvent evt)
{
_isPointerDown = true;
var draggedTarget = (VisualElement) evt.target;
// MouseDownしたタイミングのドラッグターゲットの座標
var dragTargetPos = new Vector2(
dragTarget.resolvedStyle.left,
dragTarget.resolvedStyle.top);
// MouseDownしたタイミングのMouseの座標
var mouseDownPosition = new Vector2(evt.position.x, evt.position.y);
// ドラッグターゲットの調整値を保持
_dragOffset = dragTargetPos - mouseDownPosition;
}
private void OnPointerMove(PointerMoveEvent evt)
{
if (_isPointerDown)
{
var mousePosition = new Vector2(evt.position.x, evt.position.y);
// 現在のマウス座標に対してオフセット値を加算して座標を調整
var pos = mouseDownPosition + _dragOffset;
var draggedTarget = (VisualElement) evt.target;
draggedTarget.style.left = pos.x;
draggedTarget.style.top = pos.y;
}
}
private void OnPointerUp(PointerUpEvent evt)
{
_isPointerDown = false;
}
出来たもの(失敗その2)
一見うまく動いてそうです。
しかし、マウスを早く動かしてドラッグターゲットよりマウスカーソルをはみ出すと、ドラッグターゲットがマウスについて来なくなりました。
その後MouseDown状態でもないのにマウスにくっついて来るようにもなっています。
はい、失敗です。
2.イベントの未発火
原因はイベントの伝播が出来ていないことです。
ドラッグターゲットからマウスを外してもPointerMoveEvent、PointerUpEventは発火してもらわないと困ります。
その後MouseDown状態でもないのにマウスにくっついて来るようにもなっています。
しかしこの状態ではPointerUpEventが発火していません。
そのため内部的にはまだドラッグ状態ということになります。
※ _isPointerDownの更新がされない
よってMouseDown状態でなくてもマウスにくっついて来てしまうという状態になるわけです。
CapturePointerを使う
When a VisualElement captures a pointer, all pointer events are sent to the element, regardless of which element is under the pointer.
Unity - Scripting API: UIElements.PointerCaptureHelper.CapturePointerより
早い話が、ポインター先がどのVisualElementだろうがイベント発火するという事です。
MouseDownのタイミングでCapturePointerを実行して、ポインターのキャプチャをします。
※イベント実行時のクリックIDを内部的に保持する
private void OnPointerDown(PointerDownEvent evt)
{
// pointerIdをキャプチャ
evt.target.CapturePointer(evt.pointerId);
// ~~~~ 略 ~~~~
すると、MouseMove時にドラッグターゲットからマウスが外れてもイベントは発火し続けます。
MouseDown判定にキャプチャ状態を使用
private void OnPointerMove(PointerMoveEvent evt)
{
// // MouseDown判定をポインターをキャプチャしているかどうかで判定する
if (evt.target.HasPointerCapture(evt.pointerId))
{
// ドラッグオブジェクトの座標を更新する処理
// ~~~~ 略 ~~~~
MouseUpしたらキャプチャを解除する
private void OnPointerUp(PointerUpEvent evt)
{
if (evt.target.HasPointerCapture(evt.pointerId))
{
// ポインターのキャプチャを解放
evt.target.ReleasePointer(evt.pointerId);
}
}
出来たもの(成功)
意図通りのドラッグアンドドロップが完成しました。
最後に
全体ソースはGistにアップしています。
環境
- Unity2020.2.0f1
- UI Toolkit 1.0.0-Preview.13