2016-10-31 38 views
10

We współczesnym Unity3D używamy rodziny połączeń IPointerDownHandler.Szczypta i inne gesty wieloprzyciskowe w nowoczesnym Unity3D?

Odnośnie rodziny połączeń IPointerDownHandler,

public class FingerMove:MonoBehaviour, IPointerDownHandler... 
    { 
    public void OnPointerDown (PointerEventData data) 
     { 

Oczywiście są fantastyczne

do czynienia z pojedynczymi elementami.

Ale jak radzić sobie z wielokrotnymi dotknięciami w poważny sposób?

Możesz "robić to wszystko ręcznie" śledząc samemu dotyk, ale wydaje się, że Unity chciałby, żebyś zrobił to z czegoś tak absolutnie podstawowego. (Mam na myśli - to jest silnik gry, oczywiście, mógłbym napisać wszystkie moje własne renderowanie i fizykę!)

Oto przykład zasadniczo "programowania kowbojskiego", po prostu robi się to ręcznie, bez inżynierii oprogramowania. Jakie jest prawdziwe rozwiązanie?

// 
// 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; 
     } 

    } 

(Uwaga, nie należy odpowiedzieć w odniesieniu do spuścizny „dotyka” system, który jest bezużyteczny. To o normalnym rozwoju nowoczesnego Unity).

+0

Pracowałem na coś takiego kilka miesięcy temu. Spadnie odpowiedź kiedy znajdę go. BTW, brakuje znacznika C#. – Programmer

+2

Wiesz co jest bardziej śmieszne niż zdarzeń dotykowych Unity? Zdarzenia dotykowe Javascript w. Ale znając ich obu, rozumiem, że nie ma możliwości, aby jakakolwiek implementacja mogła zaspokoić dewelopera.Możesz łatwo napisać kod, który zadziałałby w większości typowych przypadków, takich jak tylko dwa dotknięcia, ale byłoby tak wiele przypadków, gdyby nie " t cover –

Odpowiedz

5

Nie lubię odpowiadać na własne pytania, ale po wielu badaniach i wprowadzeniu ekspertów, poniżej jest jedyny sposób, aby to zrobić.


Podsumujmy:

1. Ty w rzeczywistości trzeba dodać demona. Tak więc, dla Pinch, po prostu upuść "PinchInputModule.cs" na obiekcie konsumenta - i gotowe.

Możesz pomyśleć: "to jest do bani, że nie działa automagicznie bez dodawania demona". ALE - w rzeczywistości przy użyciu Unity trzeba dodać demona, rodzinę "TouchInput". (Które czasem dodają automatycznie, czasami zapominają i musisz to zrobić.)

Po prostu "pogoń" dla automagic jest głupia, zapomnij o tym. Musisz dodać demona.

2. Musisz odziedziczyć bok z IPointerDownHandler/etc, ponieważ, po prostu, Unity zawiedli i nie można dziedziczyć poprawnie w StandAloneInputModule. Kopiowanie i wklejanie nie jest programowaniem.

Po prostu nie jest to dobra technika, aby podążać ścieżką podklasy StandAloneInputModule, ponieważ Unity zawiodło. Po prostu użyj IPointerDownHandler/etc w swoich nowych demonom. Więcej dyskusji na ten temat poniżej.

Poniżej podaję przykłady "pojedynczego dotknięcia" i "uszczypnięcia". Są gotowe do produkcji. Możesz napisać własną rękę do innych sytuacjach, takich jak cztery-touch itp So, z pinch-demona (dosłownie upuść go na danego przedmiotu), to wtedy szalenie łatwe w obsłudze szczypty:

public void OnPinchZoom (float delta) 
    { 
    _processPinch(delta); 
    } 

Trudne aby było łatwiej.

Więc to zrobić, aż Unity pamięta to produkt „używane w telefonach” i dodać szczyptę dla połączeń itp


Dodać kostkę, umieścić swój własny scenariusz na nim FingerMove.

Utwórz skrypt, powiedz: przenieś kamerę LR, UD. (Lub cokolwiek - tylko debug.log zmian.)

Wklej w tym skrypcie handler ...

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); 
      } 
     } 

    } 

umieścić, że demon na obiekt gry, z konsumentem FingerMove i zapomnij o tym.Jest teraz

