U3D触摸基础手势(2D、3D、UGUI通杀版本)

本人以前写过一篇关于UI的基础手势的文章,文章在这里:传送门。但那个限制过多,只支持UI,不支持2D、3D物体,不支持多人操作……前几天在做一个关于TUIO的项目时,突然有了一个想法,经过了几次测试证明了想法大致是可行的。

思路分析:

  • UIGUI:UI的实现和上边的传送门基本相同,脚本实现IPointerDownHandler, IPointerUpHandler两个接口,这里不再啰嗦
  • 2D:2D的实现和UI很类似(3D的也类似),也是通过EventSystems的一些接口来实现基础手势的,但一般2D物体接受不到事件消息,而Unity自身提供了一个组件:Physics 2D Raycaster,通过这个组件2D物体也可以接收到事件消息
  • 3D:与2D相同,但组件不同Physics Raycaster

2D和3D相比较UGUI,需要添加Colloder,2D添加2D碰撞体,3D添加3D碰撞体,同时主相机添加上边的两个组件,实现2D就添加2D的Physics 2D Raycaster组件,3D的就添加3D的Physics Raycaster组件;另外场景必须要有EventSystem。

UGUI脚本:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

internal class EntityUGUI : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
{
    private RectTransform rectTransform;

    #region 触摸点
    /// <summary>
    /// 触摸事件参数集合
    /// </summary>
    private List<PointerEventData> pointerEventDatas = new List<PointerEventData>(2);

    /// <summary>
    /// Touch触摸点1的位置
    /// </summary>
    private Vector2 touch00Postion;

    /// <summary>
    /// Touch触摸点2的位置
    /// </summary>
    private Vector2 touch01Postion;
    #endregion

    #region 移动
    /// <summary>
    /// 偏移
    /// </summary>
    private Vector2 pressOffSet;

    /// <summary>
    /// 手势移动阈值
    /// </summary>
    private static float gestureMoveThreshold = 0.1f;
    #endregion

    #region 旋转
    /// <summary>
    /// 手势旋转速度
    /// </summary>
    private static float gestureRotateSpeed = 100.0f;

    /// <summary>
    /// 手势旋转阈值
    /// </summary>
    private static float gestureRotateThreshold = 0.3f;
    #endregion

    #region 缩放
    /// <summary>
    /// 缩放速度
    /// </summary>
    private static float scaleSpeed = 1.0f;

    /// <summary>
    /// 缩放阈值
    /// </summary>
    private static float scaleThreshold = 1f;

    /// <summary>
    /// 最大缩放倍数
    /// </summary>
    private static float maxScaleSize = 5.0f;

    /// <summary>
    /// 最小缩放倍数
    /// </summary>
    private static float minScaleSize = 1.0f;
    #endregion

    #region Unity
    private void Start()
    {
        this.rectTransform = this.transform as RectTransform;
    }
    #endregion

