C#
Unity

Unityで見下ろしカメラをドラッグで動かす

はじめに

地面を見下ろすカメラで地面(y=0のxz平面)をドラッグすることでカメラを動かしたいので、サンプルを作りました。
スマホだとスワイプで動くはずです。

今回のコードを下記に置きました。
https://github.com/jnhtt/LookingDownCameraOperation

こんな感じです。
ドラッグ開始がCubeの上だと動かないようにしています。
LookDown.gif

環境

  • MacBook Pro(15-inch, 2017)
  • Unity 2018.2.2f1

概要

画面をドラッグしてカメラを動かします。
処理の流れとしては下記の通り。

  1. クリックした位置にObjectレイヤーのGameObjectがあると、2以降の処理を行わない
  2. マウスの位置からRayを飛ばして、XZ平面(y=0)との交点を求める
  3. 交点から-Camera.transform.forward方向のRayを飛ばし、XZ平面(y=カメラ高さ)との交点を求める
  4. 2.で求めた交点を点Tとして保持する
  5. ドラッグ中に1と2を計算し、2の交点と点Tの差分Dを計算する
  6. カメラ位置から差分Dを引いて新しいカメラ位置とする

1はUnityでListにした座標を辿るのオブジェクトの選択と同じような処理です。

2から6をIBeginDragIDragに分けて説明します。

イベント 上記の番号
IBeginDrag 2,3,4
IDrag 5,6

IBeginDragでの処理

マウス位置からRayを打ち込むXZ平面(y=0)をinteractPlane
interactPlaneからカメラ正面の逆方向のRayを打ち込むXZ平面(y=カメラ高さ)をcameraMovablePlane
とします。

Rayを打ち込む平面/カメラが動ける平面
    private Plane interactPlane;
    private Plane cameraMovablePlane;

TryGetCameraPositionOnCameraMovablePlaneは、上記2と3を計算してcameraMovablePlane上の交点を求めます。
その交点をドラッグ開始時の情報として保持します。

OnBeginDrag
    public override void OnBeginDrag(InputData inputData)
    {
        if (!canOperate) {
            return;
        }

        base.OnBeginDrag(inputData);
        cameraMovablePlane.distance = CameraManager.Instance.Camera.transform.position.y;
        Vector3 camPos = Vector3.zero;
        if (TryGetCameraPositionOnCameraMovablePlane(inputData.screenPosition, out camPos)) {
            inputData.previousCameraPosition = camPos;
        }
    }

IDragでの処理

ドラッグ中のcameraMovablePlane上の交点とドラッグ開始時の交点の差分をCamera位置から引いたものを新しいCamera位置とすると上図のような挙動になります。

OnDrag
    public override void OnDrag(InputData inputData)
    {
        if (!canOperate) {
            return;
        }

        base.OnDrag(inputData);
        Vector3 camPos = Vector3.zero;
        if (TryGetCameraPositionOnCameraMovablePlane(inputData.screenPosition, out camPos)) {
            Vector3 diff = camPos - inputData.previousCameraPosition;
            Vector3 pos = CameraManager.Instance.Camera.transform.position - diff;
            CameraManager.Instance.ReservePosition(pos);
        }
    }

OnBeginDrag/OnDrag/OnEndDragの実装についての補足

上記に示したコードでOnDrag(InputData inputData)となっているのは、IDragHandlerの実装の中身をLookingDownCameraOperationというクラスで実装しているためです。

ControllerPanel.cs
public class ControllerPanel
    : ...
    , IBeginDragHandler, IDragHandler, IEndDragHandler
{
    ...
    private IInputOperation inputOperation;
    ...
    public void OnDrag(PointerEventData pointerEventData)
    {
        if (inputOperation != null) {
            inputData.screenPosition = pointerEventData.position;
            inputOperation.OnDrag(inputData);
        }
    }
}

使いそうなInterfaceを定義しておいて、

public interface IInputOperation
{
    void OnPointerDown(InputData inputData);
    void OnPointerUp(InputData inputData);
    void OnPointerExit(InputData inputData);
    void OnBeginDrag(InputData inputData);
    void OnDrag(InputData inputData);
    void OnEndDrag(InputData inputData);
}

OnPointerDown/OnPointerUpやOnBeginDrag/OnDrag/OnEndDragのセットで実装がないと動かないものを空実装で揃えて、

public class BaseInputOperation : IInputOperation
{
    public virtual void OnPointerDown(InputData inputData) {}
    public virtual void OnPointerUp(InputData inputData) {}
    public virtual void OnPointerExit(InputData inputData) {}
    public virtual void OnBeginDrag(InputData inputData) {}
    public virtual void OnDrag(InputData inputData) {}
    public virtual void OnEndDrag(InputData inputData) {}
}
public class LookingDownCameraOperation : BaseInputOperation
{
    public override void OnDrag(InputData inputData)
    {
        ...
        //IDragでの処理で示したコード
        ...
    }
}

イベントの実行順番

今回のサンプル時確認した実行順番です。
OnPointerDownでオブジェクトを判別して処理を分岐するのが良い?

  1. OnPointerDown
  2. OnBeginDrag
  3. OnDrag
  4. OnPointerUp
  5. OnExitDrag
  6. OnExit

おわりに

Unityで見下ろしカメラをドラッグで動かす処理を実装してみました。
今回はカメラを動かしました。
別の方法として、カメラを動かさずに3Dのルートオブジェクトを作って、そのルートオブジェクトを動かすことで実現する方法もあります。

IInputOperation inputOperationに関しては、Zenjectを使ってもいいのかもしれません。正しく使えるかはわかりませんが。
ピンチイン/ピンチアウトは、Input.touchCountが2になった時にIInputOperationを切り替えることで実現してみようと思います。