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

时间:2016-10-31 11:23:06

标签: 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;
        }

    }

(注意,请不要回答关于遗留下来的“触摸”系统的问题。这是关于正常的现代Unity开发。)

3 个答案:

答案 0 :(得分:2)

实现这一点并不复杂。

使用List并在每次有pointerId事件时存储OnPointerDown,然后增加touchCount变量。如果pointerId中已存在{On},请不要将List存储在OnPointerDown上。

调用OnPointerUp或发布时,请检查pointerId是否存在。如果是,则递减touchCount变量。如果<{1}}中存在,则不要减少任何内容。

1 非常简单的实施

List

2 第一个示例非常简单,但可以使用我们自己的界面进行改进。

此方法使用两个脚本:

public class FingerMove : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { public int touchCount; public List<int> touchID = new List<int>(6); //6 touches limit public void OnPointerDown(PointerEventData data) { Debug.Log("Pressed"); //Check If PointerId exist, if it doesn't add to list if (touchID.Contains(data.pointerId)) { return; //Exit if PointerId exist } //PointerId does not exist, add it to the list then increment touchCount touchID.Add(data.pointerId); touchCount++; } public void OnPointerUp(PointerEventData data) { Debug.Log("Released"); //Check If PointerId exist, if it exist remove it from list then decrement touchCount if (touchID.Contains(data.pointerId)) { touchID.Remove(data.pointerId); touchCount--; return; } } void Update() { Debug.Log("Touch Count: " + touchCount); } } interface:

IPointerCounterHandler.cs

public interface IPointerCounterHandler : IEventSystemHandler { void OnPointerCounterChanged(int touchCount); void OnPointerCounterChanged(PointerCounterEventData touchCountData); } 脚本。

PointerCounterEventData.cs

<强>用法

在您的脚本中实施public class PointerCounterEventData : BaseEventData { //The callback with int parameter public static readonly ExecuteEvents.EventFunction<IPointerCounterHandler> counterChangedV1Delegate = delegate (IPointerCounterHandler handler, BaseEventData data) { //var casted = ExecuteEvents.ValidateEventData<PointerCounterEventData>(data); handler.OnPointerCounterChanged(touchCount); }; //The callback with PointerCounterEventData parameter public static readonly ExecuteEvents.EventFunction<IPointerCounterHandler> counterChangedV2Delegate = delegate (IPointerCounterHandler handler, BaseEventData data) { var casted = ExecuteEvents.ValidateEventData<PointerCounterEventData>(data); handler.OnPointerCounterChanged(casted); }; public static int touchCount = 0; public PointerCounterInfo touchCountData = new PointerCounterInfo(); public static List<int> touchID = new List<int>(6); //6 touches limit //Constructor with the int parameter public PointerCounterEventData( EventSystem eventSystem, int tempTouchId, PointerState pointerStat ) : base(eventSystem) { //Process the Input event processTouches(pointerStat, tempTouchId, null, CallBackType.TouchCountOnly); } //Constructor with the PointerEventData parameter public PointerCounterEventData( EventSystem eventSystem, PointerEventData eventData, PointerState pointerStat, GameObject target ) : base(eventSystem) { //Process the Input event processTouches(pointerStat, eventData.pointerId, eventData, CallBackType.CounterData); //Create new PointerCounterInfo for the OnPointerCounterChanged(PointerCounterEventData eventData) function PointerCounterInfo pcInfo = createPointerInfo(eventData, target, pointerStat); //Update touchCountData touchCountData = pcInfo; } void processTouches(PointerState pointerStat, int tempTouchId, PointerEventData touchCountData, CallBackType cbType) { if (pointerStat == PointerState.DOWN) { //Check If PointerId exist, if it doesn't add to list if (touchID.Contains(tempTouchId)) { //eventData.eventData return; //Exit if PointerId exist } //PointerId does not exist, add it to the list then increment touchCount touchID.Add(tempTouchId); touchCount++; } if (pointerStat == PointerState.UP) { //Check If PointerId exist, if it exist remove it from list then decrement touchCount if (touchID.Contains(tempTouchId)) { touchID.Remove(tempTouchId); touchCount--; return; } } } public static void notifyPointerDown(EventSystem eventSystem, PointerEventData eventData, GameObject target) { PointerState pointerStat = PointerState.DOWN; notifyfuncs(eventSystem, eventData, target, pointerStat); } public static void notifyPointerUp(EventSystem eventSystem, PointerEventData eventData, GameObject target) { PointerState pointerStat = PointerState.UP; notifyfuncs(eventSystem, eventData, target, pointerStat); } private static void notifyfuncs(EventSystem eventSystem, PointerEventData eventData, GameObject target, PointerState pointerStat) { //////////////////////Call the int parameter////////////////////// PointerCounterEventData eventParam1 = new PointerCounterEventData( eventSystem, eventData.pointerId, pointerStat); ExecuteEvents.Execute<IPointerCounterHandler>( target, eventParam1, PointerCounterEventData.counterChangedV1Delegate); //////////////////////Call the PointerCounterEventData parameter////////////////////// PointerCounterEventData eventParam2 = new PointerCounterEventData( eventSystem, eventData, pointerStat, target); ExecuteEvents.Execute<IPointerCounterHandler>( target, eventParam2, PointerCounterEventData.counterChangedV2Delegate); } //Creates PointerCounterInfo for the OnPointerCounterChanged(PointerCounterEventData eventData) function private static PointerCounterInfo createPointerInfo(PointerEventData eventData, GameObject target, PointerState pointerStat) { PointerCounterInfo pointerCounterInfo = new PointerCounterInfo(); pointerCounterInfo.pointerId = eventData.pointerId; pointerCounterInfo.touchCount = touchCount; pointerCounterInfo.eventData = eventData; pointerCounterInfo.pointerState = pointerStat; pointerCounterInfo.target = target; return pointerCounterInfo; } public enum CallBackType { TouchCountOnly, CounterData } } public enum PointerState { NONE, DOWN, UP } public class PointerCounterInfo { public int pointerId = 0; public int touchCount = 0; public PointerEventData eventData; public PointerState pointerState; public GameObject target; } ,然后覆盖

IPointerCounterHandler

void OnPointerCounterChanged(int touchCount);函数。

最后,在void OnPointerCounterChanged(PointerCounterEventData touchCountData);函数中调用PointerCounterEventData.notifyPointerDown,并在OnPointerDown函数中调用PointerCounterEventData.notifyPointerUp

测试

OnPointerUp

答案 1 :(得分:2)

您没有预定义的方法在Unity中执行此操作。您所能做的就是以面向对象的方式再次使用自定义解决方案。最好的办法是拆分事件检测和事件处理。

要问的主要问题是,如何以OOP方式表示手指,触摸,手势等。我选择这样做:

  • 每当发生指针向下事件时,都会创建一个新手指。
  • 创建新手指时,还会创建与现有手指的所有可能子集的组合。这意味着,当添加第三个手指时,如果您将手指标记为f1, f2, f3,则创建的手指组合为:f3, f1f3, f2f3, f1f2f3。当使用多个手指时,这提供了极大的灵活性。你可以做手势like this。例如,如果您想要执行锚定手势,则只需要f2f3的手势,但f1也必须存在。在这种情况下,您可以忽略f1
  • 当手指移动时,会创建一个新手势,并根据手指使用新创建的手势触发所有组合的更改事件。

此外,您通常需要多点触控事件:

  • 手指的平均位置
  • 手指的集体旋转
  • 手指所代表的形状的集体大小,用于缩放和填充。它可以是两个手指之间的矢量幅度,也可以是多边形的面积
  • 所有顶点的位置,如果你想做高级的东西
  • 上述所有内容的变更(增量)

未来的长代码:

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

public class MultitouchHandler : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler {
    public List<Finger> Fingers = new List<Finger>();
    public List<FingerCombination> FingerCombinations = new List<FingerCombination>();

    public FingerCombination GetFingerCombination(params int[] fingerIndices) {
        var fc = FingerCombinations.Find(x => x.IDs.Count == fingerIndices.Length && fingerIndices.All(y => x.IDs.Contains(Fingers[y].ID)));
        if (fc != null) return fc;

        fc = new FingerCombination() {
            Fingers = fingerIndices.Select(x => Fingers[x]).ToList()
        };
        fc.IDs = fc.Fingers.Select(x => x.ID).ToList();
        fc.Data = Fingers.Select(x => x.Data).ToList();
        fc.PreviousData = Fingers.Select(x => x.Data).ToList();
        FingerCombinations.Add(fc);
        return fc;
    }

    public delegate void MultitouchEventHandler(int touchCount, MultitouchHandler sender);
    public event MultitouchEventHandler OnFingerAdded;
    public event MultitouchEventHandler OnFingerRemoved;


    public void OnDrag(PointerEventData eventData) {
        var finger = Fingers.Find(x => x.ID == eventData.pointerId);
        var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));

        finger.PreviousData = finger.Data;
        finger.Data = eventData;

        foreach (var fc in fcs) {
            fc.PreviousData = fc.Data;
            fc.Data = fc.Fingers.Select(x => x.Data).ToList();
            fc.PreviousGesture = fc.Gesture;
            fc.Gesture = new Gesture() {
                Center = fc.Center,
                Size = fc.Size,
                Angle = fc.Angle,
                SizeDelta = 1
            };
            if (fc.PreviousGesture != null) {
                fc.Gesture.CenterDelta = fc.Center - fc.PreviousGesture.Center;
                fc.Gesture.SizeDelta = fc.Size / fc.PreviousGesture.Size;
                fc.Gesture.AngleDelta = fc.Angle - fc.PreviousGesture.Angle;
            }

            fc.Changed();
        }
    }

