zwracanie poprawnego identyfikatora multiTouch

9

Spędziłem niezliczone godziny na czytaniu samouczków i analizowaniu każdego pytania związanego z multiTouch odtąd i Stackoverflow. Ale po prostu nie mogę wymyślić, jak to zrobić poprawnie. Używam pętli, aby uzyskać pointerId, nie widzę wielu ludzi, którzy to robią, ale jest to jedyny sposób, w jaki udało mi się sprawić, by trochę działała.

Na ekranie mam dwa joysticki, jeden do poruszania się, drugi do kontrolowania rotacji duszków i kąta, który strzela, jak w Monster Shooter. Oba działają dobrze.

Mój problem polega na tym, że kiedy poruszam duszka w tym samym czasie, co strzelanie, mój touchingPointruch jest ustawiony na touchingPointmój strzelanie, ponieważ xi yjest wyżej po touchingPointmoim strzelaniu ( moving-stickpo lewej stronie ekranu, shooting-stickpo prawej stronie) , mój duszek przyspiesza, to powoduje niechcianą zmianę prędkości dla mojego duszka.

tak to rozwiązałem z twoją pomocą! dotyczy to każdego, kto może napotkać podobny problem:

    public void update(MotionEvent event) {
    if (event == null && lastEvent == null) {
        return;
    } else if (event == null && lastEvent != null) {
        event = lastEvent;
    } else {
        lastEvent = event;
    }   

        int action = event.getAction();
        int actionCode = action & MotionEvent.ACTION_MASK;
        int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        int x = (int) event.getX(pid);
        int y = (int) event.getY(pid); 
        int index = event.getActionIndex();
        int id = event.getPointerId(index);
        String actionString = null;


        switch (actionCode)
        {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:

                actionString = "DOWN";
                try{
                    if(x > 0 && x < steeringxMesh + (joystick.get_joystickBg().getWidth() * 2)
                            && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                            movingPoint.x = x;
                            movingPoint.y = y;
                            dragging = true;
                            draggingId = id;

                        }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                            && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            shooting=true;
                            shootingId=id;
                        }
                    }catch(Exception e){

                    }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_OUTSIDE:            
                if(id == draggingId)
                    dragging = false;
                if(id ==  shootingId)
                    shooting = false;
                actionString = "UP";
                break;  
            case MotionEvent.ACTION_MOVE:           
                for(index=0; index<event.getPointerCount(); index++) {
                    id=event.getPointerId(index);
                    int xx = (int) event.getX(index); //pro naming of variable
                    int yy = (int) event.getY(index); 
                    if(dragging && id == draggingId) {
                        if(xx > 0 && xx < (steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                            && yy > yMesh - (joystick.get_joystickBg().getHeight()) && yy < panel.getHeight()) {
                            movingPoint.x = xx;
                            movingPoint.y = yy;
                        }
                        else
                            dragging = false;
                        }
                    if(shooting && id == shootingId){
                        if(xx > shootingxMesh - (joystick.get_joystickBg().getWidth()) && xx < panel.getWidth()
                            && yy > yMesh - (joystick.get_joystickBg().getHeight()) && yy < panel.getHeight()) {
                            shootingPoint.x = xx;
                            shootingPoint.y = yy;                            
                        }
                        else
                            shooting = false;
                        }
                    }

                    actionString = "MOVE";
                    break;

        }
    Log.d(TAG, "actionsString: " + actionString + ", pid: " + pid + ", x: " + x + ", y: " + y);

Nie opublikowałbym tak dużo kodu, gdybym nie miał absolutnej straty z tego, co robię źle. Po prostu nie mogę dobrze zrozumieć, jak działa funkcja MultiTouching.

zasadniczo movingPointzmienia się zarówno dla mojego pierwszego, jak i drugiego palca. Wiążę go do pudełka, ale dopóki trzymam w nim jeden palec, zmienia swoją wartość w zależności od tego, gdzie dotyka mój drugi palec. Porusza się we właściwym kierunku i nic nie daje błędu, problemem jest zmiana prędkości, to prawie tak, jakby sumuje dwa dotykające punkty.

Max
źródło

Odpowiedzi:

4

Myślę, że to zadziała dla ciebie.

Jednym z popełnionych błędów było powtarzanie wszystkich wskaźników dla każdego wydarzenia. Jest to konieczne tylko w przypadku wydarzeń związanych z przenoszeniem.

Po drugie, faktycznie musisz wstawić wartość indeksu do funkcji getX i getY, ale identyfikator powiązany z tym indeksem jest używany jako odniesienie do twoich obiektów gry. Przypisujesz identyfikator joystickowi podczas zdarzenia Down, a następnie podczas iteracji indeksów wskaźnika, sprawdź, czy indeks jest powiązany z identyfikatorem wskaźnika przypisanym do joysticka podczas zdarzenia Down. Jeśli tak, sprawdź, czy nadal jest w granicach, i zaktualizuj go lub wyłącz.

Nie testuję tego kodu, ale wiem, że działa on koncepcyjnie, ponieważ używam tej metody we własnym kodzie. Daj mi znać, jeśli występują jakieś problemy, których nie możesz rozwiązać.

Najpierw musisz dodać następujące elementy do swojej klasy joysticka.

boolean dragging=false;
int draggingId;
boolean shooting=false;
int shootingId;

Zmień swój onTouchEvent na to.

public boolean onTouchEvent(MotionEvent event) {


    int index = event.getActionIndex();
    int id = event.getPointerId(index);
    String actionString;

    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            try{
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.dragging) {
                            movingPoint.x = x;
                            movingPoint.y = y;
                            joystick.dragging = true;
                            joystick.draggingId = id;
                    }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.shooting) { 
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            joystick.shooting=true;
                            joystick.shootingId=id;
                    }
              }
              catch(Exception e){

              }

            actionString = "DOWN";
            break;
        case MotionEvent.ACTION_UP:
            if(id == draggingID)
                joystick.dragging = false;
            if(id ==  shootingID)
                joystick.shooting = false;
            actionString = "UP";
            break;  
        case MotionEvent.ACTION_POINTER_DOWN:
            try{
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.dragging) {
                            movingPoint.x = x;
                            movingPoint.y = y;
                            joystick.dragging = true;
                            joystick.draggingId = id;
                    }
                    else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()
                        && !joystick.shooting) { 
                            shootingPoint.x = x;
                            shootingPoint.y = y;
                            joystick.shooting=true;
                            joystick.shootingId=id;
                    }
              }
              catch(Exception e){

              }

            actionString = "PNTR DOWN";
            break;
        case MotionEvent.ACTION_POINTER_UP:
            if(id == joystick.draggingID)
                joystick.dragging = false;
            if(id ==  joystick.shootingID)
                joystick.shooting = false;
            actionString = "PNTR UP";
            break;
        case MotionEvent.ACTION_CANCEL:
            if(id == joystick.draggingID)
                joystick.dragging = false;
            if(id ==  joystick.shootingID)
                joystick.shooting = false;
            actionString = "CANCEL";
            break;
        case MotionEvent.ACTION_MOVE:
            for(index=0; index<e.getPointerCount(); index++) {
                id=e.getPointerId(index);
                int x = (int) event.getX(index);
                int y = (int) event.getY(index); 
                if(joystick.dragging && id == joystick.draggingId) {
                    if(x > 0 && x < steeringxMesh + joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()) {
                        movingPoint.x = x;
                        movingPoint.y = y;
                    }
                    else
                        dragging = false;
                    }
                }
                else if(joystick.shooting && id == joystick.shootingId){
                    if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()) {
                        shootingPoint.x = x;
                        shootingPoint.y = y;                            
                    }
                    else
                        shooting = false;
                    }
                }
            }
            actionString = "MOVE";
            break;
        }
    }
