网络知识 娱乐 Unity之Touch触摸屏单指、多指触碰

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

一、效果展示

二、前言

我们制作多指触碰主要用到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:这里面我加了一个鼠标左键控制旋转,中间滑轮控制放大缩小的功能。脚本中有详细注释

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

/// 
/// 多指操作管理器
/// 
public class DoubleTouchManager : MonoBehaviour
{
    public LayerMask layer;

    static DoubleTouchManager intance;
    public static DoubleTouchManager GetIntance()
    {
        return intance;
    }

    private void Awake()
    {
        intance = this;
    }
    void Start()
    {
    }

    TouchItem _mouseObj;//射线检测获取item

    //封装 用于item判断是否10秒后位置复原
    public TouchItem MouseObj { get => _mouseObj; set => _mouseObj = value; }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Debug.Log("按下ESC退出");
            Application.Quit();
        }

        #region 鼠标控制旋转缩放
        //没有手指触摸时才可执行鼠标
        if (Input.touches.Length <= 0)
        {
            //鼠标左键按下 记录鼠标位置
            if (Input.GetMouseButtonDown(0))
            {
                MouseObj = RayDetection(Input.mousePosition);
                Debug.Log("1: "+MouseObj);

                //如果没有检测到3D物体,则检测ui
                if (MouseObj == null)
                    MouseObj = GetFirstPickGameObject(Input.mousePosition);

                Debug.Log(MouseObj);

                //item为空不执行
                if (MouseObj != null)
                    MouseObj.OnMouseDownFountion();//修改item的oldPos值;
            }

            //鼠标拖动旋转物体碰撞的物体不为空
            if (Input.GetMouseButton(0) && MouseObj != null)
                MouseObj.OnMouseFountion();


            //鼠标滚轮控制放大缩小
            float value = Input.GetAxis("Mouse ScrollWheel");
            if (value != 0)
            {
                //滑动发出射线获取碰撞到的item
                MouseObj = RayDetection(Input.mousePosition);

                //如果没有检测到3D物体,则检测ui
                if (MouseObj == null)
                    MouseObj = GetFirstPickGameObject(Input.mousePosition);

                //item为空不执行
                if (MouseObj!= null)
                    MouseObj.OnMouseWheelFountion(value);
            }

            //鼠标拖动旋转物体碰撞的物体不为空
            if (Input.GetMouseButtonUp(0))
                MouseObj = null;

            return;
        }
        #endregion

        #region 多指触摸
        for (int i = 0; i < Input.touches.Length; i++)
        {
            //射线检测获取Item
            TouchItem touchObj = RayDetection(Input.GetTouch(i).position);
            //如果没有检测到3D物体,则检测ui
            if (touchObj == null)
                touchObj = GetFirstPickGameObject(Input.GetTouch(i).position);

            if (touchObj != null)
            {
                if (!touchObj.istouch1)
                {
                    //手指1不存在时,执行
                    touchObj.Touch1(i);
                }
                else if (touchObj.istouch1 && touchObj.touch1ID == Input.GetTouch(i).fingerId)
                {
                    //手指1存在 并且这个手指id相同时
                    touchObj.Touch1(i);
                }
                else
                {
                    //手指1存在 手指不相同
                    touchObj.Touch2(i);
                }

                Debug.Log("检测到物体");
            }
        }
        #endregion


    }

    #region 3D物体射线检测
    TouchItem RayDetection(Vector2 pos)
    {
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, int.MaxValue, layer))
            return hit.transform.GetComponent();

        return null;

    }

    #endregion

    #region UI射线检测
    /// 
    /// 获取点击的UI类型Item
    /// 
    /// 点击屏幕坐标
    /// 
    public TouchItem GetFirstPickGameObject(Vector2 position)
    {
        EventSystem eventSystem = EventSystem.current;
        PointerEventData pointerEventData = new PointerEventData(eventSystem);
        pointerEventData.position = position;
        //射线检测ui
        List uiRaycastResultCache = new List();
        eventSystem.RaycastAll(pointerEventData, uiRaycastResultCache);
        if (uiRaycastResultCache.Count > 0 && uiRaycastResultCache[0].gameObject.GetComponent() != null)
            return uiRaycastResultCache[0].gameObject.GetComponent();
        return null;
    }
    #endregion

}

Item类:

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

