[Unity]InputSystemでタップ&スライドしたときにカメラの移動(バグあり)

2020/09/04
Unity

よくある、スワイプした分だけカメラを動かす実装です。

環境

  • Unity 2020.1.3f1
  • InputSystem 1.0.0

既知の不具合

Android実機環境で動かしたときに、ポインタの移動量とカメラの移動量が一致しない。上手い設定の方法を知っている方には教えてほしい!
iOSでは不明。 予測だと Resolution Scaling とかその辺が悪さをしているかなあ。

  • 機種: ZenFone Max (M2)
  • ディスプレイ: 720 x 1520px (HD+, 19:9) 267ppi
  • OS: Android 9

スライド時にカメラを移動する実装

InputActionの設定はこんな感じです。

InputActions

Delta の bainding はポインターの位置に変化があったときに変化量を通知するbindです。

Press の イベントでタップしている間を取得して、タップしている間はDeltaの移動量をcameraのtransformに適用するという実装です。

using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// スライドでカメラを移動する
/// </summary>
public class MoveCameraController : MonoBehaviour
{
    /// <summary>
    /// 移動させる対象
    /// </summary>
    [SerializeField]
    private Camera targetCamera;

    /// <summary>
    /// タップ中かどうか
    /// true: タップ中
    /// </summary>
    private bool isHolding;

    /// <summary>
    /// ポイント時のイベント
    /// </summary>
    /// <param name="context"></param>
    public void OnInputPoint(InputAction.CallbackContext context)
    {
        switch (context.phase)
        {
            case InputActionPhase.Started:
                isHolding = true;
                break;
            case InputActionPhase.Canceled:
                isHolding = false;
                break;
        }
    }

    /// <summary>
    /// ポインターポジションの変更時イベント
    /// </summary>
    /// <param name="context"></param>
    public void OnInputPositionDelta(InputAction.CallbackContext context)
    {
        if (isHolding)
        {
            MoveCamera(targetCamera.ConvertScreenDeltaToWorldDelta(context.ReadValue<Vector2>()));
        }
    }

    /// <summary>
    /// カメラを移動する
    /// </summary>
    /// <param name="delta">移動量(world)</param>
    private void MoveCamera(Vector2 delta)
    {
        targetCamera.transform.Translate(-delta);
    }
}

タップ時のポインター位置を得るときもそうですが、位置関係のイベントとタップ時のイベントが別々に来るので、状態持っておかないといけないのがInputSystemのイマイチポイントですね。 (これもいい方法があるかもしれない)

Deltaで得られる差分はスクリーン座標なのでワールド座標に変換する必要があります。
変換の実装は以下

/// <summary>
/// スクリーン距離をワールド距離に変換する
/// </summary>
/// <param name="camera">対象のカメラ</param>
/// <param name="screenDelta">スクリーン上の距離</param>
/// <returns>ワールド上の距離</returns>
public static Vector2 ConvertScreenDeltaToWorldDelta(this Camera camera, Vector2 screenDelta)
{
    Vector3 p1 = camera.ScreenToWorldPoint(Vector3.zero);
    Vector3 p2 = camera.ScreenToWorldPoint(Vector3.right);
    float unit = p2.x - p1.x;
    return screenDelta * unit;
}

ScreenToWorldPointのdelta版が無いので実装しています。
ScreenToWorldPointを使ってScreen座標の(0, 0, 0)と(1, 0, 0)をワールド座標に変換したものの間を計測することで、1ピクセルあたりのワールド距離(unit)を求めています。

これで少なくともWindows環境では正常に動作するのでコード自体は間違ってないと思うんだけどなあ

追記1 (2020/09/05)

バグの原因はカメラ位置が原因かもしれない。orthographicのカメラだから関係ないと思ってたけど関係ありそうな挙動をしている。

追記2 (2020/09/07)

InputSystemを直に使うのではなく、EventSystem経由で利用するように変更しました。UIに遮られるクリック操作
いずれにせよUIに被っている箇所はブロッキングする必要があったのでこちらの方が自然でしょう。
なお、座標の変換処理には問題無く、InputSystemのdeltaバインディングの挙動がちょっとおかしいみたいです。[1]

© 2019-2020 hassakulab.com, built with Gatsby