Rückgabe der korrekten MultiTouch-ID


9

Ich habe unzählige Stunden damit verbracht, Tutorials zu lesen und mir alle Fragen zu multiTouch von hier und Stackoverflow anzuschauen. Aber ich kann einfach nicht herausfinden, wie ich das richtig machen soll. Ich benutze eine Schleife, um meine zu bekommen pointerId, ich sehe nicht viele Leute, die dies tun, aber es ist die einzige Möglichkeit, es irgendwie zum Laufen zu bringen.

Ich habe zwei Joysticks auf meinem Bildschirm, einen zum Bewegen und einen zum Steuern der Sprites-Rotation und des Winkels, den er schießt, wie in Monster Shooter. Beide funktionieren gut.

Mein Problem ist, dass, wenn ich mein Sprite gleichzeitig mit dem Schießen bewege, touchingPointmeine Bewegung auf das touchingPointSchießen eingestellt ist, da das xund yhöher auf dem touchingPointSchießen liegt ( moving-stickauf der linken Seite des Bildschirms, shooting-stickauf der rechten Seite). Wenn mein Sprite schneller wird, führt dies zu einer unerwünschten Geschwindigkeitsänderung für mein Sprite.

So habe ich es mit Ihrer Hilfe gelöst! Dies ist für alle, die auf ein ähnliches Problem stoßen könnten:

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

Ich würde nicht so viel Code posten, wenn ich nicht den absoluten Verlust dessen hätte, was ich falsch mache. Ich kann einfach nicht gut verstehen, wie MultiTouching funktioniert.

Grundlegende movingPointÄnderungen für meinen ersten und zweiten Finger. Ich binde es an eine Box, aber solange ich einen Finger in dieser Box halte, ändert sich sein Wert je nachdem, wo mein zweiter Finger berührt. Es bewegt sich in die richtige Richtung und nichts gibt einen Fehler aus. Das Problem ist die Geschwindigkeitsänderung. Es ist fast so, als würde es die beiden Berührungspunkte addieren.

Antworten:


4

Ich denke, das wird für dich funktionieren.

Ein Fehler, den Sie gemacht haben, war das Durchlaufen aller Zeiger für jedes Ereignis. Es ist nur für Bewegungsereignisse erforderlich.

Zweitens müssen Sie den Indexwert tatsächlich in die Funktionen getX und getY einfügen, aber Sie erhalten die diesem Index zugeordnete ID als Referenz für Ihre Spielobjekte. Sie weisen Ihrem Joystick während des Down-Ereignisses eine ID zu und überprüfen dann beim Durchlaufen Ihrer Zeigerindizes, ob der Index der Zeiger-ID zugeordnet ist, die Sie dem Joystick während des Down-Ereignisses zugewiesen haben. Wenn dies der Fall ist, überprüfen Sie, ob es sich noch in Grenzen befindet, und aktualisieren oder deaktivieren Sie es.

Ich teste diesen Code nicht, aber ich weiß, dass er im Konzept funktioniert, da ich die Methode in meinem eigenen Code verwende. Lassen Sie mich wissen, wenn es Probleme gibt, die Sie nicht herausfinden können.

Zunächst müssen Sie Ihrer Joystick-Klasse Folgendes hinzufügen.

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

Ändern Sie Ihr onTouchEvent in dieses.

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

Sie, mein Herr, sind mein Held. Ich werde den aktualisierten Code zu meiner Frage hinzufügen, damit Sie sehen können, wie ich ihn mit Ihrer Hilfe gelöst habe! Jetzt kann ich endlich mein Spiel beenden: D
Green_qaue

7

Sie haben es fast richtig gemacht, aber Sie sollten Ihre Zeiger-ID verwenden, um das X / Y anstelle von anzufordern i

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

Aus der MotionEvent-Dokumentation:

Die Reihenfolge, in der einzelne Zeiger innerhalb eines Bewegungsereignisses angezeigt werden, ist undefiniert. Somit kann sich der Zeigerindex eines Zeigers von einem Ereignis zum nächsten ändern, aber die Zeiger-ID eines Zeigers bleibt garantiert konstant, solange der Zeiger aktiv bleibt. Verwenden Sie die Methode getPointerId (int), um die Zeiger-ID eines Zeigers abzurufen und ihn über alle nachfolgenden Bewegungsereignisse in einer Geste zu verfolgen. Verwenden Sie dann für aufeinanderfolgende Bewegungsereignisse die Methode findPointerIndex (int), um den Zeigerindex für eine bestimmte Zeiger-ID in diesem Bewegungsereignis abzurufen.

event.getX/Ybenötigen eine Zeiger-ID, nicht i, da es keine Garantie gibt, dass sie in derselben Reihenfolge sind.

