先上效果圖

該相機控制器目前有以下功能:
- 跟隨主角移動
- 平滑移動,可設定x軸y軸的跟進速度
- 主角在一段距離內自由活動可不影響相機跟進
- 相機在場景四周的活動限制,以及x軸和y軸的移動限制
- 震屏
一、整體思路
首先要使相機跟隨主角,只需要獲取到主角位置,然后挪動相機到該位置即可,
之后我們希望相機在移動的程序中有一個平滑的移動,像是相機在一直追趕主角的效果,于是使用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;
}
}
如有錯誤,懇請糾正
下接:
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/213455.html
標籤:其他
上一篇:分析整裝臺式機是否物有所值
