Unity チュートリアルのタワーディフェンステンプレートを触ってみる(51)
Unity チュートリアルのタワーディフェンステンプレートを触ってみる(50)では「カメラ編」を行った。今回は GameCamera に付属しているコンポーネントである、CameraRig について解説を行っていく。
1.カメラ編 – CameraRig.cs
CameraRig.cs について稚拙ながら解説
「CameraRig.cs」は「Assets/Scripts/Core/Camera/CameraRig.cs」の指しておりスクリプトについては以下の通り。内容としては を行っている。
[cce_csharp]using Core.Input;
using UnityEngine;
namespace Core.Camera
{
/// <summary>
/// Class to control the camera's behaviour. Camera rig currently operates best on terrain that is mostly on
/// a single plane
/// </summary>
public class CameraRig : MonoBehaviour
{
/// <summary>
/// Look dampening factor
/// </summary>
public float lookDampFactor;
/// <summary>
/// Movement dampening factor
/// </summary>
public float movementDampFactor;
/// <summary>
/// Nearest zoom level - can go a bit further than this on touch, for springiness
/// </summary>
public float nearestZoom = 15;
/// <summary>
/// Furthest zoom level - can go a bit further than this on touch, for springiness
/// </summary>
public float furthestZoom = 40;
/// <summary>
/// True maximum zoom level
/// </summary>
public float maxZoom = 60;
/// <summary>
/// Logarithm used to decay zoom beyond furthest
/// </summary>
public float zoomLogFactor = 10;
/// <summary>
/// How fast zoom recovers to normal
/// </summary>
public float zoomRecoverSpeed = 20;
/// <summary>
/// Y-height of the floor the camera is assuming
/// </summary>
public float floorY;
/// <summary>
/// Camera angle when fully zoomed in
/// </summary>
public Transform zoomedCamAngle;
/// <summary>
/// Map size, edited through the CameraRigEditor script in edit mode
/// </summary>
[HideInInspector]
public Rect mapSize = new Rect(-10, -10, 20, 20);
/// <summary>
/// Is the zoom able to exceed its normal zoom extents with a rubber banding effect
/// </summary>
public bool springyZoom = true;
/// <summary>
/// Current look velocity of camera
/// </summary>
Vector3 m_CurrentLookVelocity;
/// <summary>
/// Rotations of camera at various zoom levels
/// </summary>
Quaternion m_MinZoomRotation;
Quaternion m_MaxZoomRotation;
/// <summary>
/// Current camera velocity
/// </summary>
Vector3 m_CurrentCamVelocity;
/// <summary>
/// Current reusable floor plane
/// </summary>
Plane m_FloorPlane;
public Plane floorPlane
{
get { return m_FloorPlane; }
}
/// <summary>
/// Target position on the grid that we're looking at
/// </summary>
public Vector3 lookPosition { get; private set; }
/// <summary>
/// Current look position of camera
/// </summary>
public Vector3 currentLookPosition { get; private set; }
/// <summary>
/// Target position of the camera
/// </summary>
public Vector3 cameraPosition { get; private set; }
/// <summary>
/// Bounds of our look area, related to map size, zoom level and aspect ratio/screen size
/// </summary>
public Rect lookBounds { get; private set; }
/// <summary>
/// Gets our current zoom distance
/// </summary>
public float zoomDist { get; private set; }
/// <summary>
/// Gets our current internal zoom distance, before clamping and scaling is applied
/// </summary>
public float rawZoomDist { get; private set; }
/// <summary>
/// Gets the unit we're tracking if any
/// </summary>
public GameObject trackingObject { get; private set; }
/// <summary>
/// Cached camera component
/// </summary>
public UnityEngine.Camera cachedCamera { get; private set; }
/// <summary>
/// Initialize references and floor plane
/// </summary>
protected virtual void Awake()
{
cachedCamera = GetComponent<UnityEngine.Camera>();
m_FloorPlane = new Plane(Vector3.up, new Vector3(0.0f, floorY, 0.0f));
// Set initial values
var lookRay = new Ray(cachedCamera.transform.position, cachedCamera.transform.forward);
float dist;
if (m_FloorPlane.Raycast(lookRay, out dist))
{
currentLookPosition = lookPosition = lookRay.GetPoint(dist);
}
cameraPosition = cachedCamera.transform.position;
m_MinZoomRotation = Quaternion.FromToRotation(Vector3.up, -cachedCamera.transform.forward);
m_MaxZoomRotation = Quaternion.FromToRotation(Vector3.up, -zoomedCamAngle.transform.forward);
rawZoomDist = zoomDist = (currentLookPosition - cameraPosition).magnitude;
}
/// <summary>
/// Setup initial zoom level and camera bounds
/// </summary>
protected virtual void Start()
{
RecalculateBoundingRect();
}
/// <summary>
/// Handle camera behaviour
/// </summary>
protected virtual void Update()
{
RecalculateBoundingRect();
// Tracking?
if (trackingObject != null)
{
PanTo(trackingObject.transform.position);
if (!trackingObject.activeInHierarchy)
{
StopTracking();
}
}
// Approach look position
currentLookPosition = Vector3.SmoothDamp(currentLookPosition, lookPosition, ref m_CurrentLookVelocity,
lookDampFactor);
Vector3 worldPos = transform.position;
worldPos = Vector3.SmoothDamp(worldPos, cameraPosition, ref m_CurrentCamVelocity,
movementDampFactor);
transform.position = worldPos;
transform.LookAt(currentLookPosition);
}
#if UNITY_EDITOR
/// <summary>
/// Debug bounds area gizmo
/// </summary>
void OnDrawGizmosSelected()
{
// We dont want to display this in edit mode
if (!Application.isPlaying)
{
return;
}
if (cachedCamera == null)
{
cachedCamera = GetComponent<UnityEngine.Camera>();
}
RecalculateBoundingRect();
Gizmos.color = Color.red;
Gizmos.DrawLine(
new Vector3(lookBounds.xMin, 0.0f, lookBounds.yMin),
new Vector3(lookBounds.xMax, 0.0f, lookBounds.yMin));
Gizmos.DrawLine(
new Vector3(lookBounds.xMin, 0.0f, lookBounds.yMin),
new Vector3(lookBounds.xMin, 0.0f, lookBounds.yMax));
Gizmos.DrawLine(
new Vector3(lookBounds.xMax, 0.0f, lookBounds.yMax),
new Vector3(lookBounds.xMin, 0.0f, lookBounds.yMax));
Gizmos.DrawLine(
new Vector3(lookBounds.xMax, 0.0f, lookBounds.yMax),
new Vector3(lookBounds.xMax, 0.0f, lookBounds.yMin));
Gizmos.color = Color.yellow;
Gizmos.DrawLine(transform.position, currentLookPosition);
}
#endif
/// <summary>
/// Pans the camera to a specific position
/// </summary>
/// <param name="position">The look target</param>
public void PanTo(Vector3 position)
{
Vector3 pos = position;
// Look position is floor height
pos.y = floorY;
// Clamp to look bounds
pos.x = Mathf.Clamp(pos.x, lookBounds.xMin, lookBounds.xMax);
pos.z = Mathf.Clamp(pos.z, lookBounds.yMin, lookBounds.yMax);
lookPosition = pos;
// Camera position calculated from look position with view vector and zoom dist
cameraPosition = lookPosition + (GetToCamVector() * zoomDist);
}
/// <summary>
/// Cause the camera to follow a unit
/// </summary>
/// <param name="objectToTrack"></param>
public void TrackObject(GameObject objectToTrack)
{
trackingObject = objectToTrack;
PanTo(trackingObject.transform.position);
}
/// <summary>
/// Stop tracking a unit
/// </summary>
public void StopTracking()
{
trackingObject = null;
}
/// <summary>
/// Pan the camera
/// </summary>
/// <param name="panDelta">How far to pan the camera, in world space units</param>
public void PanCamera(Vector3 panDelta)
{
Vector3 pos = lookPosition;
pos += panDelta;
// Clamp to look bounds
pos.x = Mathf.Clamp(pos.x, lookBounds.xMin, lookBounds.xMax);
pos.z = Mathf.Clamp(pos.z, lookBounds.yMin, lookBounds.yMax);
lookPosition = pos;
// Camera position calculated from look position with view vector and zoom dist
cameraPosition = lookPosition + (GetToCamVector() * zoomDist);
}
/// <summary>
/// Zoom the camera by a specified value
/// </summary>
/// <param name="zoomDelta">How far to zoom the camera</param>
public void ZoomCameraRelative(float zoomDelta)
{
SetZoom(rawZoomDist + zoomDelta);
}
/// <summary>
/// Zoom the camera to a specified value
/// </summary>
/// <param name="newZoom">The absolute zoom value</param>
public void SetZoom(float newZoom)
{
if (springyZoom)
{
rawZoomDist = newZoom;
if (newZoom > furthestZoom)
{
zoomDist = furthestZoom;
zoomDist += Mathf.Log((Mathf.Min(rawZoomDist, maxZoom) - furthestZoom) + 1, zoomLogFactor);
}
else if (rawZoomDist < nearestZoom)
{
zoomDist = nearestZoom;
zoomDist -= Mathf.Log((nearestZoom - rawZoomDist) + 1, zoomLogFactor);
}
else
{
zoomDist = rawZoomDist;
}
}
else
{
zoomDist = rawZoomDist = Mathf.Clamp(newZoom, nearestZoom, furthestZoom);
}
// Update bounding rectangle, which is based on our zoom level
RecalculateBoundingRect();
// Force recalculated CameraPosition
PanCamera(Vector3.zero);
}
/// <summary>
/// Calculates the ray for a specified pointer in 3d space
/// </summary>
/// <param name="pointer">The pointer info</param>
/// <returns>The ray representing a screen-space pointer in 3D space</returns>
public Ray GetRayForPointer(PointerInfo pointer)
{
return cachedCamera.ScreenPointToRay(pointer.currentPosition);
}
/// <summary>
/// Gets the screen position of a given world position
/// </summary>
/// <param name="worldPos">The world position</param>
/// <returns>The screen position of that point</returns>
public Vector3 GetScreenPos(Vector3 worldPos)
{
return cachedCamera.WorldToScreenPoint(worldPos);
}
/// <summary>
/// Decay the zoom if it's beyond its zoom limits, for springiness
/// </summary>
public void ZoomDecay()
{
if (springyZoom)
{
if (rawZoomDist > furthestZoom)
{
float recover = rawZoomDist - furthestZoom;
SetZoom(Mathf.Max(furthestZoom, rawZoomDist - (recover * zoomRecoverSpeed * Time.deltaTime)));
}
else if (rawZoomDist < nearestZoom)
{
float recover = nearestZoom - rawZoomDist;
SetZoom(Mathf.Min(nearestZoom, rawZoomDist + (recover * zoomRecoverSpeed * Time.deltaTime)));
}
}
}
/// <summary>
/// Returns our normalized zoom ratio
/// </summary>
public float CalculateZoomRatio()
{
return Mathf.Clamp01(Mathf.InverseLerp(nearestZoom, furthestZoom, zoomDist));
}
/// <summary>
/// Gets the to camera vector based on our current zoom level
/// </summary>
Vector3 GetToCamVector()
{
float t = Mathf.Clamp01((zoomDist - nearestZoom) / (furthestZoom - nearestZoom));
t = 1 - ((1 - t) * (1 - t));
Quaternion interpolatedRotation = Quaternion.Slerp(
m_MaxZoomRotation, m_MinZoomRotation,
t);
return interpolatedRotation * Vector3.up;
}
/// <summary>
/// Update the size of our camera's bounding rectangle
/// </summary>
void RecalculateBoundingRect()
{
Rect mapsize = mapSize;
// Get some world space projections at this zoom level
// Temporarily move camera to final look position
Vector3 prevCameraPos = transform.position;
transform.position = cameraPosition;
transform.LookAt(lookPosition);
// Project screen corners and center
var bottomLeftScreen = new Vector3(0, 0);
var topLeftScreen = new Vector3(0, Screen.height);
var centerScreen = new Vector3(Screen.width * 0.5f, Screen.height * 0.5f);
Vector3 bottomLeftWorld = Vector3.zero;
Vector3 topLeftWorld = Vector3.zero;
Vector3 centerWorld = Vector3.zero;
float dist;
Ray ray = cachedCamera.ScreenPointToRay(bottomLeftScreen);
if (m_FloorPlane.Raycast(ray, out dist))
{
bottomLeftWorld = ray.GetPoint(dist);
}
ray = cachedCamera.ScreenPointToRay(topLeftScreen);
if (m_FloorPlane.Raycast(ray, out dist))
{
topLeftWorld = ray.GetPoint(dist);
}
ray = cachedCamera.ScreenPointToRay(centerScreen);
if (m_FloorPlane.Raycast(ray, out dist))
{
centerWorld = ray.GetPoint(dist);
}
Vector3 toTopLeft = topLeftWorld - centerWorld;
Vector3 toBottomLeft = bottomLeftWorld - centerWorld;
lookBounds = new Rect(
mapsize.xMin - toBottomLeft.x,
mapsize.yMin - toBottomLeft.z,
Mathf.Max(mapsize.width + (toBottomLeft.x * 2), 0),
Mathf.Max((mapsize.height - toTopLeft.z) + toBottomLeft.z, 0));
// Restore camera position
transform.position = prevCameraPos;
transform.LookAt(currentLookPosition);
}
}
}[/cce_csharp]
137行目 : 「protected virtual void Awake()」は Unity 固有の起動処理を行っている。内容としては Plane と Ray を生成し、その2つの距離を計算する。その後、最大と最小のズームの設定と距離の計算を行っている。
160行目 : 「protected virtual void Start()」は Unity 固有の開始処理を行っている。内容としては RecalculateBoundingRect を実行している。
168行目 : 「protected virtual void Update()」は Unity 固有の更新処理を行っている。内容としては RecalculateBoundingRect を実行後、trackingObject に設定があり、かつ trackingObject がヒエラルキー上で有効であれば StopTracking している。その後、現在の currentLookPosition と worldPos を取得し、自身の position と LookAt に設定している。
237行目 : 「public void PanTo(Vector3 position)」は引数で与えられた position にカメラを合わせる処理を行っている。
257行目 : 「public void TrackObject(GameObject objectToTrack)」は引数で与えられた GameObject を trackingObject として設定後、PanTo を実行している。
266行目 : 「public void StopTracking()」は trackingObject に対して null を設定している。
275行目 : 「public void PanCamera(Vector3 panDelta)」は引数で与えられた panDelta 分カメラをずらす処理を行っている。
293行目 : 「public void ZoomCameraRelative(float zoomDelta)」は SetZoom を実行している。
302行目 : 「public void SetZoom(float newZoom)」はズーム距離の設定を行っている。内容としては springyZoom が True であり、 最遠・最近以内にあればその値をズームの距離として設定し、それ以外であればもしくは最遠・最近としている。その後、RecalculateBoundingRect と PanCamera を実行している。
340行目 : 「public Ray GetRayForPointer(PointerInfo pointer)」はカメラの ScreenPointToRay を実行している。
350行目 : 「public Vector3 GetScreenPos(Vector3 worldPos)」は世界座標系を画面座標系へ変換している。
358行目 : 「public void ZoomDecay()」は springyZoom が True であれば、カメラのズームを上限、下限以内に設定している。
378行目 : 「public float CalculateZoomRatio()」はズーム距離を正規化している。
386行目 : 「Vector3 GetToCamVector()」はカメラのベクトルを取得している。
399行目 : 「void RecalculateBoundingRect()」はカメラの外接矩形のサイズを更新している。内容としては現在のカメラ位置とスクリーンの下左、上右、中心の座標を取得後、スクリーンの各座標を世界座標に変換し、矩形の作成を行っている。