Darüber hinaus gibt es ein weiteres subtiles, aber wichtiges Problem. Beachten Sie, dass die Funktionsfamilie getAction () keinen Parameter akzeptiert. Das ist irgendwie komisch, oder? Um X / Y zu erhalten, ist die Zeiger-ID erforderlich, nicht jedoch die ausgeführte Aktion. Das deutet auf ein paar wichtige Dinge hin:

  • Sie erhalten einen Aufruf an Ihren Touch-Handler für jede Aktion jedes Zeigers und nicht einen einzigen Aufruf pro Frame für alle Zeiger
  • getX / Y überprüft die bisherige Ablaufverfolgung des Zeigers und gibt den neuesten Wert zurück, während getAction nur nach dem aktuellen Ereignis fragt

Das bedeutet, dass Sie pro Zeigeraktion (nach unten / nach oben / nach oben) einen Anruf an Ihren Touch-Handler erhalten . Also zwei Finger bewegen sich = 2 Anrufe. Ein böser Nebeneffekt Ihres Codes ist, dass er die Aktion eines Ereignisses auf alle Zeiger anwendet ...

Anstatt die Spuren zu durchlaufen, rufen Sie einfach die Aktion pid / x / y / nur für das aktuelle Ereignis ab (bei gleichzeitiger Bewegung erhalten Sie, wie gesagt, ein kleines bisschen später einen weiteren Anruf bei Ihrem Handler).

Hier ist mein Code zur Behandlung von Ereignissen:

 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;

}}

Um zu Ihrem Code zurückzukehren, sollten Sie ihn auf folgende Weise vereinfachen können. Die Schleife ist weg, andernfalls können Sie sie jederzeit wieder hinzufügen, wenn Sie andere Einschränkungen haben, die Sie nicht erwähnt haben. Es funktioniert, indem verfolgt wird, welche Zeiger-ID für welches Steuerelement (Bewegen / Schießen) verwendet wird, wenn DOWN ausgelöst wird, und diese auf UP zurückgesetzt werden. Da Sie keine Gesten verwenden, können Sie Ihren Schalter () auf DOWN, UP, MOVE und OUTSIDE beschränken.

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

    }
}

danke für diese tolle antwort, klärt wirklich ein paar dinge auf. Könnten Sie einfach erklären, was diese Zeile sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));tut und wann Sie sie nennen?
Green_qaue

Auch wenn ich diese Zeile verwende: int pid = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;Eclipse fordert mich auf, eine SupressWarning hinzuzufügen. Ist das normal? Entschuldigen Sie alle Fragen nach einer so detaillierten Antwort. MotionEvent ist sehr neu für mich und kann die Logik aus irgendeinem Grund nicht verstehen
Green_qaue

aktualisierter Code hinzugefügt, wie Sie sehen können, weiß ich nicht wirklich, was ich tun soll pid, und die Schleife ist immer noch da, wird ohne sie nicht funktionieren, da ich bekommen muss i.
Green_qaue

@Max: Die sendTouchToGameEngine ist einfach ein Aufruf, um dieses Ereignis zu meiner Game Engine-Warteschlange hinzuzufügen und es beim nächsten Update zu verarbeiten. Wenn Sie keine Warteschlange verwenden, um Ihr Eingabeereignis abzufragen, besteht die Gefahr, dass Ihre aufrufende Funktion den Status der Spiel-Engine auf unerwartete Weise beeinträchtigt, da das TouchEvent aus dem UI-Thread und vermutlich aus dem Update Ihrer Spiel-Engine stammt läuft in einem anderen Thread
ADB

@ Max: Ich weiß leider nichts über die Warnung. Können Sie uns sagen, welches es ist?
ADB

0

Mit diesem Code ist ein bisschen komisch. Ich benutze kein Android, also denke ich vielleicht, dass dies etwas ist, was es nicht ist. Ich habe jedoch bemerkt, dass Sie die Position zweimal bekommen, auf zwei verschiedene Arten:

Zuerst erhalten Sie es so am Anfang Ihrer pointerCountSchleife:

(int) event.getX(i)

Dann erhalten Sie es in Ihrer switch-Anweisung folgendermaßen:

(int) event.getX(id)

Beachten Sie, dass Sie von using izu using wechseln id.

Ich gehe davon aus, dass es die erstere Methode sein soll. Ich empfehle, alle Instanzen von zu ersetzen (int) event.getX(id)und xden zu Beginn festgelegten Wert zu verwenden. Ebenso wechseln (int) event.getY(id)zu y.

Versuchen Sie, diese Teile auszutauschen:

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

Verwenden Sie dann in Ihrem Switch Folgendes:

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

2
Möchten Sie kommentieren, warum dies nicht nützlich und einer Ablehnung würdig ist? Es ist die höfliche Sache zu tun.
MichaelHouse

