Edited at

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を切り替えることで実現してみようと思います。