/// 
/// 触摸item 基类
/// 
public class TouchItem : MonoBehaviour
{
    //单双指位置
    protected Vector2 oldPos1;
    protected Vector2 oldPos2;

    [SerializeField] string layer = "touchItem";  //层级

    [Range(0, 10f)]
    [SerializeField] protected float rotSpeed = 10;  //旋转速度
    protected Vector3 originRot;//原旋转位置
    protected Vector3 originScale;//原大小
    public bool istouch1; //触摸1是否存在
    public bool istouch2; //触摸2是否存在

    public float minSize = 0.9f;
    public float maxSize = 1.3f;
    public Transform moveobj;

    void Start()
    {
        InitData();//参数初始化
    }

    void Update()
    {
        Recover();//无人操作复原
    }

    #region 初始化参数
    /// 
    /// 初始化参数
    /// 
    protected virtual void InitData()
    {
        istouch1 = false;
        istouch2 = false;
        timer = Time.realtimeSinceStartup;

        //3D移动物体原旋转和大小
        if (moveobj != null)
        {
            originRot = moveobj.localEulerAngles;
            originScale = moveobj.localScale;
        }


        //初始化layer
        gameObject.layer = LayerMask.NameToLayer(layer);
    }
    #endregion

    #region Update无人操作复原
    protected float timer = 0;
    /// 
    /// Update无人操作复原
    /// 
    protected virtual void Recover()
    {
        //当没有触摸时并且鼠标选中的item不是自己时,每过10秒执行位置大小复原
        if (touch1ID == -1 && DoubleTouchManager.GetIntance().MouseObj != this)
        {
            if (Time.realtimeSinceStartup - timer >= 10f)
            {
                if (moveobj != null)
                {
                    moveobj.eulerAngles = originRot;
                    moveobj.localScale = originScale;
                }

                timer = Time.realtimeSinceStartup;
            }
        }
        else
            timer = Time.realtimeSinceStartup;
    }
    #endregion

    #region 单指操作
    public int touch1ID = -1;
    protected int touch1Index = -1;
    public virtual void Touch1(int i)
    {
        //当有双指时 不执行旋转
        if (istouch2)
            return;

        touch1Index = i;
        istouch1 = true;

        //防止多次触碰出现问题
        if (touch1Index >= Input.touchCount)
        {
            //Debug.Log("单指 防止多次触碰出现问题");
            touch1ID = -1;
            touch1Index = -1;
            istouch1 = false;
            return;
        }

        Touch touch = Input.GetTouch(touch1Index);

        //判断id是否相同
        if (touch1ID == -1)
            touch1ID = touch.fingerId;
        else
            if (touch1ID != touch.fingerId)
        {
            touch1ID = -1;
            touch1Index = -1;
            istouch1 = false;
            //id不同 不执行
            return;
        }

        //单指操作 
        if (touch.phase == TouchPhase.Began)
            SingleBeganOperation(touch.position); //单指操作

        if (touch.phase == TouchPhase.Moved)
        {
            SingleMovedOperation(touch.position);
            //Debug.Log("旋转");
        }

        //手指抬起 或者系统取消对触摸的跟踪
        if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
        {
            SingleEndOperation(); //抬起
            //Debug.Log("手指抬起 或者系统取消对触摸的跟踪");
        }

    }
    #endregion

