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

    技术2022-07-11  140

    本人以前写过一篇关于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或自行处理。

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

    Processed: 0.010, SQL: 9