チュートリアルから学ぶカメラの実装方法の違い

[公式チュートリアル](https://unity3d.com/jp/learn/tutorials "公式チュートリアル")を通じて学んだ内容をアプトプットしつつ、備忘録として残していきます。

今回は各チュートリアルのカメラの実装方法についてまとめていきます。

#[Roll a Ball](https://unity3d.com/jp/learn/tutorials/projects/roll-ball-tutorial)（玉転がしゲーム）
プレイヤーから一定距離を保ち見下ろす形で追尾するシンプルなカメラ動作となっており、
回転やズーム処理などもないためスクリプトも簡素なものとなっています。

```c#:CameraController.cs
public class CameraController : MonoBehaviour
{
	public GameObject player; 	// 操作対象のボール

	private Vector3 offset;		// カメラからボールまでの距離、
								// カメラは常にボールからoffset分の距離を保つことになる

	void Start ()
	{
		// ボールの初期位置からoffsetを初期化する
		offset = transform.position - player.transform.position;
	}

	void LateUpdate ()
	{
		// （移動後の）ボールの位置を元にカメラの位置を移動する
		transform.position = player.transform.position + offset;		
	}
}
```

#[Tanks!](https://unity3d.com/jp/learn/tutorials/projects/tanks-tutorial)（対戦型の戦車ゲーム）
一つの画面内で2台の戦車をおさめる必要があるため、
2台の中間地点を中心にカメラが移動し、画面内に表示が治らない場合はズームアウトする動作となっています。

カメラのズーム処理についてはコード量も少なく複雑な計算はしておりませんが、
ローカル空間への理解が必要になるため前提知識がない場合、なかなか理解できない可能性が高いです。
Tutorialの動画で図解で説明されているため詳細は割愛しますが、
繰り返し確認して不明点を洗い出すことをお勧めします。

座標系で詰まった時は自分でも図解して整理することが効果的です。
<img width="800" alt="カメラリグのローカル空間" src="https://qiita-image-store.s3.amazonaws.com/0/55307/986002e0-cbbb-f8c7-3883-481510e6bee7.png">

```c#:CameraControl.cs
using UnityEngine;

public class CameraControl : MonoBehaviour
{
	public float m_DampTime = 0.2f;                 // カメラが目標に到達するまでの時間
	public float m_ScreenEdgeBuffer = 4f;           // 戦車が画面端に貼り付かないようにするために設定する余地
	public float m_MinSize = 6.5f;                  // カメラが画面に寄り過ぎないようにするために設定する最小値
	[HideInInspector] public Transform[] m_Targets; // カメラにおさめる対象（戦車）を配列で格納する


	private Camera m_Camera;                        // カメラ
	private float m_ZoomSpeed;                      // カメラのズーム速度
	private Vector3 m_MoveVelocity;                 // カメラの移動速度
	private Vector3 m_DesiredPosition;              // カメラが向かっている位置


	private void Awake ()
	{
		m_Camera = GetComponentInChildren<Camera> ();
	}


	private void FixedUpdate ()
	{
		Move ();
		Zoom ();
	}


	private void Move ()
	{
		// 2点間の中央地点を求める
		FindAveragePosition ();

		// 目的座標にカメラリグを移動させる
		transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
	}


	private void FindAveragePosition ()
	{
		Vector3 averagePos = new Vector3 ();
		int numTargets = 0;

		// 残存している全ての戦車の情報を確認し、位置情報を加えていく
		for (int i = 0; i < m_Targets.Length; i++)
		{
			// プレイヤーが死亡している時は戦車が画面内に存在しないためスキップする
			if (!m_Targets[i].gameObject.activeSelf)
				continue;

			// ベクトル変数に戦車の位置情報を加える
			averagePos += m_Targets[i].position;
			numTargets++;
		}

		// 戦車が複数台残っている場合は、中央地点を求める
		if (numTargets > 0)
			averagePos /= numTargets;

		// 戦車やカメラはY座標を移動しないため安全装置として初期値0を維持させる
		averagePos.y = transform.position.y;

		// メンバ変数に目標地点を格納する
		m_DesiredPosition = averagePos;
	}


	private void Zoom ()
	{
		// 複数台の戦車が画面内におさまるためのカメラサイズを求め変更する
		float requiredSize = FindRequiredSize();
		m_Camera.orthographicSize = Mathf.SmoothDamp (m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);
	}


	private float FindRequiredSize ()
	{
		// カメラリグのローカル空間において、カメラの目標地点の座標を取得する
		Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);

		// カメラサイズの初期値
		float size = 0f;

		// 残存している全ての戦車の情報を確認し、位置情報を加えていく
		// 最も離れた場所にある戦車を含んでズームアウトすれば画面内におさまる
		for (int i = 0; i < m_Targets.Length; i++)
		{
			if (!m_Targets[i].gameObject.activeSelf)
				continue;

			// カメラリグのローカル空間における戦車の座標を取得する
			Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);

			// カメラリグのローカル空間において、カメラ移動後の目標地点を原点として戦車の距離を求める
			Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;

			// 現在のカメラサイズとカメラリグのローカル空間における戦車のY軸の距離を比較し大きい方を格納する
			size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));

			// 現在のカメラサイズとカメラリグのローカル空間における戦車のX軸の距離を比較し大きい方を格納する
			size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);
		}

		// カメラの画面端に戦車が画面端に貼り付かないようにするため余地（遊び）を加える
		size += m_ScreenEdgeBuffer;

		// カメラが画面に寄り過ぎないようにするため、カメラサイズの最小値と比較し下回っていたら上書きする
		size = Mathf.Max (size, m_MinSize);

		return size;
	}

	// GameManagerクラスからゲーム開始時に呼び出される
	public void SetStartPositionAndSize ()
	{
		FindAveragePosition ();
		transform.position = m_DesiredPosition;
		m_Camera.orthographicSize = FindRequiredSize ();
	}
}
```

#[Survival Shooter](https://unity3d.com/jp/learn/tutorials/projects/survival-shooter-tutorial)（見下ろし型のガンアクションゲーム）
Roll a Ballと同じくプレイヤーから一定距離を保ち見下ろす形で追尾するシンプルなカメラです。
カメラの移動に関して、LateUpdate()ではなくFixedUpdate()で行っており、
Vector3.Lerp関数を使用して2点間を補完し徐々にカメラが移動するように実装されています。
（体感的にカメラが少し遅れてプレイヤーに追尾する）

```c#:CameraFollow.cs
public class CameraFollow : MonoBehaviour
{
	public Transform target;		// カメラの追尾対象（プレイヤー）の位置
	public float smoothing = 5f;	// カメラの追尾速度

	Vector3 offset;					// カメラからtargetまでの距離、
									// カメラは常にプレイヤーからoffset分の距離を保つことになる

	void Start ()
	{
		// プレイヤーの初期位置からoffsetを初期化する
		offset = transform.position - target.position;
	}


	void FixedUpdate ()
	{
		// プレイヤーの位置から追尾後のカメラ位置を算出する
		Vector3 targetCamPos = target.position + offset;

		// プレイヤーの位置を元にカメラを移動させる
		transform.position = Vector3.Lerp (transform.position, targetCamPos, smoothing * Time.deltaTime);
	}
}
```

---

今後も追加予定です