美文网首页
Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方

Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方

作者: Danny_Yan | 来源:发表于2019-08-27 17:55 被阅读0次
  1. 使用拖动地图,镜头固定的方式,能对touch点进行精确的拖动,不受透视影响.
  2. 边界控制(可视范围控制)是通过判断FOV的4个点(也是屏幕的4个角)与地图的hit交点是否在指定的范围内来做的.
  3. 缩放是通过移动镜头高度来做的.
  4. 提供定点缩放, 缩放时如果超出可视范围,会对缩放点进行移动.
  5. 需要在Hierarchy中添加EasyTouch.
  6. 如果要想使用镜头移动方式,具体参考: Unity: 一个简单的镜头移动/缩放管理类(只移动镜头方式)

代码如下:

using UnityEngine;
using HedgehogTeam.EasyTouch;
using XLua;
using System.Collections.Generic;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace DCG
{
    /// <summary>
    /// 摄像机管理类: 固定摄像机,移动地图的方式.
    /// 挂载到摄像机所在的GameObject
    /// @Author: Danny Yan
    /// </summary>
    [RequireComponent(typeof(Camera))]
    [LuaCallCSharp]
    public class CameraViewFixedCam : MonoBehaviour
    {
        [Header("moveTarget可视边界(localPos):左上->右上->右下->左下")]
        /// 为了方便配置,设计为相对于moveTarget的localPosition,而不是全局坐标
        public Vector3[] moveTargetLocalBounds = new Vector3[4]{
            new Vector3(100f, 0f, 900f),
            new Vector3(900f, 0f, 900f),
            new Vector3(900f, 0f, 100f),
            new Vector3(100f, 0f, 100f)
        };

        [Header("缩放时的最高高度")]
        public float scaleMaxY = 160;
        [Header("缩放时的最低高度")]
        public float scaleMinY = 100;
        [Header("缩放速度,值越大缩放越快")]
        public float scaleSpeed = 35f;

        [Header("moveTarget移动的目标点(缓动方式)")]
        public Vector3 lerpMoveTarget = Vector3.zero;

        [Header("手势滑动结束后,需要继续移动的系数,值越大移动得越远")]
        public float lerpGoOnMoveScale = 1.5f;
        [Header("手势滑动结束后,继续(减速)移动的速度,值越大移动得越快")]
        public float lerpMoveSpeed = 10f;

        public float camDistance
        {
            get
            {
                var _camDistance = 0f;
                Ray ray = new Ray(transform.position, transform.forward);
                RaycastHit[] hits = Physics.RaycastAll(ray, this.rayMaxDistance);
                for (int i = 0; i < hits.Length; i++)
                {
                    if (hits[i].collider.gameObject == this.moveTarget)
                    {
                        _camDistance = hits[i].distance;
                        break;
                    }
                }
                this._scopeDitry = true;
                this.GetScreenCornersPosInWorld();

                return _camDistance;
                // return this._camDistance;
            }
        }

        [HideInInspector]
        public int moveToBorderStatus = 0;

        [Header("摄像机最大射线距离")]
        public int rayMaxDistance = 5000;

        [Header("需要移动的目标对象(必须设置)")]
        public GameObject moveTarget;

        private Vector4 currentScope = Vector4.zero;

        [SerializeField]
        [Header("摄像机中心射线和场景的距离")]
        // private float _camDistance = float.NaN;
        private Vector3[] _camCornersCache = new Vector3[4];
        private bool _scopeDitry = true;


        private bool swipeTargetValid = false;
        /// swipe时记录上一次的世界坐标
        private Vector3 preSwipeWorldPos;

        /// 滑动期间的最后几次变化
        private Vector3[] swipeIncrement = new Vector3[5];
        private int swipeIncrementIndex = 0;

        internal Camera mainCamera;

        /// 是否要进行lerpMove
        internal bool lerpMove = false;
        [SerializeField]
        internal bool showDebugLines = false;
        // 是否正在缩放
        private bool isPinching = false;
        // 是否锁定并阻止swipe
        private bool lockTouch = false;

        private Vector3[] ScreenCorners;
        private Vector3[] screenViewTrapezoid = new Vector3[4];

        private void Awake()
        {
            ScreenCorners = new Vector3[]{
                new Vector2(0,Screen.height),
                new Vector2(Screen.width,Screen.height),
                new Vector2(Screen.width,0),
                new Vector2(0,0),
            };

            this.mainCamera = this.gameObject.GetComponent<Camera>();
            this.lerpMove = false;

            if (this.moveTarget == null)
            {
                throw new UnityException("必须指定一个移动目标");
            }
            lerpMoveTarget = this.moveTarget.transform.position;

            this.GetScreenCornersPosInWorld();

            EasyTouch.On_Pinch += EasyTouch_On_Pinch;
            EasyTouch.On_SwipeStart += EasyTouch_On_SwipeStart;
            EasyTouch.On_Swipe += EasyTouch_On_Swipe;
            EasyTouch.On_SwipeEnd += EasyTouch_On_SwipeEnd;
            EasyTouch.On_TouchDown2Fingers += EasyTouch_On_TouchDown2Fingers;
            EasyTouch.On_TouchUp2Fingers += EasyTouch_On_TouchUp2Fingers;
        }

        public void LookAt(GameObject tag, bool useLerp = true)
        {
            this.LookAt(tag.transform.position, useLerp);
        }
        ///<summary>移动到目标位置,并使其与摄像机中心位置对齐</summary>
        public void LookAt(Vector3 worldPos, bool useLerp = true)
        {
            this.CancelLerpMove();

            // 1.使用射线检测moveTarget的BoxCollider来得到hitPoint(世界坐标),
            // 2.使用该hitPoint与worldPos的向量差得到偏移量,
            // 所以需要设置boxCollider的size.y,使其和worldPos.y一致,
            // 这样才使得worldPos的y平面位于屏幕中心
            var bc = this.moveTarget.GetComponent<BoxCollider>();
            var size = bc.size;
            size.y = (worldPos.y - this.moveTarget.transform.position.y) * 2;
            bc.size = size;

            // 获取相机中心射线检测到的位置
            Ray ray = new Ray(this.transform.position, transform.forward);
            var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
            var camRayPoint = worldPos;
            if (hits != null)
            {
                for (int i = 0; i < hits.Length; i++)
                {
                    if (hits[i].collider.gameObject == this.moveTarget)
                        camRayPoint = hits[i].point;
                }
            }
            var transOff = camRayPoint - worldPos;
            transOff.y = 0;
            transOff = this.WrapPosInRect(transOff, true);
            this.MoveTo(this.moveTarget.transform.position+transOff, useLerp);
        }

        /// <summary>移动到相对于moveTarget的位置</summary>
        public void LookAtLocal(Vector3 tagLocalPos, bool useLerp = true)
        {
            var tagPos = this.moveTarget.transform.TransformPoint(tagLocalPos);
            this.LookAt(tagPos, useLerp);
        }

        public void MoveTo(Vector3 tagPos, bool useLerp = true)
        {
            // this._scopeDitry = true;
            this.lerpMove = useLerp;
            if (useLerp)
            {
                this.lerpMoveTarget = tagPos;
            }
            else
            {
                this.moveTarget.transform.position = tagPos;
            }
        }

        public void Scale(float scaleDelta, Vector2 scaleCenterPosOnScreen)
        {
            // 计算摄像机视口(摄像机显示画面)的宽高
            float halfFOV = (this.mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
            float aspect = this.mainCamera.aspect;

            // 视口在Z轴上变化时(相当于缩放效果),对应的宽高变化量,相当于直接使用scaleDelta作为Z轴的变化距离
            // 乘2是因为scaleDelta * Mathf.Tan(halfFOV)计算出的只是视口画面的一半高,缩放是全画面,需要乘2
            float height = scaleDelta * Mathf.Tan(halfFOV) * 2;
            float width = height * aspect;

            // 缩放中心点在屏幕中的比例,减0.5f,因为世界坐标是相对于屏幕的中心
            float cpRateX = scaleCenterPosOnScreen.x / Screen.width - 0.5f;
            float cpRateY = scaleCenterPosOnScreen.y / Screen.height - 0.5f;

            Vector3 pos = this.transform.position;
            var oldPos = this.transform.position;

            // scaleW*cpRateX 表示视口画面宽度变化偏移度.
            // 如果cpRateX,cpRateY都为0,表示X轴,Y轴上无变化,则只以transform.forward为实际变化,效果为沿着视口中心的路径上(Z轴)前进/后退.
            // 比如cpRateX为0.2f,表示在屏幕中心右侧20%位置处作为手势缩放中心点进行操作,
            // scaleW此时假如为-5(表示放大),则transform.right就还需要往左走-1f,
            // 最终效果为transform.forward按scaleDelta前进,同时X轴往左移动,这样视觉上20%位置处没有发生任何偏移. Y轴同理
            var w = width * cpRateX;
            var h = height * cpRateY;
            pos += transform.right * w;
            pos += transform.up * h;
            pos += transform.forward * scaleDelta;

            if (pos.y <= scaleMaxY && pos.y >= scaleMinY)
            {
                // 进行一次预缩放
                this.transform.position = pos;
                this._scopeDitry = true;
                this.GetScreenCornersPosInWorld();

                // 缩放后对边界的检查,如果触碰到边界则需要对moveTarget进行transOff偏移,
                var screenViewTrapezoid = this.GetScreenCornersPosInWorld();
                var moveTargetPos = this.moveTarget.transform.position;
                var st = this.CheckScreenCornerIsOutBounds(moveTargetPos);
                var transOff = Vector3.zero;
                transOff = this.WrapTransOff(moveTargetPos, st, transOff);
                this.moveTarget.transform.Translate(transOff, Space.World);
            }
        }

        /// <summary>获取可视区域(屏幕四个角)对应的世界坐标.
        /// 可用于在小地图上显示视口的梯形范围</summary>
        public Vector3[] GetScreenCornersPosInWorld()
        {
            if (this._scopeDitry == false) return this.screenViewTrapezoid;
            this._scopeDitry = false;

            for (int i = 0; i < ScreenCorners.Length; i++)
            {
                Ray ray = this.mainCamera.ScreenPointToRay(ScreenCorners[i]);
                var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
                Vector3 dist0 = Vector2.zero;
                dist0.z = float.MaxValue;
                if (hits != null && hits.Length > 0)
                {
                    for (int j = 0; j < hits.Length; j++)
                    {
                        if (hits[j].collider.gameObject == this.moveTarget)
                        {
                            this.screenViewTrapezoid[i] = hits[j].point;
                            break;
                        }
                    }
                }
                else
                {
                    // 没有检测到
                    if (i == 0)
                    {
                        this.screenViewTrapezoid[i].x -= 1;
                        this.screenViewTrapezoid[i].z += 1;
                    }
                    else if (i == 1)
                    {
                        this.screenViewTrapezoid[i].x += 1;
                        this.screenViewTrapezoid[i].z += 1;
                    }
                    else if (i == 2)
                    {
                        this.screenViewTrapezoid[i].x += 1;
                        this.screenViewTrapezoid[i].z -= 1;
                    }
                    else if (i == 3)
                    {
                        this.screenViewTrapezoid[i].x -= 1;
                        this.screenViewTrapezoid[i].z -= 1;
                    }
                }
            }
            return screenViewTrapezoid;
        }

        /// 取消移动
        public void CancelLerpMove()
        {
            this.lerpMove = false;
            this.lerpMoveTarget = Vector3.zero;
        }

        // -----------------------------------------------------

        /// <summary>移动moveTarget</summary>
        private void MoveWithScreenPos(Vector2 screenPosition)
        {
            this.CancelLerpMove();

            Vector3 transOff = new Vector3(0, 0, 0);
            var _ray = this.mainCamera.ScreenPointToRay(screenPosition);
            var _hits = Physics.RaycastAll(_ray, this.rayMaxDistance);
            for (int i = 0; i < (_hits == null ? 0 : _hits.Length); i++)
            {
                if (_hits[i].collider.gameObject == this.moveTarget)
                {
                    transOff = _hits[i].point - this.preSwipeWorldPos; // 当前touch的世界坐标和上一次记录的世界坐标的方向向量
                    this.preSwipeWorldPos = _hits[i].point;
                }
            }
            transOff.y = 0;
            if (transOff == Vector3.zero)
            {
                return;
            }
            transOff = this.WrapPosInRect(transOff);
            this.moveTarget.transform.Translate(transOff, Space.World);
            // this._scopeDitry = true;

            if (swipeIncrementIndex >= swipeIncrement.Length)
            {
                swipeIncrementIndex = 0;
            }
            swipeIncrement[swipeIncrementIndex] = transOff;
            swipeIncrementIndex++;
        }

        private void OnDestroy()
        {
            EasyTouch.On_Pinch -= EasyTouch_On_Pinch;
            EasyTouch.On_SwipeStart -= EasyTouch_On_SwipeStart;
            EasyTouch.On_Swipe -= EasyTouch_On_Swipe;
            EasyTouch.On_SwipeEnd -= EasyTouch_On_SwipeEnd;
            EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchUp2Fingers;
            EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchDown2Fingers;
        }

        /// <summary>
        /// 缩放
        /// </summary>
        private void EasyTouch_On_Pinch(Gesture gesture)
        {
            if (this.lockTouch) return;

            this.isPinching = true;

            // 往外扩(放大)是负数,往内聚(缩小)是整数
            float scaleDelta = gesture.deltaPinch * UnityEngine.Time.deltaTime * this.scaleSpeed;
            // 缩放中心点(相对于屏幕左下角)
            Vector2 scaleCenterPosOnScreen = gesture.position;

            this.Scale(scaleDelta, scaleCenterPosOnScreen);
        }

        private void EasyTouch_On_TouchDown2Fingers(Gesture gesture)
        {
            this.isPinching = true;
        }
        private void EasyTouch_On_TouchUp2Fingers(Gesture gesture)
        {
            this.isPinching = false;
        }

        /// <summary>
        /// 开始划
        /// </summary>
        private void EasyTouch_On_SwipeStart(Gesture gesture)
        {
            var ray = this.mainCamera.ScreenPointToRay(gesture.position);
            var hits = Physics.RaycastAll(ray, this.rayMaxDistance);
            // 没有点击到目标
            if (hits == null || hits.Length < 1)
            {
                swipeTargetValid = false;
                return;
            }
            for (int i = 0; i < hits.Length; i++)
            {
                if (hits[i].collider.gameObject == this.moveTarget)
                {
                    this.preSwipeWorldPos = hits[i].point;
                    break;
                }

            }
            swipeTargetValid = true;
            this.CancelLerpMove();
        }

        /// <summary>
        /// 划
        /// </summary>
        private void EasyTouch_On_Swipe(Gesture gesture)
        {
            if (this.lockTouch || this.isPinching || !this.swipeTargetValid) return;

            this.MoveWithScreenPos(gesture.position);
        }

        /// <summary>
        /// 开始划
        /// </summary>
        private void EasyTouch_On_SwipeEnd(Gesture gesture)
        {
            if (swipeTargetValid == false) return;

            this.lerpMove = true;

            // 根据最后几帧的移动变化量来确定最后需要的持续移动强度
            var transOff = new Vector3(0, 0, 0);
            swipeIncrementIndex = 0;
            for (int i = 0; i < swipeIncrement.Length; i++)
            {
                transOff += swipeIncrement[i];
                swipeIncrement[i] = Vector3.zero;

            }
            transOff = this.WrapPosInRect(transOff);
            this.MoveTo(this.moveTarget.transform.position + transOff, true);
        }

        private void LateUpdate()
        {
            if (this.lerpMove && this.lerpMoveTarget != Vector3.zero)
            {
                var dist = Vector3.Distance(this.moveTarget.transform.position, this.lerpMoveTarget);
                if (dist >= 0.001f)
                {
                    var curPos = Vector3.Lerp(this.moveTarget.transform.position, this.lerpMoveTarget, Time.deltaTime * this.lerpMoveSpeed);
                    this.moveTarget.transform.position = curPos;
                }
                else
                {
                    this.CancelLerpMove();
                }
            }

#if UNITY_EDITOR
            if (!showDebugLines) return;

            // 可视范围边界
            var moveTargetPos = this.moveTarget.transform.position;
            var p0 = moveTargetPos + moveTargetLocalBounds[0];
            var p1 = moveTargetPos + moveTargetLocalBounds[1];
            var p2 = moveTargetPos + moveTargetLocalBounds[2];
            var p3 = moveTargetPos + moveTargetLocalBounds[3];
            Debug.DrawLine(p0, p1, Color.blue); // UpperLeft -> UpperRight
            Debug.DrawLine(p1, p2, Color.blue); // UpperRight -> LowerRight
            Debug.DrawLine(p2, p3, Color.blue); // LowerRight -> LowerLeft
            Debug.DrawLine(p3, p0, Color.blue); // LowerLeft -> UpperLeft

            // cam中心线
            Debug.DrawLine(transform.position, transform.position + transform.forward * this.rayMaxDistance, Color.green);
            // cam视锥截面
            Vector3[] corners = this.GetCorners(this.camDistance);
            Debug.DrawLine(corners[0], corners[1], Color.red); // UpperLeft -> UpperRight
            Debug.DrawLine(corners[1], corners[2], Color.red); // UpperRight -> LowerRight
            Debug.DrawLine(corners[2], corners[3], Color.red); // LowerRight -> LowerLeft
            Debug.DrawLine(corners[3], corners[0], Color.red); // LowerLeft -> UpperLeft
            // 横
            Debug.DrawLine(corners[4], corners[5], Color.red);
            // 竖
            Debug.DrawLine(corners[6], corners[7], Color.red);

            // 视口覆盖的区域
            Debug.DrawLine(screenViewTrapezoid[0], screenViewTrapezoid[1], Color.yellow); // UpperLeft -> UpperRight
            Debug.DrawLine(screenViewTrapezoid[1], screenViewTrapezoid[2], Color.yellow); // UpperRight -> LowerRight
            Debug.DrawLine(screenViewTrapezoid[2], screenViewTrapezoid[3], Color.yellow); // LowerRight -> LowerLeft
            Debug.DrawLine(screenViewTrapezoid[3], screenViewTrapezoid[0], Color.yellow); // LowerLeft -> UpperLeft

#endif
        }

        /// <summary>
        /// 对移动偏移量进行验证,超出边界要做相关处理
        /// <param name="nearBorder">如果超出边界是否修正为离边界最近的偏移而不是直接变为0</param>
        /// </summary>
        private Vector3 WrapPosInRect(Vector3 transOff, bool nearBorder = false)
        {
            var currPos = this.moveTarget.transform.position;
            var moveTargetPos = currPos + transOff;
            var st = this.CheckScreenCornerIsOutBounds(moveTargetPos);

            if (st == 0) return transOff;

            if (nearBorder == false)
            {
                var outX = (st & 1) != 0 || (st & 2) != 0 || (st & 4) != 0 || (st & 8) != 0;
                var outZ = (st & 16) != 0 || (st & 32) != 0 || (st & 64) != 0 || (st & 128) != 0;
                if (outX) transOff.x = 0;
                if (outZ) transOff.z = 0;
            }
            else
            {
                transOff = this.WrapTransOff(moveTargetPos, st, transOff);
            }

            return transOff;
        }

        private Vector3 WrapTransOff(Vector3 moveTargetPos, int st, Vector3 transOff){
            var screenViewTrapezoid = this.GetScreenCornersPosInWorld();

            var _p0 = (moveTargetPos + this.moveTargetLocalBounds[0]);
            var _p1 = (moveTargetPos + this.moveTargetLocalBounds[1]);
            var _p2 = (moveTargetPos + this.moveTargetLocalBounds[2]);
            var _p3 = (moveTargetPos + this.moveTargetLocalBounds[3]);

            // 移动后视口各个位置与边界的差,就是transOff应该再次偏移的量
            // 经过偏移后使得 moveTargetPos+transOff 被限定在边界范围内
            if ((st & 1) != 0) transOff.x += screenViewTrapezoid[0].x - _p0.x;
            else if ((st & 2) != 0) transOff.x += screenViewTrapezoid[1].x - _p1.x;
            else if ((st & 4) != 0) transOff.x += screenViewTrapezoid[2].x - _p2.x;
            else if ((st & 8) != 0) transOff.x += screenViewTrapezoid[3].x - _p3.x;
            if ((st & 16) != 0) transOff.z += screenViewTrapezoid[0].z - _p0.z;
            else if ((st & 32) != 0) transOff.z += screenViewTrapezoid[1].z - _p1.z;
            else if ((st & 64) != 0) transOff.z += screenViewTrapezoid[2].z - _p2.z;
            else if ((st & 128) != 0) transOff.z += screenViewTrapezoid[3].z - _p3.z;
            return transOff;
        }  
      
        /// 检查可视区域(ScreenCorners)是否超出边界
        private int CheckScreenCornerIsOutBounds(Vector3 moveTargetPos)
        {
            var screenViewTrapezoid = this.GetScreenCornersPosInWorld();
            var _p0 = (moveTargetPos + moveTargetLocalBounds[0]);
            var _p1 = (moveTargetPos + moveTargetLocalBounds[1]);
            var _p2 = (moveTargetPos + moveTargetLocalBounds[2]);
            var _p3 = (moveTargetPos + moveTargetLocalBounds[3]);

            int st = 0; // 00000000,用8个位来代表4个角的x,z状态
            if (_p0.x >= screenViewTrapezoid[0].x) st |= 1;
            if (_p1.x <= screenViewTrapezoid[1].x) st |= 2;
            if (_p2.x <= screenViewTrapezoid[2].x) st |= 4;
            if (_p3.x >= screenViewTrapezoid[3].x) st |= 8;
            if (_p0.z <= screenViewTrapezoid[0].z) st |= 16;
            if (_p1.z <= screenViewTrapezoid[1].z) st |= 32;
            if (_p2.z >= screenViewTrapezoid[2].z) st |= 64;
            if (_p3.z >= screenViewTrapezoid[3].z) st |= 128;

            return st;
        }

        /// 获取视口的边界. 上左-上右-下右-下左
        private Vector3[] GetCorners(float distance)
        {
            Vector3[] corners = new Vector3[8];

            float halfFOV = (mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
            float aspect = mainCamera.aspect;
            float halfHeight = distance * Mathf.Tan(halfFOV);
            float halfWidth = halfHeight * aspect;

            var tx = this.transform;
            // UpperLeft
            corners[0] = tx.position - (tx.right * halfWidth);
            corners[0] += tx.up * halfHeight;
            corners[0] += tx.forward * distance;

            // UpperRight
            corners[1] = tx.position + (tx.right * halfWidth);
            corners[1] += tx.up * halfHeight;
            corners[1] += tx.forward * distance;

            // LowerRight
            corners[2] = tx.position + (tx.right * halfWidth);
            corners[2] -= tx.up * halfHeight;
            corners[2] += tx.forward * distance;

            // LowerLeft
            corners[3] = tx.position - (tx.right * halfWidth);
            corners[3] -= tx.up * halfHeight;
            corners[3] += tx.forward * distance;

            // 横
            corners[4] = tx.position + Vector3.zero;
            corners[4] -= tx.right * halfWidth;
            corners[4] += tx.forward * this.camDistance;
            corners[5] = tx.position + Vector3.zero;
            corners[5] += tx.right * halfWidth;
            corners[5] += tx.forward * this.camDistance;

            // 竖
            corners[6] = tx.position + Vector3.zero; ;
            corners[6] -= tx.up * halfHeight;
            corners[6] += tx.forward * this.camDistance;
            corners[7] = tx.position + Vector3.zero;
            corners[7] += tx.up * halfHeight;
            corners[7] += tx.forward * this.camDistance;

            return corners;
        }

    }

#if UNITY_EDITOR
    [CustomEditor(typeof(CameraViewFixedCam))]
    public class DCGCameraViewFixedCamEditor : Editor
    {
        private Vector3 lookAtPos = new Vector3(269f, 36.8f, 299f);
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            var scview = this.target as DCG.CameraViewFixedCam;

            GUILayout.Space(5);
            if (GUILayout.Button("跳转到moveTarget", GUILayout.Height(25)))
            {
                scview.lerpMove = true;
            }
            GUILayout.Space(5);
            lookAtPos = EditorGUILayout.Vector3Field("LookAt坐标点", lookAtPos);
            if (GUILayout.Button("LookAtLocal", GUILayout.Height(25)))
            {
                scview.LookAtLocal(lookAtPos); //new Vector3(122.4f, 5.3f, 132.2f)
                // scview.LookAt(GameObject.Find("FarmBlock"));
            }
        }
    }
#endif
}

主要说明一下边界的检查处理,如图:

垂直: image1.png
  • 蓝色线条表示地图可视边界
  • 红色表示视锥截面(视锥在指定距离下的截面,本例中即是视锥在摄像机与地图距离上的截面)
  • 绿色表示摄像机中心射线
  • 灰色是视锥边缘线
  • 黄色只是边界垂直线的示例以便显示更直观
  • 此处地图应该是:平行于Z轴,在有一定Y高度,带有collider box(box高度不能为0)的gameobject.

是否在边界内也主要是通过判断灰色边缘线在地图上的hitPoint是否在边界内来得到的.
观察以下几张图片:

旋转后: image2.png
旋转后视锥上侧边界: image3.png
旋转后视锥下侧边界: image.png

在旋转镜头后,如果不做处理,会出现上述图中的情况: 浅蓝色的虚线就是视锥多看到的边界之外的情况. 通过代码中的WrapPosInRect()方法处理后就可以限制地图位置,使其在拖动时视锥边缘不超过指定边界,处理后:
上边缘:

image.png
下边缘: image.png
上面2个效果是在摄像机Y轴不旋转的情况下使用Scene窗口的正交模式查看的,Y轴旋转得到如下效果:
image.png
image.png

Scale()方法中,在处理了缩放后又进行了一次检查,用来确定缩放后,视锥边缘是否超出范围了(比如先拖动到边界,再在屏幕边缘进行缩小操作),如果不处理,就又会出现超出边界的的情况.

一般情况下,最好使可视控制边界不要超过地图的碰撞盒大小,如下图:

image.png
因为如果大小比较匹配,在缩放时可能出现视锥边界无法检测到hitpoint的情况,如下图: image.png
无法精确解决(除非自行进行三角函数运算),GetScreenCornersPosInWorld()中进行了一次简单处理, 但依然会使得WrapPosInRect()中(else部分)出现偏差.

转载请注明出处: https://www.jianshu.com/p/71fead5f6f51

相关文章

网友评论

      本文标题:Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方

      本文链接:https://www.haomeiwen.com/subject/loxdectx.html