    #region interface
    /// <summary>
    /// 当按下时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        //鼠标左键:-1;鼠标右键:-2;鼠标中键:-3
        this.pointerEventDatas.Add(eventData);
        //偏移
        if (this.pressOffSet == Vector2.zero)
            this.pressOffSet = this.rectTransform.anchoredPosition - eventData.pressPosition;
    }

    /// <summary>
    /// 当拖拽时
    /// </summary>
    /// <param name="eventData"></param>
    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        if (this.pointerEventDatas.Count <= 0)
            return;
        if (this.pointerEventDatas.Count == 1)
        {
            Vector2 tarPos = eventData.position + this.pressOffSet;
            //限制位置
            if (Vector2.Distance(this.rectTransform.anchoredPosition, tarPos) <= EntityUGUI.gestureMoveThreshold)
                return;
            this.rectTransform.anchoredPosition = tarPos;
        }
        else if (this.pointerEventDatas.Count == 2)
        {
            #region 初始化位置
            if (this.touch00Postion == Vector2.zero && this.touch01Postion == Vector2.zero)
            {
                this.touch00Postion = this.pointerEventDatas[0].position;
                this.touch01Postion = this.pointerEventDatas[1].position;
                return;
            }
            #endregion

            this.Rotating();
            this.Zoom();

            #region 更新位置
            this.touch00Postion = this.pointerEventDatas[0].position;
            this.touch01Postion = this.pointerEventDatas[1].position;
            #endregion
        }
    }

    /// <summary>
    /// 当抬起时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        for (int i = 0; i < this.pointerEventDatas.Count; i++)
            if (this.pointerEventDatas[i].pointerId == eventData.pointerId)
            {
                this.pointerEventDatas.RemoveAt(i);
                break;
            }

        //偏移归零
        if (this.pointerEventDatas.Count <= 0)
            this.pressOffSet = Vector2.zero;
        //更新偏移
        else if (this.pointerEventDatas.Count == 1)
            this.pressOffSet = this.rectTransform.anchoredPosition - this.pointerEventDatas[0].position;
    }
    #endregion

    #region PrivateFunction
    /// <summary>
    /// 旋转
    /// </summary>
    private void Rotating()
    {
        //上一帧方向
        Vector2 lastDir = (this.touch00Postion - this.touch01Postion).normalized;
        //当前帧方向
        Vector2 currentDir = (this.pointerEventDatas[0].position - this.pointerEventDatas[1].position).normalized;
        //角度差
        float angle = this.VectorAngle(lastDir, currentDir);
        if (angle < -EntityUGUI.gestureRotateThreshold)
            this.rectTransform.Rotate(Vector3.forward, Time.deltaTime * EntityUGUI.gestureRotateSpeed);
        else if (angle > EntityUGUI.gestureRotateThreshold)
            this.rectTransform.Rotate(Vector3.forward, -Time.deltaTime * EntityUGUI.gestureRotateSpeed);
    }

    /// <summary>
    /// 缩放
    /// </summary>
    private void Zoom()
    {
        //上一帧距离
        float lastDistance = Vector2.Distance(this.touch00Postion, this.touch01Postion);
        //当前帧距离
        float currentDistance = Vector2.Distance(this.pointerEventDatas[0].position, this.pointerEventDatas[1].position);
        //差值
        float difference = lastDistance - currentDistance;
        if (difference < -EntityUGUI.scaleThreshold)
        {
            this.rectTransform.localScale = Vector3.Lerp(this.rectTransform.localScale, Vector3.one * EntityUGUI.maxScaleSize, Time.deltaTime * EntityUGUI.scaleSpeed);
            if (Vector3.Distance(this.rectTransform.localScale, Vector3.one * EntityUGUI.maxScaleSize) <= 0.01f)
                this.rectTransform.localScale = Vector3.one * EntityUGUI.maxScaleSize;
        }
        else if (difference > EntityUGUI.scaleThreshold)
        {
            this.rectTransform.localScale = Vector3.Lerp(this.rectTransform.localScale, Vector3.one * EntityUGUI.minScaleSize, Time.deltaTime * EntityUGUI.scaleSpeed);
            if (Vector3.Distance(this.rectTransform.localScale, Vector3.one * EntityUGUI.minScaleSize) <= 0.01f)
                this.rectTransform.localScale = Vector3.one * EntityUGUI.minScaleSize;
        }
    }

    /// <summary>
    /// 计算两个向量之间的夹角(-180 ,180)
    /// </summary>
    /// <param name="last"></param>
    /// <param name="curr"></param>
    /// <returns></returns>
    public float VectorAngle(Vector2 last, Vector2 curr)
    {
        Vector3 cross = Vector3.Cross(last, curr);
        float angle = Vector2.Angle(last, curr);
        return cross.z > 0 ? -angle : angle;
    }
    #endregion
}

2D脚本:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 实体
/// </summary>
internal class Entity2D : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
{
    private Rigidbody2D rigidBody2D;

    #region 触摸点
    /// <summary>
    /// 触摸事件参数集合
    /// </summary>
    private List<PointerEventData> pointerEventDatas = new List<PointerEventData>(2);

    /// <summary>
    /// Touch触摸点1的位置
    /// </summary>
    private Vector2 touch00Postion;

    /// <summary>
    /// Touch触摸点2的位置
    /// </summary>
    private Vector2 touch01Postion;
    #endregion

    #region 移动
    /// <summary>
    /// 偏移
    /// </summary>
    private Vector3 pressOffSet;

    /// <summary>
    /// 手势移动阈值
    /// </summary>
    private static float gestureMoveThreshold = 0.1f;
    #endregion

    #region 旋转
    /// <summary>
    /// 手势旋转速度
    /// </summary>
    private static float gestureRotateSpeed = 100.0f;

    /// <summary>
    /// 手势旋转阈值
    /// </summary>
    private static float gestureRotateThreshold = 0.3f;
    #endregion

