Unity之Touch触摸屏单指、多指触碰

打印 上一主题 下一主题

主题 810|帖子 810|积分 2430

一、效果展示



二、前言

我们制作多指触碰主要用到Unity中已经封装好的Touch类来制作,首先来看看unity官方对于Touch的描述:地址
   在移动设备上,Input 类提供对触摸屏、加速度计和地理/位置输入的访问。
  通过 iOS 键盘可以访问移动设备上的键盘。
  iPhone、iPad 和 iPod Touch 设备最多可跟踪五根手指同时触摸屏幕。可通过访问 Input.touches 属性数组来获取在最后一帧期间触摸屏幕的每根手指的状态。
  Android 设备对其跟踪的手指数量没有统一限制。相反,此限制因设备而异,可能是旧设备上的双手指触摸到某些新设备上的五指触摸。
    通过上边的描述我们可以知道几点重要信息
  1.iPhone、iPad 和 iPod Touch 设备最多可跟踪五根手指同时触摸屏幕
  2.Android 设备跟踪的手指数量没有统一限制,这个限制是由设备决定的
  3.所有的touch操作是在最后一帧来处理。
   Input类中要使用到的函数:
   1.Input.touches :这是一个Touch[]数组,里面存储了所有手指在屏幕的触摸(Touch类)
  2.Input. touchCount: 获取屏幕中触摸的数量
  3.Input.GetTouch(int i):获取Input.touches数组中的touch类,这里要注意,当触摸在屏幕抬起时,Touch在Input.touches数组下标会变更。
  举例:当前有个三个手指触摸 A、B、C,在Input.touches 数组中分别对应其下标为0,1,2.即:A(下标 0)、B(下标 1)、C(下标 2),当我们抬起手指A,然后在按下,此时他们的下标就变成,B(下标 0)、C(下标 1)、A(下标 2)。
  所以我们在做多指时不能通过索引来获取唯一的touch类,因为当一个touch抬起时,后面的touch索引会一次往前进一位。为了避免这个不唯一问题可以用到Touch类的fingerld。
   好的现在我们再来看一下Touch类的一些主要函数:
   1.fingerld: 触摸的唯一索引,上面说过touch的索引在生命周期为结束时会进行更改,不能使用索引代表其唯一性,fingerld在生命周期结束前是不会更改的。
  2.position:   触摸屏幕的位置
  3.deltatime: 从最后状态到目前状态所经过的时间
  4.tapCount: 点击数。Andorid设备不对点击计数,这个方法总是返回1
  5.deltaPosition: 自最后一帧所改变的屏幕位置
  6.phase相位,也即屏幕操作状态,其中phase(状态)有以下这几种:
    (1) Began 手指刚刚触摸屏幕
      (2) Moved 手指在屏幕上移动
      (3) Stationary 手指触摸屏幕,但自最后一帧没有移动
      (4) Ended 手指离开屏幕
      (5) Canceled系统取消触控跟踪,原因如把设备放在脸上或同时超过5个触摸点
  三、案例实现

1.单指触碰到指定区域时控制物体旋转,双指触碰到指定区域控制物体放大缩小,并且不受其他指头触摸影响
   思路:因为Touch的属性是在最后一帧来处理,所以在unity生命周期中我们要在Update中执行,我们通过for循环来遍历Input.touches中所有的touch,然后让相机向Touch的position位置发射一条射线,如果检测碰撞(通过层级判断,如:item层)到可以控制的物体身上,就获取这个物体身上的item类来执行对应的方法(单指或双指方法)。这里每个可以操控的物体都有一个Item脚本,这个脚本中存在单指和双指的执行方法
  具体实现:
DoubleTouchManager类,负责遍历touch射线检测是否碰撞到物体
   PS:这里面我加了一个鼠标左键控制旋转,中间滑轮控制放大缩小的功能。脚本中有详细注释
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine.UI;
  4. using UnityEngine;
  5. using UnityEngine.EventSystems;
  6. /// <summary>
  7. /// 多指操作管理器
  8. /// </summary>
  9. public class DoubleTouchManager : MonoBehaviour
  10. {
  11.     public LayerMask layer;
  12.     static DoubleTouchManager intance;
  13.     public static DoubleTouchManager GetIntance()
  14.     {
  15.         return intance;
  16.     }
  17.     private void Awake()
  18.     {
  19.         intance = this;
  20.     }
  21.     void Start()
  22.     {
  23.     }
  24.     TouchItem _mouseObj;//射线检测获取item
  25.     //封装 用于item判断是否10秒后位置复原
  26.     public TouchItem MouseObj { get => _mouseObj; set => _mouseObj = value; }
  27.     void Update()
  28.     {
  29.         if (Input.GetKeyDown(KeyCode.Escape))
  30.         {
  31.             Debug.Log("按下ESC退出");
  32.             Application.Quit();
  33.         }
  34.         #region 鼠标控制旋转缩放
  35.         //没有手指触摸时才可执行鼠标
  36.         if (Input.touches.Length <= 0)
  37.         {
  38.             //鼠标左键按下 记录鼠标位置
  39.             if (Input.GetMouseButtonDown(0))
  40.             {
  41.                 MouseObj = RayDetection(Input.mousePosition);
  42.                 Debug.Log("1: "+MouseObj);
  43.                 //如果没有检测到3D物体,则检测ui
  44.                 if (MouseObj == null)
  45.                     MouseObj = GetFirstPickGameObject(Input.mousePosition);
  46.                 Debug.Log(MouseObj);
  47.                 //item为空不执行
  48.                 if (MouseObj != null)
  49.                     MouseObj.OnMouseDownFountion();//修改item的oldPos值;
  50.             }
  51.             //鼠标拖动旋转物体碰撞的物体不为空
  52.             if (Input.GetMouseButton(0) && MouseObj != null)
  53.                 MouseObj.OnMouseFountion();
  54.             //鼠标滚轮控制放大缩小
  55.             float value = Input.GetAxis("Mouse ScrollWheel");
  56.             if (value != 0)
  57.             {
  58.                 //滑动发出射线获取碰撞到的item
  59.                 TouchItem _obj = RayDetection(Input.mousePosition);
  60.                 //如果没有检测到3D物体,则检测ui
  61.                 if (MouseObj == null)
  62.                     MouseObj = GetFirstPickGameObject(Input.mousePosition);
  63.                 //item为空不执行
  64.                 if (_obj != null)
  65.                     _obj.OnMouseWheelFountion(value);
  66.             }
  67.             //鼠标拖动旋转物体碰撞的物体不为空
  68.             if (Input.GetMouseButtonUp(0))
  69.                 MouseObj = null;
  70.             return;
  71.         }
  72.         #endregion
  73.         #region 多指触摸
  74.         for (int i = 0; i < Input.touches.Length; i++)
  75.         {
  76.             //射线检测获取Item
  77.             TouchItem touchObj = RayDetection(Input.GetTouch(i).position);
  78.             //如果没有检测到3D物体,则检测ui
  79.             if (touchObj == null)
  80.                 touchObj = GetFirstPickGameObject(Input.GetTouch(i).position);
  81.             if (touchObj != null)
  82.             {
  83.                 if (!touchObj.istouch1)
  84.                 {
  85.                     //手指1不存在时,执行
  86.                     touchObj.Touch1(i);
  87.                 }
  88.                 else if (touchObj.istouch1 && touchObj.touch1ID == Input.GetTouch(i).fingerId)
  89.                 {
  90.                     //手指1存在 并且这个手指id相同时
  91.                     touchObj.Touch1(i);
  92.                 }
  93.                 else
  94.                 {
  95.                     //手指1存在 手指不相同
  96.                     touchObj.Touch2(i);
  97.                 }
  98.                 Debug.Log("检测到物体");
  99.             }
  100.         }
  101.         #endregion
  102.     }
  103.     #region 3D物体射线检测
  104.     TouchItem RayDetection(Vector2 pos)
  105.     {
  106.         Ray ray = Camera.main.ScreenPointToRay(pos);
  107.         RaycastHit hit;
  108.         if (Physics.Raycast(ray, out hit, int.MaxValue, layer))
  109.             return hit.transform.GetComponent<TouchItem>();
  110.         return null;
  111.     }
  112.     #endregion
  113.     #region UI射线检测
  114.     /// <summary>
  115.     /// 获取点击的UI类型Item
  116.     /// </summary>
  117.     /// <param name="position">点击屏幕坐标</param>
  118.     /// <returns></returns>
  119.     public TouchItem GetFirstPickGameObject(Vector2 position)
  120.     {
  121.         EventSystem eventSystem = EventSystem.current;
  122.         PointerEventData pointerEventData = new PointerEventData(eventSystem);
  123.         pointerEventData.position = position;
  124.         //射线检测ui
  125.         List<RaycastResult> uiRaycastResultCache = new List<RaycastResult>();
  126.         eventSystem.RaycastAll(pointerEventData, uiRaycastResultCache);
  127.         if (uiRaycastResultCache.Count > 0 && uiRaycastResultCache[0].gameObject.GetComponent<TouchItem>() != null)
  128.             return uiRaycastResultCache[0].gameObject.GetComponent<TouchItem>();
  129.         return null;
  130.     }
  131.     #endregion
  132. }
