ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Unity之Touch触摸屏单指、多指触碰
[打印本页]
作者:
数据人与超自然意识
时间:
2022-6-23 16:37
标题:
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;
/// <summary>
/// 多指操作管理器
/// </summary>
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
TouchItem _obj = RayDetection(Input.mousePosition);
//如果没有检测到3D物体,则检测ui
if (MouseObj == null)
MouseObj = GetFirstPickGameObject(Input.mousePosition);
//item为空不执行
if (_obj != null)
_obj.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<TouchItem>();
return null;
}
#endregion
#region UI射线检测
/// <summary>
/// 获取点击的UI类型Item
/// </summary>
/// <param name="position">点击屏幕坐标</param>
/// <returns></returns>
public TouchItem GetFirstPickGameObject(Vector2 position)
{
EventSystem eventSystem = EventSystem.current;
PointerEventData pointerEventData = new PointerEventData(eventSystem);
pointerEventData.position = position;
//射线检测ui
List<RaycastResult> uiRaycastResultCache = new List<RaycastResult>();
eventSystem.RaycastAll(pointerEventData, uiRaycastResultCache);
if (uiRaycastResultCache.Count > 0 && uiRaycastResultCache[0].gameObject.GetComponent<TouchItem>() != null)
return uiRaycastResultCache[0].gameObject.GetComponent<TouchItem>();
return null;
}
#endregion
}
复制代码
Item类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 触摸item 基类
/// </summary>
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 初始化参数
/// <summary>
/// 初始化参数
/// </summary>
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;
/// <summary>
/// Update无人操作复原
/// </summary>
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 单指 (按下、持续按下、抬起操作)
/// <summary>
/// 开始操作
/// </summary>
/// <param name="pos">位置</param>
protected virtual void SingleBeganOperation(Vector3 pos)
{
//Debug.Log("按下");
oldPos1 = pos;
}
/// <summary>
/// 持续操作
/// </summary>
/// <param name="pos">位置</param>
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;
}
/// <summary>
/// 结束操作
/// </summary>
/// <param name="pos">=位置</param>
protected virtual void SingleEndOperation()
{
touch1ID = -1;
touch1Index = -1;
istouch1 = false;
}
#endregion
}
复制代码
场景布局:
然后打包即可,注意在触摸屏中Input.GetMouse()这种获取鼠标单机事件的函数可以触发手指触摸,但是touch在没有触摸功能的设备上使用鼠标是无法检测的,所以要测试这个效果需要打包到有触摸功能的设备上才可以执行。
四、项目包
https://pan.baidu.com/s/1Im9JCz83WCdv-cK8f6L8Qg
提取码:syq1
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4