添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Unity 操作检测的各种实现#2手机端 - 触屏与手势

写在前面

继续填坑了。这次是讲手机端。

  1. 电脑端 [ 按我跳转 ]
    1. 键盘按键控制 [ 上文内容 ]
    2. 鼠标点击控制 [ 上文内容 ]
  2. 手机端
    1. 触屏控制 [ 本文内容 ]
    2. 手势控制 [ 本文内容 ]
    3. 重力控制 [ 下文内容 ]
    4. 陀螺仪控制 [ 下文内容 ]

本文的限制 :接上文,本文主要讲在手机检查操作的方法,至于之后执行的相关命令,读者完全可大开脑洞。如果你有什么好的想法,欢迎告诉我!

正文

1 触屏控制

试想象 神庙逃亡 这类游戏,你能想到些什么操作方式?通常玩家会使用 手指 手势 来操控游戏。所谓的手势指的是,类似下划,上划一类的操作方式。

检测触屏,需要用到,由MonoBehaviour提供的OnMouseDown()方法。这个方法在电脑和手机端都可以调用。

例子

void OnMouseDown()
    Debug.Log("The Button is pressed"+this.name);
}

这个方法可以检测用户对 GUI Element (比如GUI Texture)和 碰撞体 触屏点击 。因此通常这个方法不会放进主程序的Update内,这是因为只要我们给点击相关物件的代码加入这个方法,那么当它被触摸时,就会调用OnMouseDown方法。

Unity Remote可以为电脑和手机提供接口,让游戏画面可以通过Unity的play按钮,在手机上同步呈现。读者可自行到goolg play商店下载安装。

然而,假设你为你的一些GUI button加入了这个方法,并做一些输出之后,你会发现一个问题。如果你试着同时按下两个按钮,会发现什么信息也没有出现,或者只出现其中一个按钮被按下的信息。

绝大部分的游戏都需要 多点触控 ,所以上面的方法显然还行不通。

先来了解 多点触控 的概念。看图

当玩家的手触碰到手机屏幕时,Unity便会触发 触碰事件 ,我们將手指与屏幕触碰过程中的所有资讯称为 触控信息 。Unity会为我们识别每一个触控信息所携带的ID、触控点位置、状态、点击次数等资讯。

聪明的你或许已经注意到了,每个触控点都会有一个属于它们的状态。当我们第一次触碰到屏幕时,第一笔的触控信息会被记录,并且设定状态为 Begin 。而当我们在继续触碰的情况下滑动手指,触控点的状态便更改为 Moved 。如果我们只是按住而没有滑动的动作,状态便设定为 Stationary 。当我们把手指离开屏幕,状态变为 Ended 。当然,即使我们的手指已经离开屏幕,Unity仍然会保留这笔状态一段时间,这是为了可以让那些短时间内再次触碰的动作减少资源消耗。当我们再次触碰屏幕,Unity便会再一次追踪触控信息。每次成功的触控追踪都会被记录为 触控事件 ,并且让资料中的点击次数加一。

如果玩家长时间不碰屏幕,或手机已经离开并触碰屏幕的其他地方。由于系统追踪不到原先的触控点,便会将该点的状态记录为 Canceled 。值得注意的是,在状态 Moved 中,触控信息会记录额外的位移与时间信息,供开发者作 手势判别 。这是我们下一个讨论内容,这里就先介绍到这。

上文提到的关于 触控信息 的内容,在Unity中会被存储在Touch类中,读者可前往文档详细阅读。

另外,触控点的状态则定义在TouchPhase类的结构中。这是一个Enum类别的数据。有Began, Moved, Stationary, Ended, Canceled五个状态。

小实践:多点触控

为了做到这一点,我们需要抛弃OnMouseDown这个方法,改而在游戏刷新的过程中不断检测触控的数量,根据触控的次数来作对应的操作命令

void Update(){
    int touchCount = Input.touchCount;  // 获取触控点个数
    if (touchCount > 0){  // 若玩家有触碰到屏幕
        for (int i = 0 ; i < touchCount ; i++ ){
            Touch touch = Input.GetTouch(i);  // 将触控点存储
            if (this.getTexture.HitTest(touch.position)){
                if (this.name == "Forward"){
                    Debug.Log("Forward");
                else if (this.name == "Jump"){
                    Debug.Log("Jump!");
                } // 若触控点有碰到按钮,则判断是按下了哪个按钮
}

接下来,你可以把这段代码挂到任何一个需要使用到触控检测的GUI element上。HitTest()是GUI element的方法,你可以将触控点的位置传入来作检测。如果触控点在GUI element的范围内,则回传true,反之回传false。

一些问题和可行的解决方案

当你试着实现多点触控时,你会发现一些事情,如果你一直按住某个按钮。比如Jump键,那么console信息栏中就会一直输出"Jump!"的信息。换句话说,按钮事件会一直处在被触发的状态。这好说,因为手指一直在按钮上,而这段代码则被写在Update中,那么每一帧都会执行这端代码,当然信息会一直输出了。这时读者可以使用上文提到的TouchPhase类进行点击状态的判断,进而减轻系统输出负担。

2 手势检测

多点触控的用处很多,手势的侦测就是其中一种。

source:wikipedia

当我们游玩神庙逃亡时,如果我们手指向上滑动,那么角色就会做出一个跳跃的动作。当然这里很可能是没有实现多点触控的,笔者只是想让读者思考手势检测的例子。

延申前文所提到的 光线投射 的概念,在结合本文的 触控检测 概念,就可以写出很美妙的手势操作判断了。笔者假设读者需要开发有多点触控功能的应用,因此延续上文的代码。

void Update(){
    int touchCount = Input.touchCount;
    Camera cam = Camera.main;
    if (touchCount > 0){
        for (int i = 0 ; i < touchCount ; i++ ){
            Touch touch = Input.GetTouch(i);
            if (touch.phase == TouchPhase.Bagan)){ // 如果触控点状态为按下
                Ray ray = cam.ScreenPointToRay(touch.position);  // 从触控点位置产生一条Ray
                RaycastHit hit;  // 用来检测碰撞的Ray
                if (Physics.Raycast(ray, out hit))
                    Debug.Log(hit.transform.name); // 输出点击物体名称
}

你可能会觉得,这不就是一个【按下】的功能嘛!说好的手势判断呢!哎先别打我啊,我们来看看程式设计的概念:如果玩家的手碰到了屏幕,再做一个向上滑动的手势后,就向上跳。设计手势时,可以善用触控点的状态资讯,就是TouchPhase。

我们可以把这个概念写作这样的流程:

  1. 玩家触碰到屏幕 - 触控点状态:TouchPhase.Began
  2. 玩家向上滑动 - 触控点状态:TouchPhase.Moved
  3. 玩家手指离开 - 触控点状态:TouchPhase.Ended

好的,代码可以写成这样:

...
int touchCount = Input.touchCount;
    Camera cam = Camera.main;
    if (touchCount > 0){
        for (int i = 0 ; i < touchCount ; i++ ){
            Touch touch = Input.GetTouch(i);
            if (touch.phase == TouchPhase.Bagan)){ // 如果触控点状态为按下
            else if (touch.phase == TouchPhase.Moved)){ // 如果触控点状态为移动
            else if (touch.phase == TouchPhase.Ended)){ // 如果触控点状态为离开屏幕
    }

读者可以看到,现在的代码被分成了三个区块,分别处理按下、移动和离开屏幕后的对应事件。由于我们提到的例子只需要检测向上划与否,所以处理按下时,只需检查玩家是否按到游戏内的碰撞体,并记录位置,方便计算即可。

按下的处理:

Vector2 startTouchPos; // pre-defined
Camera cam = Camera.main; // pre-defined
if (touch.phase == TouchPhase.Bagan)){ // 如果触控点状态为按下
   Ray ray = cam.ScreenPointToRay(Input.position);
   RaycastHit hit;
   if (Physics.Raycast(ray, out hit))
       Debug.Log(hit.transform.name); // 输出点击物体名称
       startTouchPos = touch.position;
...

移动的处理:

...
if (touch.phase == TouchPhase.Moved)){ // 如果触控点状态为按下
   if ( touch.position.y - startTouchPos.y > 0)
       Debug.Log("Jump!");