    public void OnPointerDown(PointerEventData eventData) {
        var finger = new Finger() { ID = eventData.pointerId, Data = eventData };
        Fingers.Add(finger);

        if (OnFingerAdded != null)
            OnFingerAdded(Fingers.Count, this);

    }

    public void OnPointerUp(PointerEventData eventData) {
        Fingers.RemoveAll(x => x.ID == eventData.pointerId);

        if (OnFingerRemoved != null)
            OnFingerRemoved(Fingers.Count, this);

        var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));
        foreach (var fc in fcs) {
            fc.Finished();
        }

        FingerCombinations.RemoveAll(x => x.IDs.Contains(eventData.pointerId));
    }

    public class Finger {
        public int ID;
        public PointerEventData Data;
        public PointerEventData PreviousData;
    }

    public class FingerCombination {
        public List<int> IDs = new List<int>();
        public List<Finger> Fingers;
        public List<PointerEventData> PreviousData;
        public List<PointerEventData> Data;

        public delegate void GestureEventHandler(Gesture gesture, FingerCombination sender);
        public event GestureEventHandler OnChange;
        public delegate void GestureEndHandler(FingerCombination sender);
        public event GestureEndHandler OnFinish;

        public Gesture Gesture;
        public Gesture PreviousGesture;

        public Vector2 Center
        {
            get { return Data.Aggregate(Vector2.zero, (x, y) => x + y.position) / Data.Count; }
        }

        public float Size
        {
            get
            {
                if (Data.Count == 1) return 0;
                var magnitudeSum = 0f;
                for (int i = 1; i < Data.Count; i++) {
                    var dif = (Data[i].position - Data[0].position);
                    magnitudeSum += dif.magnitude;
                }
                return magnitudeSum / (Data.Count - 1);
            }
        }

        public float Angle
        {
            get
            {
                if (Data.Count == 1) return 0;
                var angleSum = 0f;
                for (int i = 1; i < Data.Count; i++) {
                    var dif = (Data[i].position - Data[0].position);
                    angleSum += Mathf.Atan2(dif.y, dif.x) * Mathf.Rad2Deg;
                }
                return angleSum / (Data.Count - 1);
            }
        }

        internal void Changed() {
            if (OnChange != null)
                OnChange.Invoke(Gesture, this);
        }

        internal void Finished() {
            if (OnFinish != null)
                OnFinish.Invoke(this);
        }
    }

    public class Gesture {
        public Vector2 Center;
        public float Size;
        public float Angle;

        public Vector2 CenterDelta;
        public float SizeDelta;
        public float AngleDelta;
    }
}