śmiesznie łatwe

obsłużyć przeciąganie:

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 .. 
     } 
    } 

jak powiedziałem,

śmiesznie proste!

Teraz zastanówmy się nad przypadkiem z dwoma palcami, uszczypnięciem w celu przybliżenia/oddalenia.

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; 
     } 

    } 

umieścić, że demon się do obiektu gra, w której masz konsumenta usługi. Zauważ, że nie ma absolutnie żadnego problemu z "mieszaniem i dopasowaniem". W tym przykładzie zróbmy OBU gest przeciągania i szczypania. Jest teraz

prostu głupio proste

obsłużyć pinch:

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) 
     } 
    } 

Jak mówię,

głupio proste! :)

Aby zobaczyć, jak elegancko to jest, rozważ takie kwestie jak: czy przy szczypcaniu chcesz, aby to "wstrzymało" przeciąganie, czy pozwoliło, aby to się stało? Zadziwiające jest to, że po prostu programujesz to wewnątrz SingleFingerInputModule.cs. W tym konkretnym przykładzie chciałem, aby "trzymał" przeciąganie, podczas gdy/jeśli użytkownik przybliża, tak pojedynczy program SingleFingerInputModule.cs jest tak zaprogramowany. Możesz go łatwo modyfikować, aby kontynuować przeciąganie, zmieniać na centroidy, anulować przeciąganie lub cokolwiek chcesz. Zadziwiające jest to, że FingerMove.cs w ogóle nie ma wpływu! Niezwykle przydatna abstrakcja!

Należy pamiętać, że za doskonały przykład cztery narożną GÖKHAN za powyższym, chciałbym napisać to tak:

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 
     } 

Która jest tylko umysł bogglingly proste podejście.

To umysł bogglingly prosta: O

Gökhan by upakować całą logikę palców wewnątrz FourCornerInputModule.cs które mają IFourCornerHandler interfejsu. Zauważ, że FourCornerInputModule rozsądnie podejmowałby wszystkie filozoficzne decyzje (przykład, musisz mieć wszystkie cztery palce w dół, co jeśli masz jeden dodatkowy, etcetera itp.).

Oto kwestie wynikające:

1. powinniśmy zrobić "Event-System jak" programowanie?

Spójrz na swój projekt jedności w tak zwanym „Stand Alone modułu wejść”, który jest przedmiotem gra z EventSystem i StandAloneInputModule

Można w rzeczywistości „Write od zera” lub coś podobnego SingleFingerInputModule.cs PinchInputModule.cs, , aby "działał jak" Unity StandAloneInputModule.

Mimo że można to zrobić, zwróć uwagę na linki w komentarzach in this answer.

Ale jest problem specyficzny, knock-down: śmiesznie, nie można korzystać z zasad OO w moim kodu powyżej SingleFingerInputModule.cs, mamy bardzo rozsądnie - oczywiście - wykorzystanie istniejącego niesamowite IPointerDownHandler itp moc, która Jedność już się dokonała, a my (w zasadzie) "podklasujemy", dodając trochę więcej logiki. Dokładnie to, co powinieneś zrobić, naprawdę musisz. W przeciwieństwie do tego: jeśli zdecydujesz się "stworzyć coś, co działa jak StandAloneInputModule", to jest to fiasko - musisz zacząć od nowa, prawdopodobnie kopiować i wklejać kod źródłowy Unity (dla IPointerDownHandler itp.) I trochę go modyfikować nieco w swój sposób, co jest oczywiście dokładnym przykładem tego, jak nigdy nie należy wykonywać inżynierii oprogramowania.

2. Ale musisz "pamiętać, aby dodać demona"?

Zauważ, że jeśli przejdziesz do opcji "stwórz coś, co działa jak StandAloneInputModule", w rzeczywistości musisz to zrobić !!!!! Co jest dość dziwne; nie ma żadnej korzyści.

3. Musisz zadzwonić do wszystkich subskrybentów?

Jeśli wybierzesz opcję "stwórz coś, co działa jak StandAloneInputModule", Unity ma wywołanie "Execute ....", które ... właśnie to robi. To niewiele więcej niż makro dla "wywoływania subskrybentów" (co wszyscy robimy codziennie w każdym skrypcie innym niż najbardziej banalny); bez przewagi.

