从0开始学Unity做SLG系列(主场景移动, EasyTouch)

5 篇文章 0 订阅
4 篇文章 0 订阅

主场景(拖动/缓动/缩放/边界)

  文章链接贴在这里,防止丢失把代码转过来,这里有两篇,分别是移动镜头和移动场景。
  需要提示的是,这个需要下载一个easytouch5的第三方库(我这里是unity2019),已经不维护了,官方下不到,需要自己找,然后在Hierarchy里创建个EasyToch,不然代码不生效。

参考资料:
EasyTouch基本用法
https://www.cnblogs.com/chinarbolg/p/9601477.html
Unity3D EasyTouch 初步使用教程(详细)
http://blog.csdn.net/lifeonelive/article/details/47974905
【Unity插件】EasyTouch5教程
http://blog.csdn.net/weixin_38158625/article/details/72673294
Unity插件——EasyTouch的使用
http://blog.csdn.net/u014086857/article/details/52087379
Unity3d 插件研究之EasyTouch插件
https://blog.csdn.net/qq_23377827/article/details/80820679

using UnityEngine;
using HedgehogTeam.EasyTouch;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace DCG
{
    /// <summary>
    /// 摄像机管理类.
    /// 挂载到摄像机所在的GameObject.
    /// @Author:Danny Yan
    /// </summary>
    public class SceneCameraView : MonoBehaviour
    {
        /// 摄像机距离
        private float distance = 100;
        [Tooltip("边界,顺序:左上->右上->右下->左下")]
        public Vector3[] rect = new Vector3[4]{
            new Vector3(70.0f, 160.0f, -65.1f),
            new Vector3(324.6f, 160.0f, -65.1f),
            new Vector3(324.6f, 160.0f, -182.5f),
            new Vector3(70.0f, 160.0f, -182.5f)
        };

        [Tooltip("缩放时的最高高度")]
        public float scaleMaxY = 160;
        [Tooltip("缩放时的最低高度")]
        public float scaleMinY = 100;

        private Camera mainCamera;

        [Tooltip("摄像机移动到的目标点"),SerializeField]
        private Vector3 lerpMoveTarget = Vector3.zero;

        /// swipe结束后需要继续滑动的系数,是对swipe的gesture.deltaPosition的缩放,值越大滑动得越远
        [Tooltip("手势滑动结束后,需要继续移动的系数,值越大移动得越远")]
        public float lerpGoOnMoveScale = 6f;
        /// lerpMove速度,值越大滑动得越快
        [Tooltip("手势滑动结束后,继续(减速)移动的速度,值越大移动得越快")]
        public float lerpMoveSpeed = 10f;
        [Tooltip("射线最大检测距离")]
        public float rayDistance = 2000;

        /// 是否要进行lerpMove
        internal bool lerpMove = false;
        [SerializeField]
        internal bool showDebugLines = false;

        private bool isPinching = false;

        private void Awake()
        {
            this.mainCamera = this.gameObject.GetComponent<Camera>();
            this.lerpMove = false;

            lerpMoveTarget = this.transform.localPosition;

            this.GetCameraToTargetDistance();

            EasyTouch.On_Pinch += EasyTouch_On_Pinch;
            EasyTouch.On_DragStart += EasyTouch_On_DragStart;
            EasyTouch.On_SwipeStart += EasyTouch_On_SwipeStart;
            EasyTouch.On_Drag += EasyTouch_On_Drag;
            EasyTouch.On_Swipe += EasyTouch_On_Swipe;
            EasyTouch.On_DragEnd += EasyTouch_On_DragEnd;
            EasyTouch.On_SwipeEnd += EasyTouch_On_SwipeEnd;

            EasyTouch.On_TouchUp2Fingers += EasyTouch_On_TouchUp2Fingers;
        }

        private void OnDestroy()
        {
            EasyTouch.On_DragStart -= EasyTouch_On_DragStart;
            EasyTouch.On_SwipeStart -= EasyTouch_On_SwipeStart;
            EasyTouch.On_Drag -= EasyTouch_On_Drag;
            EasyTouch.On_Swipe -= EasyTouch_On_Swipe;
            EasyTouch.On_DragEnd -= EasyTouch_On_DragEnd;
            EasyTouch.On_SwipeEnd -= EasyTouch_On_SwipeEnd;
            EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchUp2Fingers;
        }

        /// <summary>
        /// 缩放
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_Pinch(Gesture gesture)
        {
            this.isPinching = true;

            // 往外扩(放大)是负数,往内聚(缩小)是整数
            float scaleDelta = gesture.deltaPinch * UnityEngine.Time.deltaTime;

            // 缩放中心点(相对于屏幕左下角)
            Vector2 scaleCenterPos = gesture.position;

            // 计算摄像机视口(摄像机显示画面)的宽高
            float halfFOV = (this.mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
            float aspect = this.mainCamera.aspect;

            // 视口在Z轴上变化时(相当于缩放效果),对应的宽高变化量,相当于直接使用scaleDelta作为Z轴的变化距离
            float scaleH = scaleDelta * Mathf.Tan(halfFOV) * 2;
            float scaleW = scaleH * aspect;

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

            Vector3 pos = this.transform.localPosition;
            // 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轴同理
            pos += transform.right * (scaleW * cpRateX);
            pos += transform.up * (scaleH * cpRateY);
            pos += transform.forward * scaleDelta;

            if (pos.y <= scaleMaxY && pos.y >= scaleMinY)
            {
                this.transform.localPosition = pos;
                var borderScVec = new Vector3(scaleW * .5f, 0, scaleH * .5f);
                // 边界跟随
                for (int i = 0; i < rect.Length; i++)
                {
                    rect[i] += transform.right * (scaleW * cpRateX);
                    rect[i] += transform.up * (scaleH * cpRateY);
                    rect[i] += transform.forward * scaleDelta;
                }
            }

            this.GetCameraToTargetDistance();
        }

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


        /// <summary>
        /// 开始拖
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_DragStart(Gesture gesture)
        {
            EasyTouch_On_SwipeStart(gesture);
        }

        /// <summary>
        /// 开始划
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_SwipeStart(Gesture gesture)
        {
            this.lerpMoveTarget = Vector3.zero;
            this.lerpMove = false;
        }

        /// <summary>
        /// 拖
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_Drag(Gesture gesture)
        {
            EasyTouch_On_Swipe(gesture);
        }

        /// <summary>
        /// 划
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_Swipe(Gesture gesture)
        {
            if (this.isPinching) return;

            this.lerpMoveTarget = Vector3.zero;
            this.lerpMove = false;

            // 计算摄像机视口(摄像机显示画面)的宽高
            float halfFOV = (this.mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad;
            float aspect = this.mainCamera.aspect;

            // 从camera开始到当前屏幕点击点对应世界坐标下的distance
            var screenPosition = gesture.position;
            var ray = this.mainCamera.ScreenPointToRay(screenPosition);
            RaycastHit hit;
            var screenPointDistance = this.distance;
            if (Physics.Raycast(ray, out hit, this.rayDistance))
            {
                screenPointDistance = hit.distance;
            }

            // float halfH = this.distance * Mathf.Tan(halfFOV);
            float halfH = screenPointDistance * Mathf.Tan(halfFOV);
            float halfW = halfH * aspect;
            // gesturePos是相对于屏幕的操作偏移,通过与Screen的比例乘以halfW,halfH,得到最终在视口上的位移
            // 通过这个计算,最终效果是在拖动时也不能做到完全同步:即拖动场景中某个点到屏幕任意位置,该点依然精确的位于鼠标(手指)处.
            var offx = (-gesture.deltaPosition.x / Screen.width) * halfW;
            var offy = (-gesture.deltaPosition.y / Screen.height) * halfH;

            var v3pos = new Vector3(offx, 0, offy);

            // 只使用y的旋转信息构建一个新的四元数
            var qua = Quaternion.identity;
            qua.y = this.transform.rotation.y;
            // 旋转vector:vector3的各分量被四元数按旋转角度计算新值
            v3pos = qua * v3pos;

            var tagPos = this.transform.localPosition + v3pos;

            if (tagPos.x < rect[0].x)
            {
                tagPos.x = rect[0].x; v3pos.x = 0;
            }
            else
            {
                if (tagPos.x > rect[1].x)
                {
                    tagPos.x = rect[1].x; v3pos.x = 0;
                }
            }

            if (tagPos.z < rect[3].z)
            {
                tagPos.z = rect[3].z; v3pos.z = 0;
            }
            else
            {
                if (tagPos.z > rect[0].z)
                {
                    tagPos.z = rect[0].z; v3pos.z = 0;
                }
            }

            this.transform.Translate(v3pos, Space.World);
            // 额外增加一个分量来,使得lerpMove时进行更多偏移, 效果为:减速滑动得更远
            var extPos = v3pos * lerpGoOnMoveScale; // new Vector3(offx * lerpGoOnScale, 0, offy * lerpGoOnScale);
            extPos = qua * extPos;
            this.lerpMoveTarget = this.WrapPosInRect(tagPos + extPos);
        }


        private void EasyTouch_On_DragEnd(Gesture gesture)
        {
            EasyTouch_On_SwipeEnd(gesture);
        }

        /// <summary>
        /// 开始划
        /// </summary>
        /// <param name="gesture"></param>
        private void EasyTouch_On_SwipeEnd(Gesture gesture)
        {
            this.lerpMove = true;
        }

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

#if UNITY_EDITOR
            if (!showDebugLines) return;

            // 可拖动区域
            Debug.DrawLine(rect[0], rect[1], Color.blue);
            Debug.DrawLine(rect[1], rect[2], Color.blue);
            Debug.DrawLine(rect[2], rect[3], Color.blue);
            Debug.DrawLine(rect[3], rect[0], Color.blue);

            // 视口
            Debug.DrawLine(transform.position, transform.position + transform.forward * 1000, Color.red);

            Vector3[] corners = GetCorners(this.distance);
            Debug.DrawLine(corners[0], corners[1], Color.red); // UpperLeft -> UpperRight
            Debug.DrawLine(corners[1], corners[3], Color.red); // UpperRight -> LowerRight
            Debug.DrawLine(corners[3], corners[2], Color.red); // LowerRight -> LowerLeft
            Debug.DrawLine(corners[2], corners[0], Color.red); // LowerLeft -> UpperLeft
#endif
        }

        private Vector3 WrapPosInRect(Vector3 pos)
        {
            if (pos.x < rect[0].x) pos.x = rect[0].x;
            else
                if (pos.x > rect[1].x) pos.x = rect[1].x;

            if (pos.z < rect[3].z) pos.z = rect[3].z;
            else
                if (pos.z > rect[0].z) pos.z = rect[0].z;

            return pos;
        }


        ///<summary>移动到目标位置,并使其与摄像机中心位置对齐</summary>
        public void LookAt(Vector3 tagV3)
        {
            this.lerpMove = false;
            this.lerpMoveTarget = Vector3.zero;

            // 要lookAt指定目标点,使用向量减法,向量减法常用于取得一个对象到另一个对象之间的方向和距离
            var qua = Quaternion.Euler(transform.eulerAngles);
            var tagPos = tagV3 - (qua * Vector3.forward * this.distance + qua * Vector3.right + qua * Vector3.up);

            if (Vector3.Distance(this.transform.position, tagPos) < 0.01) return;

            this.lerpMove = true;
            this.lerpMoveTarget = this.WrapPosInRect(tagPos);
        }

        /// 得到摄像机与指定对象的距离
        private float GetCameraToTargetDistance()
        {
            Ray ray = new Ray(transform.position, transform.forward);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, 1000))
            {
                distance = hit.distance;
            }
            return distance;
        }

        ///<summary>
        /// 计算一个点是否在一个多边形范围内
        /// 如果过该点的线段与多边形的交点不为零且距该点左右方向交点数量都为奇数时  该点再多边形范围内
        /// <summary>
        /// <param name="point">测试点</param>
        /// <param name="vertexs">多边形的顶点集合</param>
        /// <returns><returns>
        public static bool PolygonIsContainPoint(Vector3 point, Vector3[] vertexs)
        {
            //判断测试点和横坐标方向与多边形的边的交叉点
            int leftNum = 0;  //左方向上的交叉点数
            int rightNum = 0;  //右方向上的交叉点数
            int index = 1;
            for (int i = 0; i < vertexs.Length; i++)
            {
                if (i == vertexs.Length - 1) { index = -i; }
                //找到相交的线段 
                if (point.z >= vertexs[i].z && point.z < vertexs[i + index].z || point.z < vertexs[i].z && point.z >= vertexs[i + index].z)
                {
                    Vector3 vecNor = (vertexs[i + index] - vertexs[i]);

                    //处理直线方程为常数的情况
                    if (vecNor.x == 0.0f)
                    {
                        if (vertexs[i].x < point.x)
                        {
                            leftNum++;
                        }
                        else if (vertexs[i].x == point.x)
                        { }
                        else
                        {
                            rightNum++;
                        }
                    }
                    else
                    {
                        vecNor = vecNor.normalized;
                        float k = vecNor.z / vecNor.x;
                        float b = vertexs[i].z - k * vertexs[i].x;

                        if ((point.z - b) / k < point.x)
                        {
                            leftNum++;
                        }
                        else if ((point.z - b) / k == point.x)
                        { }
                        else
                        {
                            rightNum++;
                        }
                    }
                }
            }

            if (leftNum % 2 != 0 || rightNum % 2 != 0)
            {
                return true;
            }
            return false;
        }


#if UNITY_EDITOR
        private Vector3[] GetCorners(float distance)
        {
            Vector3[] corners = new Vector3[4];

            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;

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

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

            return corners;
        }
#endif
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(SceneCameraView))]
    public class DCGSceneCameraViewEditor : Editor
    {
        private Vector3 lookAtPos;
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            var scview = this.target as DCG.SceneCameraView;

            GUILayout.Space(5);
            if (GUILayout.Button("跳转到moveTarget", GUILayout.Height(25)))
            {
                scview.lerpMove = true;
            }
            GUILayout.Space(5);
            lookAtPos = EditorGUILayout.Vector3Field("LookAt坐标点", lookAtPos);
            if (GUILayout.Button("LookAt", GUILayout.Height(25)))
            {
                scview.LookAt(lookAtPos); //new Vector3(122.4f, 5.3f, 132.2f)
            }
        }
    }
#endif
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值