6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity】UI Toolkitのドラッグアンドドロップ実装を分かりやすく解説してみた

Posted at

【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;
}

出来たもの(失敗作)

D&D_Fail.gif

なんか挙動がおかしい...

  1. ドラッグ対象の基準点
  2. イベントの未発火

上記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)

D&D_Fail2.gif

一見うまく動いてそうです。
しかし、マウスを早く動かしてドラッグターゲットよりマウスカーソルをはみ出すと、ドラッグターゲットがマウスについて来なくなりました。
その後MouseDown状態でもないのにマウスにくっついて来るようにもなっています。

はい、失敗です。

2.イベントの未発火

原因はイベントの伝播が出来ていないことです。

ドラッグターゲットからマウスを外してもPointerMoveEventPointerUpEventは発火してもらわないと困ります。

その後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);
    }
}

出来たもの(成功)

D&D.gif

意図通りのドラッグアンドドロップが完成しました。

最後に

全体ソースはGistにアップしています。

環境

  • Unity2020.2.0f1
  • UI Toolkit 1.0.0-Preview.13
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?