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

  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

    /// <summary>
    /// 摄像机管理类: 固定摄像机,移动地图的方式.
    /// 挂载到摄像机所在的GameObject
    /// </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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容