[Unity]マウスやタップに追従してオブジェクトを自在に移動

Unity

 今回はマウスのクリックで選択したオブジェクトをドラッグで移動させたり、スマホのタップで選択したオブジェクトをスワイプで移動させる方法について説明します。

Goal

マウスやスマホでオブジェクトを自由に動かせること!

https://amzn.to/3bd8XOX




オブジェクトが選択されたことを知る

 オブジェクトをドラッグして移動するためにはまずオブジェクトが選択されたことを知る必要があります。

オブジェクトの選択を知る手段

 クリックされたオブジェクトを選択、決定する方法は以下のようにいくつかあるようです。

  1. クリックされたことをUpdate()で検知してRaycastを飛ばして、ぶつかったオブジェクト=選択したオブジェクトと判断する
  2. EventSystemを利用して、オブジェクトのスクリプトに実装したコールバックハンドラで検知する

 1番はUpdate()で毎度見張るのが非効率そうなので、2番でいこうかと思います。オブジェクトのスクリプトで処理を受けられるので感覚的にもわかりやすいとも思います。

EventSystemの追加

 オブジェクトがクリックされたことはEventSystemから通知されます。UIを追加されているよな場合はすでにEventSystemがあるかと思います。ない場合は以下のようにEventSystemを追加します。コンポーネントにはEventSystemStandalone Input Moduleがあることを確認します。

CameraにRaycasterの追加

 イベント発生時にCameraがRayを飛ばすようにCameraのAdd ComponentからRaycasterのコンポーネントを追加します。対象はここではEverythingでかまいません。

クリック対象オブジェクトの設定

 クリックされたことを検知したいオブジェクトにはColliderとScriptを設定します。Colliderは[Unity][3D物理演算]オブジェクト同士のぶつけ方を解説でも解説した通り、物理演算の衝突を検知するためのコンポーネントでした。
 スクリプトはIPointerClickHandlerインタフェースを継承して、インタフェース関数OnPointerClick()を実装しておきます。この関数がクリックを検知した時、コールバックされることになります。以下に、クリックされた時にログを出力するサンプルコードを示します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems; // usingを追加
using UnityEngine;

// IPointerClickHandlerを追加する
public class SorobanScr : MonoBehaviour , IPointerClickHandler
{
    // StartとUpdateは省略

    // クリック検知時、コールバックされる関数
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("clicked!");
    }
}
Update

実行してみる

 これら準備ができたら、実行して振る舞いを見てみます。以下の通り、クリックでログが出力されることが確認できました。

マウスをドラッグして動かしてみる

 マウスのクリックが検知できましたので、応用すれば、マウスの押下、解放なども検知できそうです。EventSystemから通知できるイベントとインタフェースはこちらにまとまっています。ドラッグしたオブジェクトを動かすことを、以下の手順で実現してみます。

  1. マウスの押下を検知する
  2. 押下されたWorld座標を開始座標とする
  3. 押下されている間、Update()関数で開始座標の差分だけオブジェクトを動かす
  4. マウスの解放を検知したらUpdate()でオブジェクトを動かすのをやめる

 スクリプト及び解説は以下になります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems; // 追加
using UnityEngine;

public class SorobanScr : MonoBehaviour , IPointerDownHandler, IPointerUpHandler
{
    private bool _isPushed = false; // マウスが押されているか押されていないか
    private Vector3 _nowMousePosi; // 現在のマウスのワールド座標


    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        Vector3 nowmouseposi;
        Vector3 diffposi;

        // マウスが押下されている時、オブジェクトを動かす
        if (_isPushed) {
            // 現在のマウスのワールド座標を取得
            nowmouseposi = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            // 一つ前のマウス座標との差分を計算して変化量を取得
            diffposi = nowmouseposi - _nowMousePosi;
            // Y成分のみ変化させる
            diffposi.x = 0;
            diffposi.z = 0;
            // 開始時のオブジェクトの座標にマウスの変化量を足して新しい座標を設定
            GetComponent<Transform>().position += diffposi;
            // 現在のマウスのワールド座標を更新
            _nowMousePosi = nowmouseposi;
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        // 押下開始 フラグを立てる
        _isPushed = true;
        // マウスのワールド座標を保存
        _nowMousePosi = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        // 押下終了 フラグを落とす
        _isPushed = false;
        _nowMousePosi = Vector3.zero;
    }
}

マウスの押下を検知する

 マウスの押下はIPointerDownHandlerを継承してOnPointerDown()で検知します。
 検知開始時のマウスの座標を現在のマウス座標として保存しておきます。マウスの座標はInput.mousePositionで取得できますが、これはワールド座標ではなく、スクリーン座標なので、Camera.main.ScreenToWorldPoint()で変換しています。

オブジェクトを動かす

 オブジェクトはUpdate()関数の中で動かします。マウスが押されている間オブジェクトを動かします。マウスの座標を取得して、ひとつ前に保存した座標との差分を動かしたいオブジェクトに加算しています。
 今回、そろばん珠は上下にしか動かしたくないので、Y成分の差分のみ変化させています。

マウスの解放を検知する

 マウスの解放はIPointerUpHandlerを継承してOnPointerUp()で検知します。Update()での更新を止めるため、フラグを落としています。

うごかしてみる

 スクリプトを組み込んで動作させてみます。マウスのY成分の変化量に伴って、オブジェクトが動いていることが確認できます。スマホでも同様にタップで動作することが確認できました

まとめ

 今回はマウスの動きに伴って3Dオブジェクトを動かしてみました。応用すれば、マウスのクリックに連動したり、クリックなしでオブジェクトに触れる動作に連動したり、様々な動きに対応できるかと思います。座標系についてはUIのスクリーン座標ワールド座標の違いを理解しておくことが大事かと思います。座標の話についてはこちらの記事に詳しくまとめております。

https://amzn.to/3bd8XOX

 

コメント