ユニティちゃんを動かせるようになりました。 キャラクターとのオフセットでカメラを追従させてましたが、ちょっと物足りなくなったのでTPSっぽくマウスでカメラワークを変更できるようにしました。
この記事では、TPS視点のカメラワークとCinemachineをスクリプトで拡張する方法です。CinemachineVirtualCamera
やCinemachineOrbitalTransposer
をスクリプトで制御します。
操作はSkyrimをイメージしている。The Elder Scrollsの次回作が楽しみだけどまだ数年はかかるかな。
Unity 2019.4
Chinemachine
UnityのカメラツールのCinemachineというパッケージを使います。
CinemachineはUnityのカメラワークのためのパッケージです。 ターゲットをカメラで追う動きでも、定点で追う、ターゲットの真後ろから追う、ターゲットの周囲を回るなどたくさんの動きをノーコードで実現できます。 また、複数のカメラを滑らかに切り替える、手振れを付ける、画角に遊びを持たせるなど高度なカメラワークも出来ます。
▼ノーコードでもこのGIFのようにTPS視点+マウスで左右に操作することができるようになりました。
他のカメラワークの動きについては▼のリンク先の動画がわかりやすいです。
▼Cinemachineの機能についてはこちらの記事を見た方がいい。わかりやすく手順が書いてあります。
Cinemachine のインストール
Unityの「Window」→「Package Manager」を開いて「Unity Resistry」で検索するとCinemachineが出てくるのでインポートします。
Cinemachineは外部依存関係を持ってないため、以上でOKです。
TPS視点で追従するカメラを作ってみる
Cinemahineを使ってノーコードでTPS視点+マウス操作のカメラを作っていきます。 ちなみにTPSはThird Person perspectiveの略で三人称視点という意味で、 一人称視点はFPS(First Person Perspective)です。 今までFPSはシューティングゲームの視点って意味かと思ってました。
CinemahineBrain
まずは、Main Camera
にCinemahineBrain
のコンポーネントを追加します。
これによって後で追加するバーチャルカメラのカットやブレンドを制御します。
CinemahineVirtualCamera
続いて空のGameObjectを作ってCinemahineVirtualCamera
コンポーネントを追加します。
バーチャルカメラのAim、Body、Noise プロパティーを使用して、バーチャルカメラ が位置、回転、その他のプロパティーをどのようにアニメーション化するかを設定します。
空のGameObjectはVirtualCamera
と名付けます。
Follow
に紐づけたオブジェクトに追従し、Look At
のオブジェクトにカメラの照準を合わせます。
Follow
とLook At
にプレイヤーのunitychan
をアタッチしました。
CinemahineVirtualCamera
のBody
とAim
をそれぞれOrbital Transpoter
とComposer
と選択しました。
Body
Bodyプロパティーは、バーチャルカメラを動かすアルゴリズムを指定します。
そのアルゴリズムの内のひとつOrbital Transposerは、バーチャルカメラの Followターゲットから可変の距離でUnityのカメラを移動させます。
Orbital TransposerのBinding Mode
にもいくつか種類があって、これは好みで選んで下さい。動きが少しずつ異なります。
私はLock To Target With World Up
がなんとなく好みで使ってます。
Aim
Aim プロパティーはバーチャルカメラの回転をどのように行うかを設定するものです。
Look At ターゲットをカメラフレーム内に維持するComposer
を利用します。
Noise
せっかくなのでNoiseにも言及します。 これはカメラの振動をシミュレートすることができます。 手振れや混乱、ドスンとする表現にも使えます。
動かしてみる
以上の設定で冒頭のTPS視点のカメラワークになりました。
マウスを左右に動かすと水平方向にカメラの位置が回転します。
Cinemachineをスクリプトで拡張する
TPS視点のマウス操作で、水平方向の操作に加えて(個人的に)欲しいのは次の2点です。
- 垂直方向のカメラの操作
- ホイールで前後に動ける
この2つのスクリプトを書いて拡張します。
VirtualCameraオブジェクトにスクリプトを追加する。
using UnityEngine;
using Cinemachine;
[RequireComponent(typeof(CinemachineVirtualCamera))]
public class VirtualCameraController : MonoBehaviour
{
private CinemachineVirtualCamera virtualCamera;
private CinemachineOrbitalTransposer orbitalTransposer;
private Vector2 lastMousePosition;
// カメラの角度を格納する変数(初期値に0,0を代入)
private Vector2 cameraAngle = new Vector2(0, 0);
public float forwardSpeed;
public float riseSpeed;
void Start()
{
this.virtualCamera = this.GetComponent<CinemachineVirtualCamera>();
this.orbitalTransposer = this.virtualCamera.GetComponentInChildren<CinemachineOrbitalTransposer>();
}
// Update is called once per frame
void Update()
{
forwardViewPoint();
heightViewPoint();
}
// 前後のカメラ操作
private void forwardViewPoint()
{
// マウスホイールの回転値を変数 scroll に渡す
float scroll = Input.GetAxis("Mouse ScrollWheel");
Vector3 offset = this.virtualCamera.transform.forward * scroll * forwardSpeed;
orbitalTransposer.m_FollowOffset -= offset;
Debug.Log(offset.ToString());
}
// 垂直方向のカメラ操作
private void heightViewPoint()
{
// 左クリックした時
if (Input.GetMouseButtonDown(0))
{
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
// 左ドラッグしている間
else if (Input.GetMouseButton(0))
{
float y = (lastMousePosition.y - Input.mousePosition.y);
orbitalTransposer.m_FollowOffset.y += y * riseSpeed;
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
}
}
Cinemachine
のクラスを利用するため名前空間を追加。
[RequireComponent(typeof(CinemachineVirtualCamera))]
はスクリプトをアタッチするオブジェクトにCinemachineVirtualCamera
のアタッチも要求します。
forwardViewPoint()
では前後のカメラ操作を制御します。
マウスホイールの前後に合わせてOrbital Transposer
の位置を更新します。
this.virtualCamera.transform.forward
がカメラの方向ベクトルです。
heightViewPoint()
はカメラの高さを制御します。
マウスをドラッグして視点の高さを動かせる。
lastMousePosition
とマウスの上下の差分が、Orbital Transposer
のzの値を下げるか上げるか決めます。
▼イメージ通りの動きになってきました!
ホイールがスマホの拡大みたいにカクつくのと、水平方向の操作がマウスの絶対位置になってしまってるのでドラッグしたときに条件付けしたい。
おまけ、Cinemachineを使わずスクリプトで実現しようとすると
始めはCinemahineを使わずにTPS視点をスクリプトで実現しようとしてました。 結構いい感じにはなりましたが、水平方向が傾いたり動きがぎこちなかったりと細部を納得できるまで作りこむのはかなり大変だと感じました。
▼作ってみたスクリプトのカメラワーク。 マウスをドラッグして視点を動かせる。 垂直な軸の回転がうまくできておらず、横で動かすと水平軸が傾いてしまう。
一応、視点操作のリバースとFPS視点への切り替えも作りこんでいる。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraControll : MonoBehaviour
{
public Camera mainCamera;
public GameObject playerObject;
private GameObject cameraControll;
public GameObject tpsViewPoint;
public GameObject fpsViewPoint;
// カメラの回転速度を格納する変数
public Vector2 fpsRotationSpeed;
public Vector2 tpsRotationSpeed;
// カメラの前後の移動の速度
public float tpsForwardSpeed;
// マウスホイールの回転値を格納する変数
private float scroll;
// マウス移動方向とカメラ回転方向を反転する判定フラグ
public bool isReversed;
// true: 三人称視点(TPS)。false: 一人称視点(FPS)。
public bool isTPS;
// マウス座標を格納する変数
private Vector2 lastMousePosition;
// カメラの角度を格納する変数(初期値に0,0を代入)
private Vector2 cameraAngle = new Vector2(0, 0);
//呼び出し時に実行される関数
void Start()
{
//メインカメラとユニティちゃんをそれぞれ取得
cameraControll = GameObject.Find("CameraController");
setViewPoint();
}
//単位時間ごとに実行される関数
void Update()
{
//rotateCameraの呼び出し
rotateCamera();
trakingCamera();
}
//カメラを回転させる関数
private void rotateCamera()
{
toggleViewPoint();
if (isTPS)
{
thirdPersonShooter();
}
else
{
firstPersonShooter();
}
}
// F5キーでTPS, FPSの切り替え
private void toggleViewPoint()
{
if (Input.GetKeyDown(KeyCode.F5))
{
setViewPoint();
}
}
// 視点をセットする
private void setViewPoint()
{
isTPS = !isTPS;
// 視点切り替え
if (isTPS)
{
// TPS視点に初期化
cameraAngle = Vector2.zero;
mainCamera.transform.localEulerAngles = cameraAngle;
mainCamera.transform.position = tpsViewPoint.transform.position;
}
else
{
// FPS視点に初期化
cameraAngle = Vector2.zero;
mainCamera.transform.localEulerAngles = cameraAngle;
mainCamera.transform.position = fpsViewPoint.transform.position;
}
Debug.Log(mainCamera.transform.position.ToString());
}
// 一人称視点のカメラ
private void firstPersonShooter()
{
// 左クリックした時
if (Input.GetMouseButtonDown(0))
{
// カメラの角度を変数"cameraAngle"に格納
cameraAngle = mainCamera.transform.localEulerAngles;
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
// 左ドラッグしている間
else if (Input.GetMouseButton(0))
{
float reverse = isReversed ? -1f : 1f;
// Y軸の回転:マウスドラッグ方向に視点回転
// マウスの水平移動値に変数"rotationSpeed"を掛ける
//(クリック時の座標とマウス座標の現在値の差分値)
cameraAngle.y -= (Input.mousePosition.x - lastMousePosition.x) * reverse * fpsRotationSpeed.y;
cameraAngle.x -= (lastMousePosition.y - Input.mousePosition.y) * reverse * fpsRotationSpeed.x;
// カメラアングルの制限
cameraAngle.y = cameraAngle.y > 80 ? 80 : cameraAngle.y;
cameraAngle.y = cameraAngle.y < -80 ? -80 : cameraAngle.y;
cameraAngle.x = cameraAngle.x > 80 ? 80 : cameraAngle.x;
cameraAngle.x = cameraAngle.x < -80 ? -80 : cameraAngle.x;
// "cameraAngle"の角度をカメラ角度に格納
mainCamera.transform.localEulerAngles = cameraAngle;
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
}
private void thirdPersonShooter()
{
// マウスホイールの回転値を変数 scroll に渡す
scroll = Input.GetAxis("Mouse ScrollWheel");
mainCamera.transform.position += mainCamera.transform.forward * scroll * tpsForwardSpeed;
// 左クリックした時
if (Input.GetMouseButtonDown(0))
{
// カメラの角度を変数"cameraAngle"に格納
cameraAngle = mainCamera.transform.localEulerAngles;
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
// 左ドラッグしている間
else if (Input.GetMouseButton(0))
{
float reverse = isReversed ? -1f : 1f;
float x = (Input.mousePosition.x - lastMousePosition.x) * reverse;
float y = (lastMousePosition.y - Input.mousePosition.y) * reverse;
if (Mathf.Abs(x) < Mathf.Abs(y))
x = 0;
else
y = 0;
Vector3 angle = Vector3.zero;
angle.x -= x * tpsRotationSpeed.x;
angle.y -= y * tpsRotationSpeed.y;
// カメラアングルの制限
angle.y = angle.y > 80 ? 80 : angle.y;
angle.y = angle.y < -80 ? -80 : angle.y;
// "cameraAngle"の角度をカメラ角度に格納
mainCamera.transform.RotateAround(playerObject.transform.position, Vector3.up, angle.x);
mainCamera.transform.RotateAround(playerObject.transform.position, transform.right, angle.y);
// マウス座標を変数"lastMousePosition"に格納
lastMousePosition = Input.mousePosition;
}
}
// Playerを追跡するカメラ
private void trakingCamera()
{
cameraControll.transform.position = playerObject.transform.position;
cameraControll.transform.rotation = playerObject.transform.rotation;
// playerObject.transform.rotation = mainCamera.transform.rotation;
}
}
前フレームのマウスの位置lastMousePosition
と現在のマウスの位置の差分だけX,Y軸方向のアングルを変えることでマウスと連動したカメラワークを実現してます。
TPS視点はアングルをキャラクター方向に向けつつ、transform.RotateAround()
でプレイヤーを中心に回転している。
FPS視点はカメラを動かさなくて良いため、アングルだけマウスに合わせて向きを変える。
▼Inspecterと設定。
CameraController
をキャラクターに追従させて、子オブジェクトのViewPointにメインカメラの位置を切り替えている。