Reynard
źródło
proszę pana, jesteś moim bohaterem. Dodam zaktualizowany kod do mojego pytania, abyś mógł zobaczyć, jak go rozwiązałem z twoją pomocą! teraz mogę wreszcie zakończyć grę: D
Green_qaue
7

Prawie masz rację, ale powinieneś użyć swojego identyfikatora wskaźnika, aby zażądać X / Y zamiast i

    int id = event.getPointerId(i);
    int x = (int) event.getX(id);
    int y = (int) event.getY(id);

Z dokumentacji MotionEvent:

Kolejność pojawiania się poszczególnych wskaźników w zdarzeniu ruchu jest niezdefiniowana. Zatem wskaźnik wskaźnika wskaźnika może zmieniać się z jednego zdarzenia do następnego, ale identyfikator wskaźnika wskaźnika pozostaje niezmienny, dopóki wskaźnik pozostaje aktywny. Użyj metody getPointerId (int), aby uzyskać identyfikator wskaźnika wskaźnika, aby śledzić go we wszystkich kolejnych zdarzeniach ruchu w geście. Następnie w przypadku kolejnych zdarzeń ruchu użyj metody findPointerIndex (int), aby uzyskać indeks wskaźnika dla danego identyfikatora wskaźnika w tym zdarzeniu ruchu.

