先上效果图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