    #region 双指操作
    protected int touch2ID = -1;

    
    public virtual void Touch2(int i)
    {
        istouch2 = true;

        //防止多次触碰出现问题
        if (touch1Index >= Input.touchCount || i >= Input.touchCount)
        {
            //Debug.Log("多指: 防止多次触碰出现问题");
            touch1ID = -1;
            touch2ID = -1;
            istouch1 = false;
            istouch2 = false;
            return;
        }

        Touch touch1 = Input.GetTouch(touch1Index);
        Touch touch2 = Input.GetTouch(i);

        //判断id是否相同
        if (touch1ID == -1 || touch2ID == -1)
        {
            touch1ID = touch1.fingerId;
            touch2ID = touch2.fingerId;
        }
        else
        {
            if (touch1ID != touch1.fingerId || touch2ID != touch1.fingerId)
            {
                //id不同 不执行 
                touch1ID = -1;
                touch2ID = -1;
                istouch1 = false;
                istouch2 = false;
                return;
            }
        }


        //双指操作
        if (touch2.phase == TouchPhase.Began)
        {
            Debug.Log("多指************1");

            oldPos1 = touch1.position;
            oldPos2 = touch2.position;
            return;
        }

        if (touch1.phase == TouchPhase.Moved || touch2.phase == TouchPhase.Moved)
        {
            Debug.Log("多指************2");

            float oldDistance = Vector2.Distance(oldPos1, oldPos2);  //计算原先两指的距离
            float newDistance = Vector2.Distance(touch1.position, touch2.position);  //当前移动后两指的距离

            //(新距离)减去(旧距离)得出的差如果是负数的话缩小,正数就放大
            float offset = newDistance - oldDistance;
            //Debug.Log("3: 判断两指有一个在运动时: " + offset);

            //放大因子, 一个像素按 0.01倍来算(100可调整)
            float scaleFactor = offset / 100;
            //计算物体scale要放大的值
            Vector3 localScale = moveobj.localScale + (Vector3.one * scaleFactor);

            //设置放大缩小的范围值
            Vector3 scale = new Vector3(Mathf.Clamp(localScale.x, minSize, maxSize),
                Mathf.Clamp(localScale.y, minSize, maxSize),
                Mathf.Clamp(localScale.z, minSize, maxSize));

            moveobj.localScale = scale;//赋值
            Debug.Log("大小: " + scale);

            //记住最新的触摸点位置,下次使用  
            oldPos1 = touch1.position;
            oldPos2 = touch2.position;
        }

        if (touch1.phase == TouchPhase.Ended || touch2.phase == TouchPhase.Ended)
        {
            touch1ID = -1;
            touch2ID = -1;
            istouch1 = false;
            istouch2 = false;
            //Debug.Log("手指抬起2");
        }

        if (touch1.phase == TouchPhase.Canceled || touch2.phase == TouchPhase.Canceled)
        {
            touch1ID = -1;
            touch2ID = -1;
            istouch1 = false;
            istouch2 = false;
            //Debug.Log("系统取消对触摸的跟踪2");
        }

    }
    #endregion

    #region 鼠标控制旋转和放大缩小

    public virtual void OnMouseDownFountion()
    {
        //Debug.Log("按下");

        //开始按下操作
        SingleBeganOperation(Input.mousePosition);
    }

    //左键控制旋转
    public virtual void OnMouseFountion()
    {
        //Debug.Log("持续");
        SingleMovedOperation(Input.mousePosition);
    }

    //左键抬起
    public virtual void OnMouseUpFountion()
    {
        //Debug.Log("抬起");
        SingleEndOperation();
    }

    //滑轮控制放大
    public virtual void OnMouseWheelFountion(float value)
    {
        Debug.Log("滑轮");

        //计算物体scale要放大的值
        Vector3 localScale = moveobj.localScale + Vector3.one * value;

        //设置放大缩小的范围值
        Vector3 scale = new Vector3(Mathf.Clamp(localScale.x, minSize, maxSize),
            Mathf.Clamp(localScale.y, minSize, maxSize),
            Mathf.Clamp(localScale.z, minSize, maxSize));

        moveobj.localScale = scale;//赋值
        //Debug.Log("大小: " + scale);

    }
    #endregion

    #region 单指 (按下、持续按下、抬起操作)
    /// 
    /// 开始操作
    /// 
    /// 位置
    protected virtual void SingleBeganOperation(Vector3 pos)
    {
        //Debug.Log("按下");
        oldPos1 = pos;
    }

    /// 
    /// 持续操作
    /// 
    /// 位置
    protected virtual void SingleMovedOperation(Vector3 pos)
    {
        //Debug.Log("持续");
        Vector3 newRot = new Vector3((pos.y - oldPos1.y), -(pos.x - oldPos1.x), 0) * Time.deltaTime * rotSpeed;
        var rotation = Quaternion.Euler(newRot);
        moveobj.rotation *= rotation;
        //moveobj.eulerAngles = new Vector3(moveobj.eulerAngles.x, moveobj.eulerAngles.y, 0);
        moveobj.eulerAngles = new Vector3(0, moveobj.eulerAngles.y, 0); //只让Y轴旋转
        oldPos1 = pos;
    }

    /// 
    /// 结束操作
    /// 
    /// =位置
    protected virtual void SingleEndOperation()
    {
        touch1ID = -1;
        touch1Index = -1;
        istouch1 = false;
    }

    #endregion

}

场景布局:

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

四、项目包

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