Einverstanden, wenn Sie abstimmen. Lass ihn wissen warum. Sie haben Recht, es ist von einer früheren Methode. Ich habe ungefähr eine Million verschiedene Dinge ausprobiert, also habe ich einfach vergessen, sie zu entfernen.
Green_qaue

Können Sie den Code in Ihrer Frage aktualisieren, um dies widerzuspiegeln? Ich sehe, dass Sie die Einstellung von xund entfernt haben y, aber Sie erhalten die Position idspäter noch.
MichaelHouse

aah, ich habe eine Weile gebraucht, um deine Antwort zu verstehen. aber wie wird das funktionieren? Wenn ich dieselbe Variable ( x=event.getX(i)) verwende, müssen dann x2 Werte gespeichert werden, wenn pointerCountmehr als 1 ist. Richtig? Und das fühlt sich nicht richtig an.
Green_qaue

1
Soweit ich weiß, eventhandelt es sich wirklich um eine Liste von Ereignissen. Sie greifen auf die verschiedenen Ereignisse zu, indem Sie die Liste wie gewohnt durchgehen. Um auf die xPosition für das zweite Ereignis zuzugreifen, das Sie verwenden event.getX(1)(da wir bei 0 beginnen). Es gibt mehrere xs, Sie verwenden den Parameter, um anzugeben, welche auf Sie möchten. Sie durchlaufen alle Ereignisse mit Ihrer forSchleife. Verwenden Sie diese Nummer also als aktuelles Ereignis, an dem Sie interessiert sind. Siehe meinen Vorschlag zur Codeänderung.
MichaelHouse

0

Ich hatte einmal ein ähnliches Problem auf einem Huawei U8150. Das Zwei-Finger-Multitouch auf diesem Gerät war wirklich schlecht. Mit einer Multitouch-Test-App (vielleicht war es "Phone Tester", aber ich bin mir nicht sicher) konnte ich sehen, dass das Berühren mit dem zweiten Finger den ersten Berührungspunkt vieler Pixel bewegte . Wenn das Ihr Problem ist, dann hängt es mit der Hardware zusammen und ich glaube nicht, dass Sie viel dafür tun können :(

Entschuldigung für mein schlechtes Englisch


das ist nicht das Problem
Green_qaue

ok, war nur ein Gedanke
Marco Martinelli

0

Ich glaube, Ihr Problem ist, dass Sie davon ausgehen, dass zwischen den Aktualisierungen die Reihenfolge, in der die Zeiger angeordnet sind, gleich bleibt. Das wird höchstwahrscheinlich nicht der Fall sein.

Stellen Sie sich eine Situation vor, in der Sie mit Finger A berühren. Es gibt einen pointerCount () von 1, und A ist das einzige Element, nach dem Sie fragen können. Wenn Sie einen zweiten Finger hinzufügen, ist pointerCount () 2, A ist auf Index 0, B ist auf Index 1. Wenn Sie Finger A heben, ist pointerCount () wieder 1 und B ist auf Index 0 . Wenn Sie dann erneut mit Finger A berühren, befindet sich A auf Index 1 und B auf Index 0 .

Aus diesem Grund wird die Zeiger-ID bereitgestellt, damit Sie einzelne Berührungen zwischen Aktualisierungen verfolgen können. Wenn also der ersten Berührung von Finger B die ID 12 zugewiesen wird, hat sie immer diese ID, selbst wenn Finger A entfernt und erneut hinzugefügt wird.

Wenn Ihr Code also eine Berührung in der Nähe Ihres Schieß-Joysticks erkennt, muss überprüft werden, ob bereits eine Berührung mit dem Schießen ausgeführt wird. Wenn nicht, sollte die ID der Schießberührung in einer Mitgliedsvariablen gespeichert werden, die bis zum nächsten Update bestehen bleibt. Wenn Sie in nachfolgenden Aktualisierungen eine "Schießerei" ausführen, durchlaufen Sie die Zeiger und suchen Sie nach dem Zeiger mit der richtigen ID. Dieser Zeiger muss zum Verfolgen von Aktualisierungen verwendet werden. Alle anderen können ignoriert werden. Gleiches gilt für die Bewegungsberührung. Wenn die Berührung losgelassen wird, löschen Sie die ID, die diesem Joystick zugeordnet ist, damit eine neue Berührung die Kontrolle darüber übernehmen kann.

Selbst wenn eine Berührung, die in der Nähe eines Joysticks beginnt, weit von der ursprünglichen Berührungsposition entfernt ist, können Sie anhand ihrer ID immer noch korrekt identifizieren, welchen Joystick sie steuert. Und als netter Nebeneffekt hat ein zweiter streunender Finger in der Nähe eines bestimmten Joysticks keine Wirkung, da der Joystick an den ersten Finger gebunden ist, der ihn ausgelöst hat, bis er losgelassen wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.