W rzeczywistości: Osobiście uważam, że w rzeczywistości jest o wiele lepiej, aby mieć połączenie subskrypcji, as Everts suggests here, po prostu mieć to jako jedno z połączeń interfejsu. Po prostu uważam, że jest to o wiele lepsza technika, niż próba "bycia" jak "whacky magiczny system Unity Unity (który tak naprawdę w ogóle nie działa - musisz" pamiętać o dołączeniu "StandAloneInputModule).

Podsumowując,

doszedłem do przekonania, że ​​

(1) budynek na IPointerDownHandler, IDragHandler, IPointerUpHandler jest w rzeczywistości, na pewno właściwe podejście

To bezspornie złym pomysłem zacznij ponownie pisać kod, aby "stworzyć coś, co działa jak StandAloneInputModule"

(2) nie ma nic złego w dodawaniu demona

Jeśli spróbujesz "zrobić coś, co działa jak StandAloneInputModule" ... musisz "pamiętać, aby go dodać" tak czy inaczej, na miłość boską.

(3) nie ma w ogóle nic złego ze znalezieniem odbiorców, albo lepiej, mający subscribe zadzwonić

Jeśli spróbujesz „zrobić coś, co działa jak StandAloneInputModule” jest prawie nieistniejąca zaletą „Wykonaj ... "zadzwoń do Unity, która jest linią kodu w stosunku do twojej (krótsza, jaśniejsza, szybsza) linia kodu, aby" zadzwonić do subskrybenta ". Znowu jest o wiele bardziej oczywiste i oczywiste, że po prostu każdy programista spoza Unity po prostu to zrobi.

Tak więc dla mnie najlepszym podejściem w Unity jest dziś napisanie modułów/interfejsów, takich jak SingleFingerInputModule.cs, PinchInputModule.cs, FourCornerInputModule.cs, upuść go na obiekcie gry, gdzie chcesz mieć ich konsumenta - i jesteś skończony. "To takie proste."

public class Zoom:MonoBehaviour, ISingleFingerHandler, IPinchHandler 
    { 
    public void OnPinchZoom (float delta) 
     { 
     ... 
1

Układ wejściowy jedność nie jest jeszcze doskonały. Trzeba samemu śledzić dotyk przy użyciu systemu niskiego poziomu. See an example here.

2

Wdrażanie tego nie jest skomplikowane.

Użyj List i zapisz pointerId za każdym razem, gdy wystąpi zdarzenie OnPointerDown, a następnie zwiększ wartość o touchCount. Nie przechowuj pointerId na OnPointerDown, jeśli już istnieje w List.

Po wywołaniu OnPointerUp lub po zwolnieniu sprawdź, czy istnieje pointerId. Jeśli tak, zmniejsz wartość zmiennej touchCount. Jeśli nie istnieje nie istnieje istnieje w List, a następnie niczego nie zmniejszać.

. Bardzo prosta implementacja:

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); 
    } 
} 

. Pierwszy przykład jest bardzo prosty, ale można go ulepszyć za pomocą naszego własnego interfejsu.

Metoda ta wykorzystuje dwa scenariusze:

IPointerCounterHandler.cs interfejsu:

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

PointerCounterEventData.cs skrypt.

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; 
} 

Wykorzystanie:

Wdrożenie IPointerCounterHandler w skrypcie następnie zastąpić

void OnPointerCounterChanged(int touchCount); i

void OnPointerCounterChanged(PointerCounterEventData touchCountData); funkcje.

Wreszcie, zadzwoń PointerCounterEventData.notifyPointerDown w funkcji OnPointerDown i również zadzwonić PointerCounterEventData.notifyPointerUp w funkcji OnPointerUp.

test:

public class Test : MonoBehaviour, IPointerCounterHandler, IPointerDownHandler, IPointerUpHandler 
{ 
    public void OnPointerCounterChanged(int touchCount) 
    { 
     Debug.Log("Simple Finger Counter: " + touchCount); 
    } 