    #region 缩放
    /// <summary>
    /// 缩放速度
    /// </summary>
    private static float scaleSpeed = 1.0f;

    /// <summary>
    /// 缩放阈值
    /// </summary>
    private static float scaleThreshold = 1f;

    /// <summary>
    /// 最大缩放倍数
    /// </summary>
    private static float maxScaleSize = 5.0f;

    /// <summary>
    /// 最小缩放倍数
    /// </summary>
    private static float minScaleSize = 1.0f;
    #endregion

    #region Unity
    private void Start()
    {
        this.rigidBody2D = this.GetComponent<Rigidbody2D>();
        if (!this.rigidBody2D)
            this.rigidBody2D = this.gameObject.AddComponent<Rigidbody2D>();
    }
    #endregion

    #region interface
    /// <summary>
    /// 当按下时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        //鼠标左键:-1;鼠标右键:-2;鼠标中键:-3
        this.pointerEventDatas.Add(eventData);
        //按下的位置
        Vector3 prePostion = Camera.main.ScreenToWorldPoint(eventData.pressPosition);
        //偏移
        if (this.pressOffSet == Vector3.zero)
            this.pressOffSet = (Vector3)this.rigidBody2D.position - prePostion;
    }

    /// <summary>
    /// 当拖拽时
    /// </summary>
    /// <param name="eventData"></param>
    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        if (this.pointerEventDatas.Count <= 0)
            return;
        if (this.pointerEventDatas.Count == 1)
        {
            Vector3 worldPostion = Camera.main.ScreenToWorldPoint(eventData.position);
            Vector3 tarPos = worldPostion + this.pressOffSet;
            //限制位置
            if (Vector3.Distance(this.rigidBody2D.position, tarPos) <= Entity2D.gestureMoveThreshold)
                return;
            this.rigidBody2D.position = tarPos;
        }
        else if (this.pointerEventDatas.Count == 2)
        {
            #region 初始化位置
            if (this.touch00Postion == Vector2.zero && this.touch01Postion == Vector2.zero)
            {
                this.touch00Postion = this.pointerEventDatas[0].position;
                this.touch01Postion = this.pointerEventDatas[1].position;
                return;
            }
            #endregion

            this.Rotating();
            this.Zoom();

            #region 更新位置
            this.touch00Postion = this.pointerEventDatas[0].position;
            this.touch01Postion = this.pointerEventDatas[1].position;
            #endregion
        }
    }

    /// <summary>
    /// 当抬起时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        for (int i = 0; i < this.pointerEventDatas.Count; i++)
            if (this.pointerEventDatas[i].pointerId == eventData.pointerId)
            {
                this.pointerEventDatas.RemoveAt(i);
                break;
            }

        //偏移归零
        if (this.pointerEventDatas.Count <= 0)
            this.pressOffSet = Vector3.zero;
        //更新偏移
        else if (this.pointerEventDatas.Count == 1)
            this.pressOffSet = (Vector3)this.rigidBody2D.position - Camera.main.ScreenToWorldPoint(this.pointerEventDatas[0].position);
    }
    #endregion

    #region PrivateFunction
    /// <summary>
    /// 旋转
    /// </summary>
    private void Rotating()
    {
        //上一帧方向
        Vector2 lastDir = (this.touch00Postion - this.touch01Postion).normalized;
        //当前帧方向
        Vector2 currentDir = (this.pointerEventDatas[0].position - this.pointerEventDatas[1].position).normalized;
        //角度差
        float angle = this.VectorAngle(lastDir, currentDir);
        if (angle < -Entity2D.gestureRotateThreshold)
            this.transform.Rotate(Vector3.forward, Time.deltaTime * Entity2D.gestureRotateSpeed);
        else if (angle > Entity2D.gestureRotateThreshold)
            this.transform.Rotate(Vector3.forward, -Time.deltaTime * Entity2D.gestureRotateSpeed);
    }

    /// <summary>
    /// 缩放
    /// </summary>
    private void Zoom()
    {
        //上一帧距离
        float lastDistance = Vector2.Distance(this.touch00Postion, this.touch01Postion);
        //当前帧距离
        float currentDistance = Vector2.Distance(this.pointerEventDatas[0].position, this.pointerEventDatas[1].position);
        //差值
        float difference = lastDistance - currentDistance;
        if (difference < -Entity2D.scaleThreshold)
        {
            this.transform.localScale = Vector3.Lerp(this.transform.localScale, Vector3.one * Entity2D.maxScaleSize, Time.deltaTime * Entity2D.scaleSpeed);
            if (Vector3.Distance(this.transform.localScale, Vector3.one * Entity2D.maxScaleSize) <= 0.01f)
                this.transform.localScale = Vector3.one * Entity2D.maxScaleSize;
        }
        else if (difference > Entity2D.scaleThreshold)
        {
            this.transform.localScale = Vector3.Lerp(this.transform.localScale, Vector3.one * Entity2D.minScaleSize, Time.deltaTime * Entity2D.scaleSpeed);
            if (Vector3.Distance(this.transform.localScale, Vector3.one * Entity2D.minScaleSize) <= 0.01f)
                this.transform.localScale = Vector3.one * Entity2D.minScaleSize;
        }
    }

    /// <summary>
    /// 计算两个向量之间的夹角(-180 ,180)
    /// </summary>
    /// <param name="last"></param>
    /// <param name="curr"></param>
    /// <returns></returns>
    public float VectorAngle(Vector2 last, Vector2 curr)
    {
        Vector3 cross = Vector3.Cross(last, curr);
        float angle = Vector2.Angle(last, curr);
        return cross.z > 0 ? -angle : angle;
    }
    #endregion
}

