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()」はカメラの外接矩形のサイズを更新している。内容としては現在のカメラ位置とスクリーンの下左、上右、中心の座標を取得後、スクリーンの各座標を世界座標に変換し、矩形の作成を行っている。