    public void OnPointerCounterChanged(PointerCounterEventData touchCountData) 
    { 
     PointerCounterInfo moreEventData = touchCountData.touchCountData; 

     Debug.Log("Finger TouchCount: " + moreEventData.touchCount); 
     Debug.Log("Finger PointerId: " + moreEventData.pointerId); 
     Debug.Log("Finger Pointer State: " + moreEventData.pointerState); 
     Debug.Log("Finger Target: " + moreEventData.target.name); 

     //Can also access PointerEventData 
     PointerEventData eventData = touchCountData.touchCountData.eventData; 
     Debug.Log("Click Time!: " + eventData.clickTime); 
    } 

    public void OnPointerDown(PointerEventData eventData) 
    { 
     PointerCounterEventData.notifyPointerDown(EventSystem.current, eventData, this.gameObject); 
    } 

    public void OnPointerUp(PointerEventData eventData) 
    { 
     PointerCounterEventData.notifyPointerUp(EventSystem.current, eventData, this.gameObject); 
    } 
} 
+0

Bardzo fajna droga! – Fattie

+0

Dzięki. Używasz 'OnPointerCounterChanged (int touchCount)', gdy chcesz uzyskać 'touchCount' i' OnPointerCounterChanged (PointerCounterEventData touchCountData) ', gdy chcesz uzyskać więcej informacji o tym dotyku. – Programmer

1

Nie masz zdefiniowanego sposobu, aby to zrobić w Jedności. Jedyne, co możesz zrobić, to ponownie zastosować niestandardowe rozwiązanie w podejściu zorientowanym obiektowo. Najlepiej byłoby podzielić wykrywanie zdarzeń i obsługę zdarzeń.

Głównym pytaniem, które należy zadać, jest sposób reprezentowania palców, dotyku, gestu itp. W sposób OOP. Wybieram to w następujący sposób:

  • Zawsze, gdy występuje zdarzenie zmniejszania wskaźnika, tworzony jest nowy palec.
  • Po utworzeniu nowego palca zostanie również utworzona kombinacja z całym możliwym podzbiorem istniejących palców. Oznacza to, że po dodaniu trzeciego palca, jeśli oznaczymy palcami jako f1, f2, f3, utworzone kombinacje palców to: f3, f1f3, f2f3, f1f2f3. Daje to maksymalną elastyczność podczas pracy z wieloma palcami. Możesz wykonywać gesty like this. Na przykład, jeśli chcesz wykonać gest kotwicy, potrzebujesz tylko gestów f2f3, ale musi też istnieć f1. W takim przypadku możesz po prostu zignorować f1.
  • Po przesunięciu palca zostaje utworzony nowy gest i następuje zmiana zdarzenia wszystkich kombinacji w zależności od tego, który palec zostanie wywołany nowym utworzonym gestem.

także to, czego zazwyczaj potrzeba ze zdarzenia multi-touch:

  • Mean położenia palców
  • Collective obrót palców
  • Collective wielkość kształtu przedstawionego przez palce, do skalowania i rzeczy. To może być wielkość wektora pomiędzy 2 palców, lub obszar wielokąta
  • położenie wszystkich wierzchołków, jeśli chcesz robić zaawansowane rzeczy
  • zmiana (delta) wszystkich powyżej

Długi kod z wyprzedzeniem:

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; 
    } 
} 

Oto przykład pokazujący sposób użycia 4 palcami.

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); 
    } 
} 

A wynik wygląda następująco:

Result

Jest to tylko prosty przykład, choć. Dobra odpowiedź byłaby zbyt długa dla formatu SO.

+0

Podejrzewam, że najważniejsza jest: "Nie masz predefiniowanego sposobu zrobienia tego w Jedności" - w rzeczy samej. – Fattie

+0

Hej mężczyźni, obie te odpowiedzi pokazują świetne rozwiązania pakietowe i musiały zająć dużo czasu. Więc system nie wyrzucił nagrody, kliknąłem nagrodę dla Gökhana. (Głównie dlatego, że kliknąłem drugą nagrodę na Prog !! :)) Macie teraz na razie ostateczne rozwiązania w Internecie dla multifingerów w nowoczesnym Unity ... – Fattie

+0

@JoeBlow Tak, to jest port Unity jakiegoś kodu, który napisałem dla Javascript jakiś czas temu. Jest na to mało przykładów, myślę, że być może nikt nigdy nie potrzebuje 3+ gestów palcami. –