以下是一个示例,说明如何使用4个手指。

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

public class MultiTouchTest : MonoBehaviour {
    public Vector2 rectSize = Vector2.one * 2;
    public Vector2 skewedRectSize = Vector2.one;
    public Vector2 rectPos = Vector2.zero;
    public List<Vector3> Fingers = new List<Vector3>();

    void Start() {
        var h = GetComponent<MultitouchHandler>();
        h.OnFingerAdded += OnGestureStart;
    }

    private void OnGestureStart(int touchCount, MultitouchHandler sender) {
        if (touchCount != 4) return;
        var fc = sender.GetFingerCombination(0, 1, 2, 3);
        fc.OnChange += OnGesture;
    }

    private void OnGesture(MultitouchHandler.Gesture gesture, MultitouchHandler.FingerCombination sender) {
        rectSize *= gesture.SizeDelta;
        Fingers = sender.Fingers.Select(x => Camera.main.ScreenToWorldPoint(x.Data.position)).ToList();
        var tan = Mathf.Tan(gesture.Angle * Mathf.Deg2Rad);
        skewedRectSize = new Vector2(rectSize.x / tan, rectSize.y * tan);
        rectPos += gesture.CenterDelta / 50;
    }

    public void OnDrawGizmos() {
        Gizmos.color = Color.red;
        Gizmos.DrawCube(rectPos, skewedRectSize);
        Gizmos.color = Color.blue;
        foreach (var finger in Fingers) Gizmos.DrawSphere(finger + Vector3.forward, 0.5f);
    }
}

结果如下:

Result

这只是一个简单的例子。对于SO的格式,一个好的答案太长了。

答案 2 :(得分:1)

统一输入系统还不完善。您现在必须使用低级别系统自行跟踪触摸。 See an example here