Unity實現一個基本的2D相機控制器-01

2020-11-13 23:00:44

先上效果圖

Unity2D相機
該相機控制器目前有以下功能:

  1. 跟隨主角移動
  2. 平滑移動,可設定x軸y軸的跟進速度
  3. 主角在一段距離內自由活動可不影響相機跟進
  4. 相機在場景四周的活動限制,以及x軸和y軸的移動限制
  5. 震屏

一、整體思路

首先要使相機跟隨主角,只需要獲取到主角位置,然後挪動相機到該位置即可。
之後我們希望相機在移動的過程中有一個平滑的移動,像是相機在一直追趕主角的效果,於是使用Vector2.Lerp去做一個插值移動。

目前相機已經可以跟隨主角移動,但是隻要主角一動,相機便會跟著動,我們希望主角能夠有一個自由活動的空間,在這個範圍內,相機不會跟隨主角,如下圖所示:
Unity2D相機
這我們只需要設定一個範圍,只要主角跑出去,便執行相機跟隨,直到相機的X座標與角色的X座標之差小於一個較小的值為止,便認為角色已位於相機中心。

一般來說場景都有一個邊界,角色抵達邊界的時候,相機不應該拍到邊界之外的物體。
2D相機
如上圖,我們只希望相機在紅框範圍內移動,並且只希望相機在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;
    }
}

如有錯誤,懇請糾正
下接: