先上效果圖dom
該相機控制器目前有如下功能:
ide
- 跟隨主角移動
- 平滑移動,可設置x軸y軸的跟進速度
- 主角在一段距離內自由活動可不影響相機跟進
- 相機在場景四周的活動限制,以及x軸和y軸的移動限制
- 震屏
1、總體思路
首先要使相機跟隨主角,只須要獲取到主角位置,而後挪動相機到該位置便可。
以後咱們但願相機在移動的過程當中有一個平滑的移動,像是相機在一直追趕主角的效果,因而使用Vector2.Lerp去作一個插值移動。
測試
目前相機已經能夠跟隨主角移動,可是隻要主角一動,相機便會跟着動,咱們但願主角可以有一個自由活動的空間,在這個範圍內,相機不會跟隨主角,以下圖所示:
這咱們只須要設置一個範圍,只要主角跑出去,便執行相機跟隨,直到相機的X座標與角色的X座標之差小於一個較小的值爲止,便認爲角色已位於相機中心。
動畫
通常來講場景都有一個邊界,角色抵達邊界的時候,相機不該該拍到邊界以外的物體。
如上圖,咱們只但願相機在紅框範圍內移動,而且只但願相機在X軸上移動。
因而在咱們但願的極限位置上設置四個標記物體,而且爲相機添加BoxCollider2D組件求出相機的邊界座標,只要相機抵達邊界,便不在讓其移動。
ui
最後使用DOTween添加一個經常使用到的震屏效果。spa
2、代碼實現
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()code
//設置四周極限距離,而後銷燬 //無則設置爲 最大/最小 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; } }
若有錯誤,懇請糾正
下接:
orm