先上效果圖
該相機控制器目前有以下功能:
首先要使相機跟隨主角,只需要獲取到主角位置,然後挪動相機到該位置即可。
之後我們希望相機在移動的過程中有一個平滑的移動,像是相機在一直追趕主角的效果,於是使用Vector2.Lerp去做一個插值移動。
目前相機已經可以跟隨主角移動,但是隻要主角一動,相機便會跟著動,我們希望主角能夠有一個自由活動的空間,在這個範圍內,相機不會跟隨主角,如下圖所示:
這我們只需要設定一個範圍,只要主角跑出去,便執行相機跟隨,直到相機的X座標與角色的X座標之差小於一個較小的值為止,便認為角色已位於相機中心。
一般來說場景都有一個邊界,角色抵達邊界的時候,相機不應該拍到邊界之外的物體。
如上圖,我們只希望相機在紅框範圍內移動,並且只希望相機在X軸上移動。
於是在我們希望的極限位置上設定四個標記物體,並且為相機新增BoxCollider2D元件求出相機的邊界座標,只要相機抵達邊界,便不在讓其移動。
最後使用DOTween新增一個常用到的震屏效果。
using DG.Tweening;
using UnityEngine;
[RequireComponent(typeof(BoxCollider2D))]
public class MainCameraController : MonoBehaviour
{
private Camera mainCamera; //獲取主相機
private Transform playerPos; //獲取主角位置
private BoxCollider2D boxCol; //Trigger, 用來計算相機邊界位置
private float cameraHalfWidth; //相機半長
private float cameraHalfHeight; //相機半高
public Vector2 centerOffset; //中心位置的偏移
[Range(0, 4)] public float centerArea; //主角自由活動位置,此範圍相機不跟隨
public float moveSpeedX; //X跟進速度
public float moveSpeedY; //Y跟進速度
[Header("Raw Froze")]
public bool frozeX = false; //鎖住X軸移動
public bool frozeY = false; //鎖住Y軸移動
[Header("Camera Limit")]
//場景四方向限制,Transfrom用來獲取極限位置座標,隨後Destroy掉
public Transform leftLimitTrans;
public Transform rightLimitTrans;
public Transform topLimitTrans;
public Transform bottomLimitTrans;
private float leftLimit = 0;
private float rightLimit = 0;
private float topLimit = 0;
private float bottomLimit = 0;
//DOTween震動相關引數
[Header("Camera Shake")]
public float shakeDuration = 0.5f; //震動週期
public float shakeStrength = 0.2f; //震動強度
public int shakeVibrato = 15; //震動次數
[Range(0, 180)] public float shakeRandomness = 180; //震動隨機性 0-180
public bool shakeFadeOut = true; //是否淡出
public Ease shakeEase = Ease.InExpo; //動畫曲線
//為所有指令碼提供的靜態震動入口
private static bool shake = false;
private void Awake()
{
//各元件的獲取
mainCamera = Camera.main;
playerPos = GameObject.FindGameObjectWithTag("Player").transform;
#if UNITY_EDITOR
if (mainCamera == null)
{
Debug.LogWarning("Main camera is null!");
enabled = false;
return;
}
if (playerPos == null)
{
Debug.LogWarning("Player can`t be find by tag 'Player'!");
enabled = false;
return;
}
else
{
Debug.Log("MainCamera follow player object named " + playerPos.gameObject.name);
}
#endif
//獲取Trigger, 並計算相機半長、半寬
boxCol = GetComponent<BoxCollider2D>();
cameraHalfWidth = boxCol.size.x * 0.5f;
cameraHalfHeight = boxCol.size.y * 0.5f;
boxCol.enabled = false;
InitLimit(); //↓↓↓
}
InitLimit()
//設定四周極限距離,然後銷燬
//無則設定為 最大/最小
private void InitLimit()
{
if (leftLimitTrans != null)
{
leftLimit = leftLimitTrans.position.x;
Destroy(leftLimitTrans.gameObject);
}
else
leftLimit = int.MinValue;
if (topLimitTrans != null)
{
topLimit = topLimitTrans.position.y;
Destroy(topLimitTrans.gameObject);
}
else
topLimit = int.MaxValue;
//bottom和right 略
}
private void LateUpdate()
{
//相機跟隨角色
CameraFollow((Vector2)playerPos.position + centerOffset);
//相機活動限制
CameraLimit();
//如果shake為true則執行一次震屏
if (shake) ShakeCameraHandler();
}
//若角色超出自由活動範圍,則followX設定為true,使相機跟隨角色
//若角色到達相機視野中央,則followX設定為false
private bool followX = false;
private void CameraFollow(Vector2 targetPos)
{
float currentX = mainCamera.transform.position.x;
float currentY = mainCamera.transform.position.y;
//未鎖住X移動
if (!frozeX)
{
//判斷角色是否超出自由活動範圍,是則設定follow為true開始x軸的跟隨
//可簡化程式碼
if (targetPos.x > currentX + centerArea ||
targetPos.x < currentX - centerArea)
{
followX = true;
}
//執行x軸的跟隨
if (followX)
{
//求相機這一幀的位置
currentX = Mathf.Lerp(currentX, targetPos.x, moveSpeedX);
//作差小於0.1f認為角色到達視野中央,則停止跟隨
if (Mathf.Abs(targetPos.x - currentX) < 0.1f)
{
followX = false;
}
}
}
//未鎖住Y軸移動
if (!frozeY)
{
currentY = Mathf.Lerp(currentY, targetPos.y, moveSpeedY);
}
//設定相機位置,Z軸不變
mainCamera.transform.position = new Vector3(
currentX, currentY, mainCamera.transform.position.z);
}
//邊界限制
private void CameraLimit()
{
float posX = mainCamera.transform.position.x;
float posY = mainCamera.transform.position.y;
/*
以posX為例, LeftBoard()求出相機左邊界,判斷是否超出左極限
是 則posX等於左極限 + 相機半寬
否 則判斷右邊界是否超出右極限
是 則posX等於右極限 - 相機半寬
否 則posX不變,等於自身
*/
posX = LeftBoard() < leftLimit ? leftLimit + cameraHalfWidth :
RightBoard() > rightLimit ? rightLimit - cameraHalfWidth : posX;
posY = BottomBoard() < bottomLimit ? bottomLimit + cameraHalfHeight :
TopBoard() > topLimit ? topLimit - cameraHalfHeight : posY;
mainCamera.transform.position = new Vector3(
posX, posY,
mainCamera.transform.position.z
);
}
#if UNITY_EDITOR
//在Inspector視窗測試震動
[ContextMenu("RunMode-ShakeCamera")]
public void ShakeCameraInEditor()
{
ShakeCamera();
}
#endif
//對外暴露的介面,設定shake為true,在LateUpdate中執行震動
public static void ShakeCamera()
{
shake = true;
}
//shake為true時在LateUpdate中呼叫
private void ShakeCameraHandler()
{
shake = false;
/*
(震動週期
震動力度
震動次數
震動隨機性
是否淡出).
(動畫曲線)
*/
Camera.main.DOShakePosition(
shakeDuration,
shakeStrength,
shakeVibrato,
shakeRandomness,
shakeFadeOut
).SetEase(shakeEase);
}
//獲取相機邊界位置
private float LeftBoard()
{
return mainCamera.transform.position.x - cameraHalfWidth;
}
private float RightBoard()
{
return mainCamera.transform.position.x + cameraHalfWidth;
}
private float TopBoard()
{
return mainCamera.transform.position.y + cameraHalfHeight;
}
private float BottomBoard()
{
return mainCamera.transform.position.y - cameraHalfHeight;
}
}
如有錯誤,懇請糾正
下接: