c# - 在现代Unity3D中捏和其他多指手势?

标签 c# unity3d touch

在现代Unity3D中,我们使用IPointerDownHandler调用家族。
关于IPointerDownHandler系列通话,

public class FingerMove:MonoBehaviour, IPointerDownHandler...
    {
    public void OnPointerDown (PointerEventData data)
        {
他们当然很棒
用于处理单点触摸。
但是,您如何认真处理多次触摸
您可以自己“手动完成”跟踪触摸,但是,令人难以置信的是Unity希望您以绝对如此简单的方式进行操作。 (我的意思是-这是一个游戏引擎。当然,我也可以编写自己的渲染图和物理学!)
这是一个基本的“牛仔编程”示例,无需软件工程即可手动完成。真正的解决方案是什么?
//
// example of programming a pinch (as well as swipes) using modern Unity
//
// here we are forced to track "by hand" in your own code
// how many fingers are down and which 
// fingers belong to you etc etc:
//

// pedagogic example code:

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

public class FingerMove:MonoBehaviour,
         IPointerDownHandler, IDragHandler, IPointerUpHandler
    {
    // these three for the ordinary one-finger-only drag
    private Vector2 prevPoint;
    private Vector2 newPoint;
    private Vector2 screenTravel;
    // and this one is the ordinary one-finger-only drag
    private int currentMainFinger = -1;
    
    // and this for the (strictly second finger only) drag...
    private int currentSecondFinger = -1;
    private Vector2 posA;
    private Vector2 posB;
    
    private float previousDistance = -1f;
    private float distance;
    private float pinchDelta = 0f;
    
    public void OnPointerDown (PointerEventData data)
        {
        if (currentMainFinger == -1)
            {
            // this is the NEW currentMainFinger
            currentMainFinger = data.pointerId;
            prevPoint = data.position;
            
            // and for the drag (if it becomes used)...
            posA = data.position;
            
            return;
            }
        
        if (currentSecondFinger == -1)
            {
            // this is the NEW currentSecondFinger
            currentSecondFinger = data.pointerId;
            posB = data.position;
            
            figureDelta();
            previousDistance = distance;
            
            return;
            }
        
        Debug.Log("third+ finger! (ignore)");
        }

    public void OnDrag (PointerEventData data)
        {
        // handle single-finger moves (swipes, drawing etc) only:
        
        if ( currentMainFinger == data.pointerId )
            {
            newPoint = data.position;
            screenTravel = newPoint - prevPoint;
            prevPoint = newPoint;
            
            if (currentSecondFinger == -1)
                {
                Debug.Log("NO 2f");
                _processSwipe(); // handle it your way
                }
            else
                {
                }
            
            // and for two-finger if it becomes used next frame
            // or is already being used...
            posA = data.position;
            }
        
        if (currentSecondFinger == -1) return;
        
        // handle two-finger (eg, pinch, rotate etc)...
        
        if ( currentMainFinger == data.pointerId ) posA = data.position;
        if ( currentSecondFinger == data.pointerId ) posB = data.position;
        
        figureDelta();
        pinchDelta =  distance - previousDistance;
        previousDistance = distance;
        
        _processPinch(); // handle it your way
        }
    
    private void figureDelta()
        {
        // when/if two touches, keep track of the distance between them
        distance = Vector2.Distance(posA, posB);
        }
    
    public void OnPointerUp (PointerEventData data)
        {
        if ( currentMainFinger == data.pointerId )
            {
            currentMainFinger = -1;
            }
        if ( currentSecondFinger == data.pointerId )
            {
            currentSecondFinger = -1;
            }
        }
    
    private float sensitivity = 0.3f;
    
    // in this example, the swipes/pinch affects these three calls:
    public Changer orbitLR;
    public Changer orbitUD;
    public Changer distanceZ;
    // initial values of those...
    private float LR = -20f;
    private float UD = 20f;
    private float distanceCam = 5f;
    
    private void _processSwipe()
        {
        // in this example, just left-right or up-down swipes
        
        LR = LR + sensitivity * screenTravel.x;
        UD = UD - sensitivity * screenTravel.y;
        
        LR = Mathf.Clamp(LR, -150f, 30f);
        UD = Mathf.Clamp(UD, 5f, 50f);
        
        orbitLR.RotationY = LR;
        orbitUD.RotationX = UD;
        }
    
    private void _processPinch()
        {
        // in this example, pinch to zoom
        
        distanceCam = distanceCam - pinchDelta * 0.0125f;
        distanceCam = Mathf.Clamp(distanceCam, 3f, 8f);
        distanceZ.DistanceZ = distanceCam;
        }
    
    }
(请注意,请不要回答无法使用的旧版“Touches”系统。这是有关正常的现代Unity开发的。)

最佳答案

我不想回答自己的问题,但是经过大量的研究和专家的投入,以下是解决问题的唯一方法。

让我们总结一下:
1.实际上,您必须添加一个守护程序。因此,对于Pinch,只需将“PinchInputModule.cs”放到使用者对象上就可以了。
您可能会认为“如果不添加守护程序,它将无法自动运行”。但是-实际上,使用Unity自己的,您必须添加一个守护程序“TouchInput”系列。 (有时他们会自动添加,有时会忘记并且您必须这样做。)
很简单,自动魔术的“追逐”很愚蠢,算了吧。您必须添加一个守护程序。
2.您确实必须从IPointerDownHandler / etc继承继承,因为很简单地,Unity搞砸了,您无法在StandAloneInputModule中正确继承。复制和粘贴未编程。
简而言之,由于Unity搞砸了,因此无法继承StandAloneInputModule的子类化路径,这不是一个好的工程。您只需在新的守护程序中使用IPointerDownHandler / etc。下面对此进行更多讨论。
下面,我给出“单点触摸”和“捏”的示例。这些已准备就绪。您可以为其他情况(例如四点触摸等)编写自己的代码。因此,使用捏住守护程序(实际上只是将其放到有问题的对象上),然后可以很轻松地处理捏住:

public void OnPinchZoom (float delta)
    {
    _processPinch(delta);
    }
很难看到它更容易。
这样做,直到Unity记住它的产品“用于手机”,然后他们添加通话请求等等。

制作一个多维数据集,在上面放置自己的脚本FingerMove
制作脚本,说移动摄像机LR,UD。 (或其他任何东西-只需Debug。记录更改。)
粘贴到此处理程序脚本中...
SingleFingerInputModule.cs
/*
ISingleFingerHandler - handles strict single-finger down-up-drag

Put this daemon ON TO the game object, with a consumer of the service.

(Note - there are many, many philosophical decisions to make when
implementing touch concepts; just some issues include what happens
when other fingers touch, can you "swap out" etc. Note that, for
example, Apple vs. Android have slightly different takes on this.
If you wanted to implement slightly different "philosophy" you'd
do that in this script.)
*/


public interface ISingleFingerHandler
    {
    void OnSingleFingerDown (Vector2 position);
    void OnSingleFingerUp (Vector2 position);
    void OnSingleFingerDrag (Vector2 delta);
    }

/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("dragging")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */


using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;

public class SingleFingerInputModule:MonoBehaviour,
                IPointerDownHandler,IPointerUpHandler,IDragHandler

    {
    private ISingleFingerHandler needsUs = null;
    // of course that would be a List,
    // just one shown for simplicity in this example code
    
    private int currentSingleFinger = -1;
    private int kountFingersDown = 0;
    
    void Awake()
        {
        needsUs = GetComponent(typeof(ISingleFingerHandler)) as ISingleFingerHandler;
        // of course, you may prefer this to search the whole scene,
        // just this gameobject shown here for simplicity
        // alternately it's a very good approach to have consumers register
        // for it. to do so just add a register function to the interface.
        }
    
    public void OnPointerDown(PointerEventData data)
        {
        kountFingersDown = kountFingersDown + 1;
        
        if (currentSingleFinger == -1 && kountFingersDown == 1)
            {
            currentSingleFinger = data.pointerId;
            if (needsUs != null) needsUs.OnSingleFingerDown(data.position);
            }
        }
    
    public void OnPointerUp (PointerEventData data)
        {
        kountFingersDown = kountFingersDown - 1;
        
        if ( currentSingleFinger == data.pointerId )
            {
            currentSingleFinger = -1;
            if (needsUs != null) needsUs.OnSingleFingerUp(data.position);
            }
        }
    
    public void OnDrag (PointerEventData data)
        {
        if ( currentSingleFinger == data.pointerId && kountFingersDown == 1 )
            {
            if (needsUs != null) needsUs.OnSingleFingerDrag(data.delta);
            }
        }
    
    }
将该守护进程与您的使用者FingerMove放到游戏对象上,然后将其忽略。就是现在
太容易了
处理拖动:
public class FingerMove:MonoBehaviour, ISingleFingerHandler
    {
    public void OnSingleFingerDown(Vector2 position) {}
    public void OnSingleFingerUp (Vector2 position) {}
    public void OnSingleFingerDrag (Vector2 delta)
        {
        _processSwipe(delta);
        }
    
    private void _processSwipe(Vector2 screenTravel)
        {
        .. move the camera or whatever ..
        }
    }
就像我说的,
简直容易!
现在,让我们考虑一下两指的情况,这是缩放/缩小的关键。
PinchInputModule.cs
/*
IPinchHandler - strict two sequential finger pinch Handling

Put this daemon ON TO the game object, with a consumer of the service.

(Note, as always, the "philosophy" of a glass gesture is up to you.
There are many, many subtle questions; eg should extra fingers block,
can you 'swap primary' etc etc etc - program it as you wish.)
*/


public interface IPinchHandler
    {
    void OnPinchStart ();
    void OnPinchEnd ();
    void OnPinchZoom (float gapDelta);
    }

/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("pinching")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */


using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class PinchInputModule:MonoBehaviour,
                IPointerDownHandler,IPointerUpHandler,IDragHandler

    {
    private IPinchHandler needsUs = null;
    // of course that would be a List,
    // just one shown for simplicity in this example code
    
    private int currentFirstFinger = -1;
    private int currentSecondFinger = -1;
    private int kountFingersDown = 0;
    private bool pinching = false;
    
    private Vector2 positionFirst = Vector2.zero;
    private Vector2 positionSecond = Vector2.zero;
    private float previousDistance = 0f;
    private float delta = 0f;
    
    void Awake()
        {
        needsUs = GetComponent(typeof(IPinchHandler)) as IPinchHandler;
        // of course, this could search the whole scene,
        // just this gameobject shown here for simplicity
        }
    
    public void OnPointerDown(PointerEventData data)
        {
        kountFingersDown = kountFingersDown + 1;
        
        if (currentFirstFinger == -1 && kountFingersDown == 1)
            {
            // first finger must be a pure first finger and that's that
            
            currentFirstFinger = data.pointerId;
            positionFirst = data.position;
            
            return;
            }
        
        if (currentFirstFinger != -1 && currentSecondFinger == -1 && kountFingersDown == 2)
            {
            // second finger must be a pure second finger and that's that
            
            currentSecondFinger = data.pointerId;
            positionSecond = data.position;
            
            FigureDelta();
            
            pinching = true;
            if (needsUs != null) needsUs.OnPinchStart();
            return;
            }
        
        }
    
    public void OnPointerUp (PointerEventData data)
        {
        kountFingersDown = kountFingersDown - 1;
        
        if ( currentFirstFinger == data.pointerId )
            {
            currentFirstFinger = -1;
            
            if (pinching)
                {
                pinching = false;
                if (needsUs != null) needsUs.OnPinchEnd();
                }
            }
        
        if ( currentSecondFinger == data.pointerId )
            {
            currentSecondFinger = -1;
            
            if (pinching)
                {
                pinching = false;
                if (needsUs != null) needsUs.OnPinchEnd();
                }
            }
        
        }
    
    public void OnDrag (PointerEventData data)
        {
        
        if ( currentFirstFinger == data.pointerId )
            {
            positionFirst = data.position;
            FigureDelta();
            }
        
        if ( currentSecondFinger == data.pointerId )
            {
            positionSecond = data.position;
            FigureDelta();
            }
        
        if (pinching)
            {
            if ( data.pointerId == currentFirstFinger || data.pointerId == currentSecondFinger )
                {
                if (kountFingersDown==2)
                    {
                    if (needsUs != null) needsUs.OnPinchZoom(delta);
                    }
                return;
                }
            }
        }
    
    private void FigureDelta()
        {
        float newDistance = Vector2.Distance(positionFirst, positionSecond);
        delta = newDistance - previousDistance;
        previousDistance = newDistance;
        }
    
    }
将该守护程序放到您拥有服务使用者的游戏对象上。注意,“混合和匹配”绝对没有问题。在此示例中,让我们同时进行拖动和捏捏手势。就是现在
简直太简单了
处理捏:
public class FingerMove:MonoBehaviour, ISingleFingerHandler, IPinchHandler
    {
    public void OnSingleFingerDown(Vector2 position) {}
    public void OnSingleFingerUp (Vector2 position) {}
    public void OnSingleFingerDrag (Vector2 delta)
        {
        _processSwipe(delta);
        }
    
    public void OnPinchStart () {}
    public void OnPinchEnd () {}
    public void OnPinchZoom (float delta)
        {
        _processPinch(delta);
        }
    
    private void _processSwipe(Vector2 screenTravel)
        {
        .. handle drag (perhaps move LR/UD)
        }
    
    private void _processPinch(float delta)
        {
        .. handle zooming (perhaps move camera in-and-out)
        }
    }
就像我说的
太容易了! :)
要了解这有多优雅,请考虑以下问题:捏时,您是希望它“暂停”拖动,还是让两者同时发生?令人惊讶的是,您只需在SingleFingerInputModule.cs中进行编程。在特定示例中,我希望它在用户缩放时“保持”拖动,因此/上方的SingleFingerInputModule.cs是用这种方式编写的。您可以轻松地对其进行修改,以使其不断拖动,更改为质心,取消拖动或任何您想要的操作。令人惊讶的是,FingerMove.cs根本不受影响!极其有用的抽象!
请注意,对于上面的Gökhan出色的四角示例,我将这样编写:
public class FingerStretch:MonoBehaviour, IFourCornerHandler
    {
    public void OnFourCornerChange (Vector2 a, b, c, d)
        {
        ... amazingly elegant solution
        ... Gökhan does all the work in FourCornerInputModule.cs
        ... here I just subscribe to it. amazingly simple
        }
这只是一个令人难以置信的简单方法。
那真是令人难以置信的简单:O
Gökhan将所有手指的逻辑封装在FourCornerInputModule.cs中,该接口(interface)将具有IFourCornerHandler接口(interface)。请注意,FourCornerInputModule将明智地做出所有哲学决定(例如,必须四根手指都放下,如果多放一根,该如何等等,等等)。
这是出现的问题:
1.我们应该进行“类似事件系统”的编程吗?
在所谓的“独立输入模块”中查看您的Unity项目,该模块是带有EventSystemStandAloneInputModule的游戏对象
实际上,您可以“从头开始”编写类似SingleFingerInputModule.cs或PinchInputModule.cs的,这样它就可以“像” Unity的StandAloneInputModule 一样工作。
尽管很难做到这一点,但请注意in this answer注释中的链接。
但是有一个特定于的组合式问题:可笑的是,您不能使用OO原则:在我上面的SingleFingerInputModule.cs代码中,我们非常明智地-当然-使用现有的惊人的IPointerDownHandler等功能,Unity已经完成了此功能我们(实质上)是“子类化”,添加了更多逻辑。您确实应该做的正是这件事。相反,如果您确实决定“制作类似于StandAloneInputModule的作品”,那将是一场惨败-您必须重新开始,可能会复制并粘贴Unity的源代码(用于IPointerDownHandler等),并以某种方式对其进行修改,当然是您永远不应该进行软件工程的确切示例。
2.但是您必须“记得添加守护程序”吗?
请注意,实际上,如果您采用“进行类似于StandAloneInputModule的操作”路线,则仍然必须这样做!!!这有点奇怪。优势为零。
3.您必须 call 所有订户?
如果您使用“制作类似于StandAloneInputModule之类的东西”路线,则Unity会执行一个“Execute ....”调用,即会执行此操作。它只不过是一个宏,仅用于“调用订阅者”(除琐碎琐事外,我们每天都会在每个脚本中执行此操作);没有优势。
实际上:我个人认为拥有一个订阅调用as Everts suggests here实际上要好得多,只需将其作为接口(interface)调用之一即可。我只是认为,这比尝试“模仿” Unity怪异的魔术调用系统(无论如何实际上都无法工作-您必须“记住”附加一个StandAloneInputModule)要好得多。
综上所述,
我已经相信
(1)建立在IPointerDownHandler,IDragHandler,IPointerUpHandler上,实际上绝对是正确的方法
开始重新编写代码以“制作类似于StandAloneInputModule的东西”无疑是一个坏主意。
(2)添加守护程序没有任何问题
如果您尝试“制作类似于StandAloneInputModule的作品” ...出于善意,无论如何您都必须“记住添加”。
(3)寻找消费者,或者更好地拥有订阅电话没有任何问题
如果您尝试“制作类似于StandAloneInputModule的东西”,Unity会为您提供“Execute ...”调用的几乎不存在的优势,这是一行代码,而您的代码行(更短,更清晰,更快)代码以“ call 订户”。同样,仅have a subscribe call更加明显和清晰,您可以说任何非Unity程序员都可以这样做。
因此,对我而言,当今Unity最好的方法就是编写模块/接口(interface),例如SingleFingerInputModule.cs,PinchInputModule.cs,FourCornerInputModule.cs,将其放到您希望拥有这些消费者的游戏对象上,完成。 “就是这么简单。”
public class Zoom:MonoBehaviour, ISingleFingerHandler, IPinchHandler
    {
    public void OnPinchZoom (float delta)
        {
        ...

关于c# - 在现代Unity3D中捏和其他多指手势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40341191/

相关文章:

c# - 无法将类型 `Steamworks.CSteamID' 隐式转换为 `float'

javascript - 将叠加层滑出屏幕

c# - Passwordbox Validation.ErrorTemplate 未显示

c# - 通用接口(interface)的非通用实现

c# - 绘制至少一个像素宽的对象

ios - 如何在iOS的Safari上禁用网页的默认滚动效果?

javascript - Chrome touchenter 触摸离开

c# - 将 project.json 包引用添加到 VSIX

c# - 检索域 url

c# - 重力和向心力