复制代码
Item类:
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 触摸item 基类
  6. /// </summary>
  7. public class TouchItem : MonoBehaviour
  8. {
  9.     //单双指位置
  10.     protected Vector2 oldPos1;
  11.     protected Vector2 oldPos2;
  12.     [SerializeField] string layer = "touchItem";  //层级
  13.     [Range(0, 10f)]
  14.     [SerializeField] protected float rotSpeed = 10;  //旋转速度
  15.     protected Vector3 originRot;//原旋转位置
  16.     protected Vector3 originScale;//原大小
  17.     public bool istouch1; //触摸1是否存在
  18.     public bool istouch2; //触摸2是否存在
  19.     public float minSize = 0.9f;
  20.     public float maxSize = 1.3f;
  21.     public Transform moveobj;
  22.     void Start()
  23.     {
  24.         InitData();//参数初始化
  25.     }
  26.     void Update()
  27.     {
  28.         Recover();//无人操作复原
  29.     }
  30.     #region 初始化参数
  31.     /// <summary>
  32.     /// 初始化参数
  33.     /// </summary>
  34.     protected virtual void InitData()
  35.     {
  36.         istouch1 = false;
  37.         istouch2 = false;
  38.         timer = Time.realtimeSinceStartup;
  39.         //3D移动物体原旋转和大小
  40.         if (moveobj != null)
  41.         {
  42.             originRot = moveobj.localEulerAngles;
  43.             originScale = moveobj.localScale;
  44.         }
  45.         //初始化layer
  46.         gameObject.layer = LayerMask.NameToLayer(layer);
  47.     }
  48.     #endregion
  49.     #region Update无人操作复原
  50.     protected float timer = 0;
  51.     /// <summary>
  52.     /// Update无人操作复原
  53.     /// </summary>
  54.     protected virtual void Recover()
  55.     {
  56.         //当没有触摸时并且鼠标选中的item不是自己时,每过10秒执行位置大小复原
  57.         if (touch1ID == -1 && DoubleTouchManager.GetIntance().MouseObj != this)
  58.         {
  59.             if (Time.realtimeSinceStartup - timer >= 10f)
  60.             {
  61.                 if (moveobj != null)
  62.                 {
  63.                     moveobj.eulerAngles = originRot;
  64.                     moveobj.localScale = originScale;
  65.                 }
  66.                 timer = Time.realtimeSinceStartup;
  67.             }
  68.         }
  69.         else
  70.             timer = Time.realtimeSinceStartup;
  71.     }
  72.     #endregion
  73.     #region 单指操作
  74.     public int touch1ID = -1;
  75.     protected int touch1Index = -1;
  76.     public virtual void Touch1(int i)
  77.     {
  78.         //当有双指时 不执行旋转
  79.         if (istouch2)
  80.             return;
  81.         touch1Index = i;
  82.         istouch1 = true;
  83.         //防止多次触碰出现问题
  84.         if (touch1Index >= Input.touchCount)
  85.         {
  86.             //Debug.Log("单指 防止多次触碰出现问题");
  87.             touch1ID = -1;
  88.             touch1Index = -1;
  89.             istouch1 = false;
  90.             return;
  91.         }
  92.         Touch touch = Input.GetTouch(touch1Index);
  93.         //判断id是否相同
  94.         if (touch1ID == -1)
  95.             touch1ID = touch.fingerId;
  96.         else
  97.             if (touch1ID != touch.fingerId)
  98.         {
  99.             touch1ID = -1;
  100.             touch1Index = -1;
  101.             istouch1 = false;
  102.             //id不同 不执行
  103.             return;
  104.         }
  105.         //单指操作
  106.         if (touch.phase == TouchPhase.Began)
  107.             SingleBeganOperation(touch.position); //单指操作
  108.         if (touch.phase == TouchPhase.Moved)
  109.         {
  110.             SingleMovedOperation(touch.position);
  111.             //Debug.Log("旋转");
  112.         }
  113.         //手指抬起 或者系统取消对触摸的跟踪
  114.         if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
  115.         {
  116.             SingleEndOperation(); //抬起
  117.             //Debug.Log("手指抬起 或者系统取消对触摸的跟踪");
  118.         }
  119.     }
  120.     #endregion
  121.     #region 双指操作
  122.     protected int touch2ID = -1;
  123.    
  124.     public virtual void Touch2(int i)
  125.     {
  126.         istouch2 = true;
  127.         //防止多次触碰出现问题
  128.         if (touch1Index >= Input.touchCount || i >= Input.touchCount)
  129.         {
  130.             //Debug.Log("多指: 防止多次触碰出现问题");
  131.             touch1ID = -1;
  132.             touch2ID = -1;
  133.             istouch1 = false;
  134.             istouch2 = false;
  135.             return;
  136.         }
  137.         Touch touch1 = Input.GetTouch(touch1Index);
  138.         Touch touch2 = Input.GetTouch(i);
  139.         //判断id是否相同
  140.         if (touch1ID == -1 || touch2ID == -1)
  141.         {
  142.             touch1ID = touch1.fingerId;
  143.             touch2ID = touch2.fingerId;
  144.         }
  145.         else
  146.         {
  147.             if (touch1ID != touch1.fingerId || touch2ID != touch1.fingerId)
  148.             {
  149.                 //id不同 不执行
  150.                 touch1ID = -1;
  151.                 touch2ID = -1;
  152.                 istouch1 = false;
  153.                 istouch2 = false;
  154.                 return;
  155.             }
  156.         }
  157.         //双指操作
  158.         if (touch2.phase == TouchPhase.Began)
  159.         {
  160.             Debug.Log("多指************1");
  161.             oldPos1 = touch1.position;
  162.             oldPos2 = touch2.position;
  163.             return;
  164.         }
  165.         if (touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved)
  166.         {
  167.             Debug.Log("多指************2");
  168.             float oldDistance = Vector2.Distance(oldPos1, oldPos2);  //计算原先两指的距离
  169.             float newDistance = Vector2.Distance(touch1.position, touch2.position);  //当前移动后两指的距离
  170.             //(新距离)减去(旧距离)得出的差如果是负数的话缩小,正数就放大
  171.             float offset = newDistance - oldDistance;
  172.             //Debug.Log("3: 判断两指有一个在运动时: " + offset);
  173.             //放大因子, 一个像素按 0.01倍来算(100可调整)
  174.             float scaleFactor = offset / 100;
  175.             //计算物体scale要放大的值
  176.             Vector3 localScale = moveobj.localScale + (Vector3.one * scaleFactor);
  177.             //设置放大缩小的范围值
  178.             Vector3 scale = new Vector3(Mathf.Clamp(localScale.x, minSize, maxSize),
  179.                 Mathf.Clamp(localScale.y, minSize, maxSize),
  180.                 Mathf.Clamp(localScale.z, minSize, maxSize));
  181.             moveobj.localScale = scale;//赋值
  182.             Debug.Log("大小: " + scale);
  183.             //记住最新的触摸点位置,下次使用  
  184.             oldPos1 = touch1.position;
  185.             oldPos2 = touch2.position;
  186.         }
  187.         if (touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended)
  188.         {
  189.             touch1ID = -1;
  190.             touch2ID = -1;
  191.             istouch1 = false;
  192.             istouch2 = false;
  193.             //Debug.Log("手指抬起2");
  194.         }
  195.         if (touch1.phase == TouchPhase.Canceled || touch2.phase == TouchPhase.Canceled)
  196.         {
  197.             touch1ID = -1;
  198.             touch2ID = -1;
  199.             istouch1 = false;
  200.             istouch2 = false;
  201.             //Debug.Log("系统取消对触摸的跟踪2");
  202.         }
  203.     }
  204.     #endregion
  205.     #region 鼠标控制旋转和放大缩小
  206.     public virtual void OnMouseDownFountion()
  207.     {
  208.         //Debug.Log("按下");
  209.         //开始按下操作
  210.         SingleBeganOperation(Input.mousePosition);
  211.     }
  212.     //左键控制旋转
  213.     public virtual void OnMouseFountion()
  214.     {
  215.         //Debug.Log("持续");
  216.         SingleMovedOperation(Input.mousePosition);
  217.     }
  218.     //左键抬起
  219.     public virtual void OnMouseUpFountion()
  220.     {
  221.         //Debug.Log("抬起");
  222.         SingleEndOperation();
  223.     }
  224.     //滑轮控制放大
  225.     public virtual void OnMouseWheelFountion(float value)
  226.     {
  227.         Debug.Log("滑轮");
  228.         //计算物体scale要放大的值
  229.         Vector3 localScale = moveobj.localScale + Vector3.one * value;
  230.         //设置放大缩小的范围值
  231.         Vector3 scale = new Vector3(Mathf.Clamp(localScale.x, minSize, maxSize),
  232.             Mathf.Clamp(localScale.y, minSize, maxSize),
  233.             Mathf.Clamp(localScale.z, minSize, maxSize));
  234.         moveobj.localScale = scale;//赋值
  235.         //Debug.Log("大小: " + scale);
  236.     }
  237.     #endregion
  238.     #region 单指 (按下、持续按下、抬起操作)
  239.     /// <summary>
  240.     /// 开始操作
  241.     /// </summary>
  242.     /// <param name="pos">位置</param>
  243.     protected virtual void SingleBeganOperation(Vector3 pos)
  244.     {
  245.         //Debug.Log("按下");
  246.         oldPos1 = pos;
  247.     }
  248.     /// <summary>
  249.     /// 持续操作
  250.     /// </summary>
  251.     /// <param name="pos">位置</param>
  252.     protected virtual void SingleMovedOperation(Vector3 pos)
  253.     {
  254.         //Debug.Log("持续");
  255.         Vector3 newRot = new Vector3((pos.y - oldPos1.y), -(pos.x - oldPos1.x), 0) * Time.deltaTime * rotSpeed;
  256.         var rotation = Quaternion.Euler(newRot);
  257.         moveobj.rotation *= rotation;
  258.         //moveobj.eulerAngles = new Vector3(moveobj.eulerAngles.x, moveobj.eulerAngles.y, 0);
  259.         moveobj.eulerAngles = new Vector3(0, moveobj.eulerAngles.y, 0); //只让Y轴旋转
  260.         oldPos1 = pos;
  261.     }
  262.     /// <summary>
  263.     /// 结束操作
  264.     /// </summary>
  265.     /// <param name="pos">=位置</param>
  266.     protected virtual void SingleEndOperation()
  267.     {
  268.         touch1ID = -1;
  269.         touch1Index = -1;
  270.         istouch1 = false;
  271.     }
  272.     #endregion
  273. }
复制代码
场景布局:



 然后打包即可,注意在触摸屏中Input.GetMouse()这种获取鼠标单机事件的函数可以触发手指触摸,但是touch在没有触摸功能的设备上使用鼠标是无法检测的,所以要测试这个效果需要打包到有触摸功能的设备上才可以执行。
四、项目包

https://pan.baidu.com/s/1Im9JCz83WCdv-cK8f6L8Qg 
提取码:syq1 


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表