event.getX/Ynie wymagają identyfikatora wskaźnika i, ponieważ nie ma gwarancji, że będą w tej samej kolejności.

Ponadto istnieje inny subtelny, ale ważny problem. Zauważ, że rodzina funkcji getAction () nie przyjmuje parametru. To trochę dziwne, prawda? Uzyskanie X / Y wymaga identyfikatora wskaźnika, ale nie wykonanej akcji? Wskazuje to na kilka ważnych rzeczy:

  • otrzymujesz połączenie z obsługą dotykową dla każdej akcji każdego wskaźnika, a nie pojedyncze połączenie na ramkę dla wszystkich wskaźników
  • getX / Y sprawdza dotychczasowy ślad wskaźnika i zwraca najnowszą wartość, podczas gdy getAction pyta tylko o bieżące zdarzenie

Oznacza to, że otrzymujesz jedno wywołanie do funkcji obsługi dotykowej na akcję wskaźnika (w dół / ruch / w górę). Więc dwa palce w ruchu = 2 połączenia. Paskudnym efektem ubocznym twojego kodu jest to, że stosuje działanie jednego zdarzenia do wszystkich wskaźników ...

Więc zamiast zapętlać ślady, po prostu uzyskaj akcję pid / x / y / tylko dla bieżącego zdarzenia (w przypadku jednoczesnego ruchu dostaniesz kolejne wywołanie do twojego handlera nieco później, jak powiedziałem)

Oto mój kod do obsługi zdarzeń:

 public static boolean sendTouchToGameEngine (MotionEvent event)
 {
  int action = event.getAction();
  int actionCode = action & MotionEvent.ACTION_MASK;
  int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;

  [...]
  sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));
  [...]

  return true;

}

Aby wrócić do kodu, możesz go uprościć w następujący sposób. Pętla zniknęła, w przeciwnym razie, jeśli masz inne przeciwwskazania, o których nie wspomniałeś, zawsze możesz ją dodać z powrotem. Działa poprzez śledzenie, który identyfikator wskaźnika jest używany do sterowania (ruch / strzelanie) po uruchomieniu DÓŁ i resetowanie ich w GÓRĘ. Ponieważ nie używasz gestów, możesz ograniczyć przełącznik () do pozycji W DÓŁ, ​​W GÓRĘ, NA PRZESUWANIE i NA ZEWNĄTRZ.

int movePointerId = -1;
int shootingPointerId = -1;