3D脚本:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 3D实体
/// </summary>
public class Entity3D : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
{
    #region 触摸点
    /// <summary>
    /// 触摸事件参数集合
    /// </summary>
    private List<PointerEventData> pointerEventDatas = new List<PointerEventData>(2);

    /// <summary>
    /// Touch触摸点1的位置
    /// </summary>
    private Vector2 touch00Postion;

    /// <summary>
    /// Touch触摸点2的位置
    /// </summary>
    private Vector2 touch01Postion;
    #endregion

    #region 移动
    /// <summary>
    /// 偏移
    /// </summary>
    private Vector3 pressOffSet;

    /// <summary>
    /// 手势移动阈值
    /// </summary>
    private static float gestureMoveThreshold = 0.1f;
    #endregion

    #region 旋转
    /// <summary>
    /// 手势旋转速度
    /// </summary>
    private static float gestureRotateSpeed = 100.0f;

    /// <summary>
    /// 手势旋转阈值
    /// </summary>
    private static float gestureRotateThreshold = 0.3f;
    #endregion

    #region 缩放
    /// <summary>
    /// 缩放速度
    /// </summary>
    private static float scaleSpeed = 1.0f;

    /// <summary>
    /// 缩放阈值
    /// </summary>
    private static float scaleThreshold = 1f;

    /// <summary>
    /// 最大缩放倍数
    /// </summary>
    private static float maxScaleSize = 5.0f;

    /// <summary>
    /// 最小缩放倍数
    /// </summary>
    private static float minScaleSize = 1.0f;
    #endregion

    #region interface
    /// <summary>
    /// 当按下时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        //鼠标左键:-1;鼠标右键:-2;鼠标中键:-3
        this.pointerEventDatas.Add(eventData);
        //按下的位置
        Vector3 prePostion = Camera.main.ScreenToWorldPoint(this.PointerEventDataPostionToVector3(eventData.pressPosition));
        //偏移
        if (this.pressOffSet == Vector3.zero)
            this.pressOffSet = (Vector3)this.transform.position - prePostion;
    }

    /// <summary>
    /// 当拖拽时
    /// </summary>
    /// <param name="eventData"></param>
    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        if (this.pointerEventDatas.Count <= 0)
            return;
        if (this.pointerEventDatas.Count == 1)
        {
            Vector3 worldPostion = Camera.main.ScreenToWorldPoint(this.PointerEventDataPostionToVector3(eventData.position));
            Vector3 tarPos = worldPostion + this.pressOffSet;
            //限制位置
            if (Vector3.Distance(this.transform.position, tarPos) <= Entity3D.gestureMoveThreshold)
                return;
            this.transform.position = tarPos;
        }
        else if (this.pointerEventDatas.Count == 2)
        {
            #region 初始化位置
            if (this.touch00Postion == Vector2.zero && this.touch01Postion == Vector2.zero)
            {
                this.touch00Postion = this.pointerEventDatas[0].position;
                this.touch01Postion = this.pointerEventDatas[1].position;
                return;
            }
            #endregion

            this.Rotating();
            this.Zoom();

            #region 更新位置
            this.touch00Postion = this.pointerEventDatas[0].position;
            this.touch01Postion = this.pointerEventDatas[1].position;
            #endregion
        }
    }

    /// <summary>
    /// 当抬起时
    /// </summary>
    /// <param name="eventData"></param>
    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        for (int i = 0; i < this.pointerEventDatas.Count; i++)
            if (this.pointerEventDatas[i].pointerId == eventData.pointerId)
            {
                this.pointerEventDatas.RemoveAt(i);
                break;
            }

        //偏移归零
        if (this.pointerEventDatas.Count <= 0)
            this.pressOffSet = Vector3.zero;
        //更新偏移
        else if (this.pointerEventDatas.Count == 1)
            this.pressOffSet = this.transform.position - Camera.main.ScreenToWorldPoint(
                this.PointerEventDataPostionToVector3(this.pointerEventDatas[0].position));
    }
    #endregion

    #region PrivateFunction
    /// <summary>
    /// 旋转
    /// </summary>
    private void Rotating()
    {
        //上一帧方向
        Vector2 lastDir = (this.touch00Postion - this.touch01Postion).normalized;
        //当前帧方向
        Vector2 currentDir = (this.pointerEventDatas[0].position - this.pointerEventDatas[1].position).normalized;
        //角度差
        float angle = this.VectorAngle(lastDir, currentDir);
        if (angle < -Entity3D.gestureRotateThreshold)
            this.transform.Rotate(Vector3.forward, Time.deltaTime * Entity3D.gestureRotateSpeed);
        else if (angle > Entity3D.gestureRotateThreshold)
            this.transform.Rotate(Vector3.forward, -Time.deltaTime * Entity3D.gestureRotateSpeed);
    }

    /// <summary>
    /// 缩放
    /// </summary>
    private void Zoom()
    {
        //上一帧距离
        float lastDistance = Vector2.Distance(this.touch00Postion, this.touch01Postion);
        //当前帧距离
        float currentDistance = Vector2.Distance(this.pointerEventDatas[0].position, this.pointerEventDatas[1].position);
        //差值
        float difference = lastDistance - currentDistance;
        if (difference < -Entity3D.scaleThreshold)
        {
            this.transform.localScale = Vector3.Lerp(this.transform.localScale, Vector3.one * Entity3D.maxScaleSize, Time.deltaTime * Entity3D.scaleSpeed);
            if (Vector3.Distance(this.transform.localScale, Vector3.one * Entity3D.maxScaleSize) <= 0.01f)
                this.transform.localScale = Vector3.one * Entity3D.maxScaleSize;
        }
        else if (difference > Entity3D.scaleThreshold)
        {
            this.transform.localScale = Vector3.Lerp(this.transform.localScale, Vector3.one * Entity3D.minScaleSize, Time.deltaTime * Entity3D.scaleSpeed);
            if (Vector3.Distance(this.transform.localScale, Vector3.one * Entity3D.minScaleSize) <= 0.01f)
                this.transform.localScale = Vector3.one * Entity3D.minScaleSize;
        }
    }

    /// <summary>
    /// 计算两个向量之间的夹角(-180 ,180)
    /// </summary>
    /// <param name="last">上一帧</param>
    /// <param name="curr">当前帧</param>
    /// <returns></returns>
    internal float VectorAngle(Vector2 last, Vector2 curr)
    {
        Vector3 cross = Vector3.Cross(last, curr);
        float angle = Vector2.Angle(last, curr);
        return cross.z > 0 ? -angle : angle;
    }

    /// <summary>
    /// 将PointerEventData位置转换为Vector3
    /// </summary>
    /// <param name="postion">PointerEventData位置</param>
    /// <returns></returns>
    internal Vector3 PointerEventDataPostionToVector3(Vector2 postion)
    {
        //取距离相机距离
        float z = Mathf.Abs(Camera.main.transform.position.z - this.transform.position.z);
        return new Vector3(postion.x, postion.y, z);
    }
    #endregion
}

注意:2D脚本中本人是通过Rigidbody2D来移动位置,是因为如果直接通过Transform来移动位置会导致物理检测可能会有问题,官方手册有说明:Rigidbody2D,另外2D刚体默认有重力,可以设置为Kinematic或自行处理。

本人水平有限,欢迎指正!!!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值