void TouchEventHandler(MotionEvent event) {   
    // grab the pointer id 
    int pid = action >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    int x = (int) event.getX(pid);
    int y = (int) event.getY(pid); 
    int action = event.getAction();
    int actionCode = action & MotionEvent.ACTION_MASK;
    int actionIndex = event.getActionIndex();
    String actionString;


    switch (actionCode)
    {
        case MotionEvent.ACTION_DOWN:
        // on DOWN, figure out whether the player used the moving or shooting control, if any.
        // if so, kept track of which pointer was used, because all following call about that
        // finger touches will use the same pointer id. Also record the current point coordinates.
            actionString = "DOWN";
            try{
                if(x > 0 && x < steeringxMesh + (joystick.get_joystickBg().getWidth() * 2)
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                        movingPoint.x = x;
                        movingPoint.y = y;
                        movePointerId = pid;
                    }
                else if(x > shootingxMesh - (joystick.get_joystickBg().getWidth()) && x < panel.getWidth()
                        && y > yMesh - (joystick.get_joystickBg().getHeight()) && y < panel.getHeight()){
                        shootingPoint.x = x;
                        shootingPoint.y = y;
                        shootingPointerId = pid;
                    }
                }catch(Exception e){

                }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_OUTSIDE:
        // whether the player lift the finger or moves it out of bounds
        // figure out which pointer that was and reset it. You can add additional
        // processing here as required
           if( pid == movePointerId )
              movePointerId = -1;
           else if( pid == shootingPointerId )
              shootingPointerId = -1;
            actionString = "UP";
            break;  
        case MotionEvent.ACTION_MOVE:
        // when the player move their finger, it is simply a matter of comparing the pid
        // to know which one it is
          if( pid == movePointerId ) {
                        movingPoint.x = x;
                        movingPoint.y = y;
          } else if( pid == shootingPointerId ) {
                        shootingPoint.x = x;
                        shootingPoint.y = y;
          }
                actionString = "MOVE";

    }
}
ADB
źródło
dzięki za tę świetną odpowiedź, naprawdę wyjaśnia kilka rzeczy. Czy możesz po prostu wyjaśnić, co sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));robi ta linia i kiedy do niej zadzwonisz?
Green_qaue
również gdy używam tej linii: int pid = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;zaćmienie mówi mi, żebym dodał supress Ostrzeżenie, czy to normalne? Przepraszamy za wszystkie pytania po tak szczegółowej odpowiedzi. MotionEvent jest dla mnie bardzo nowy i z jakiegoś powodu nie mogę zrozumieć logiki
Green_qaue 24.09.12
dodano zaktualizowany kod, jak widać Naprawdę nie wiem, co zrobić pid, a pętla nadal tam jest, nie będzie działać bez niej, ponieważ muszę ją pobrać i.
Green_qaue
@Max: sendTouchToGameEngine to po prostu wezwanie do dodania tego zdarzenia do mojej kolejki silnika gry w celu przetworzenia podczas następnej aktualizacji. Jeśli nie używasz kolejki do odpytywania zdarzenia wejściowego, ryzykujesz, że funkcja wywołująca zadziała ze stanem silnika gry w nieoczekiwany sposób, ponieważ TouchEvent pochodzi z wątku interfejsu użytkownika i, prawdopodobnie, aktualizacji silnika gry działa w innym wątku
ADB
@ Max: Niestety nie wiem o ostrzeżeniu. Czy możesz nam powiedzieć, który to jest?
ADB
0

Z tym kodem dzieje się trochę dziwności. Nie używam Androida, więc może myślę, że coś takiego nie jest. Zauważyłem jednak, że otrzymujesz pozycję dwukrotnie, na dwa różne sposoby:

Najpierw dostajesz to w ten sposób na początku swojej pointerCountpętli:

(int) event.getX(i)

Następnie wewnątrz instrukcji switch otrzymujesz to w następujący sposób:

(int) event.getX(id)

Zauważ, że przełączasz się z używania ina używanie id.

Zakładam, że to powinna być pierwsza metoda. Zalecam zastąpienie wszystkich instancji (int) event.getX(id)i użycie wartości xustawionej na początku. Podobnie zamiana (int) event.getY(id)na y.

Spróbuj zamienić te części:

int pointerCount = event.getPointerCount(); 
for (int i = 0; i < pointerCount; i++)
{       
    int x = (int) event.getX(i);
    int y = (int) event.getY(i);

Następnie w przełączniku użyj tego:

    try{
        if(x > 0 && x < touchingBox &&
              y > touchingBox && y < view.getHeight()){
            movingPoint.x = x;
            movingPoint.y = y;
            dragging = true;
        }
        else if(x > touchingBox && x < view.getWidth() &&
                   y > touchingBox && y < view.getHeight()){
            shootingPoint.x = x;
            shootingPoint.y = y;
            shooting=true;
        }else{
            shooting=false;
            dragging=false;
        }
MichaelHouse
źródło
2
Chcesz skomentować, dlaczego nie jest to przydatne i warte pochwały? To grzeczna rzecz do zrobienia.
MichaelHouse
Zgoda, jeśli głosujesz w głosowaniu w dół. Daj mu znać dlaczego. Masz rację, to z poprzedniej metody. Próbowałem około miliona różnych rzeczy, więc zapomniałem je usunąć.
Green_qaue
Czy możesz zaktualizować kod w swoim pytaniu, aby to odzwierciedlić? Widzę, że usunąłeś ustawienie xi y, ale nadal otrzymujesz pozycję do idpóźniej.
MichaelHouse
Ach, zajęło mi trochę czasu, aby zrozumieć twoją odpowiedź. ale jak to zadziała? jeśli użyję tej samej zmiennej ( x=event.getX(i)), wtedy xbędę musiał przechowywać 2 wartości, ilekroć pointerCountjest więcej niż 1, prawda? I to nie wydaje się właściwe.
Green_qaue,
1
Z tego, co rozumiem, eventnaprawdę jest lista wydarzeń. Dostęp do różnych wydarzeń można uzyskać, przeglądając listę tak, jak robisz. Aby uzyskać dostęp do xpozycji drugiego wydarzenia, którego używasz event.getX(1)(ponieważ zaczynamy od 0). Istnieje wiele xs, używasz parametru, aby powiedzieć mu, który chcesz. Zapętlasz wszystkie zdarzenia za pomocą swojej forpętli, więc użyj tego numeru jako bieżącego wydarzenia, które Cię interesuje. Zobacz moją sugestię zmiany kodu.
MichaelHouse
0

Miałem podobny problem raz na Huawei U8150. Wielodotykowy dotyk dwóch palców na tym urządzeniu był naprawdę zły, używając aplikacji testowej wielodotykowej (być może był to „Tester telefonu”, ale nie jestem pewien). Widziałem, że dotykanie drugim palcem przesunęło punkt pierwszego dotknięcia wielu pikseli . Jeśli to jest twój problem związany ze sprzętem i nie sądzę, że możesz na to wiele poradzić :(

Przepraszam za mój zły angielski

Marco Martinelli
źródło
to nie jest problem
Green_qaue
ok, to tylko myśl
Marco Martinelli
0

Uważam, że twoim problemem jest to, że zakładasz, że między aktualizacjami kolejność ułożenia wskaźników pozostaje taka sama. Najprawdopodobniej tak się nie stanie.

Wyobraź sobie sytuację, w której dotykasz palcem A. Będzie wskaźnik 1 (1), a A będzie jedynym elementem, o który możesz zapytać. Jeśli dodasz drugi palec, wskaźnik pointerCount () będzie wynosił 2, A będzie miał indeks 0, B będzie miał indeks 1. Jeśli podniesiesz palec A, pointerCount () będzie miał ponownie 1, a B będzie miał indeks 0 . Jeśli następnie ponownie dotkniesz palcem A, A będzie miało indeks 1, a B będzie pod indeksem 0 .

Dlatego podano identyfikator wskaźnika, abyś mógł śledzić poszczególne poprawki między aktualizacjami. Jeśli więc pierwsze dotknięcie palca B ma przypisany identyfikator 12, zawsze będzie on miał ten identyfikator, nawet gdy palec A zostanie usunięty i ponownie dodany.

Więc jeśli kod identyfikuje dotyk w pobliżu joysticka strzelającego, musi sprawdzić, czy już trwa dotyk „strzelający”. Jeśli nie, to identyfikator strzelającego dotyku powinien zostać zapamiętany w jakiejś zmiennej składowej, która utrzymuje się do następnej aktualizacji. W kolejnych aktualizacjach, jeśli masz dotyk „strzelania”, iteruj po wskaźnikach i szukaj wskaźnika z odpowiednim identyfikatorem, i jest to wskaźnik, którego musisz użyć do śledzenia aktualizacji, a wszystkie inne można zignorować. To samo dotyczy dotyku ruchu. Po zwolnieniu dotyku usuwasz identyfikator powiązany z tym joystickiem, aby nowy dotyk mógł przejąć nad nim kontrolę.

Nawet jeśli dotyk, który zaczyna się w pobliżu jednego joysticka, znajduje się daleko od pierwotnej pozycji dotyku, po jego identyfikatorze nadal można poprawnie zidentyfikować kontrolowany joystick. I jako przyjemny efekt uboczny, drugi zbłąkany palec w pobliżu konkretnego joysticka nie przyniesie żadnego efektu, ponieważ joystick będzie związany z pierwszym palcem, który go uruchomił, dopóki nie zostanie zwolniony.

MrCranky
źródło