devolviendo la identificación multitáctil correcta

9

He pasado innumerables horas leyendo tutoriales y examinando todas las preguntas relacionadas con multiTouch desde aquí y Stackoverflow. Pero no puedo entender cómo hacer esto correctamente. Utilizo un bucle para obtener mi pointerId, no veo mucha gente haciendo esto, pero es la única forma en que he logrado que funcione.

Tengo dos joysticks en mi pantalla, uno para moverme y otro para controlar la rotación de mis sprites y el ángulo en el que dispara, como en Monster Shooter. Ambos funcionan bien.

Mi problema es que cuando muevo mi sprite al mismo tiempo que estoy disparando, mi touchingPointmovimiento se establece en el touchingPointde mi disparo, ya que xy yes más alto en touchingPointmi disparo ( moving-sticken el lado izquierdo de la pantalla, shooting-sticken el lado derecho) , mi sprite se acelera, esto crea un cambio no deseado en la velocidad de mi sprite.

¡Así es como lo resolví con tu ayuda! Esto es para cualquier persona que pueda tener un problema similar:

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

No publicaría tanto código si no tuviera una pérdida absoluta de lo que estoy haciendo mal. Simplemente no puedo entender bien cómo funciona el MultiTouching.

básicamente movingPoint cambia para mi primer y segundo dedo. Lo ato a un cuadro, pero mientras sostengo un dedo dentro de este cuadro, cambia su valor en función de donde toca mi segundo dedo. Se mueve en la dirección correcta y nada da un error, el problema es el cambio de velocidad, es casi como si sumara los dos puntos de contacto.

Máx.
fuente

Respuestas:

4

Creo que esto funcionará para ti.

Un error que cometió fue iterar sobre todos los punteros para cada evento. Solo es necesario para mover eventos.

En segundo lugar, realmente necesitas poner el valor del índice en las funciones getX y getY, pero obtienes la identificación asociada con ese índice para usarla como referencia a los objetos de tu juego. Asigne una identificación a su joystick durante el evento Down y luego, al iterar a través de sus índices de puntero, verifique si el índice está asociado con la identificación del puntero que asignó al joystick durante el evento Down. Si es así, verifique si todavía está dentro de los límites y actualícelo o desactívelo.

No estoy probando este código, pero sé que funciona en concepto porque uso el método en mi propio código. Avíseme si hay algún problema que no pueda resolver.

Primero, tendrá que agregar lo siguiente a su clase de joystick.

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

Cambia tu onTouchEvent a esto.

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
fuente
Usted señor, es mi héroe. ¡Agregaré el código actualizado a mi pregunta para que pueda ver cómo lo resolví con su ayuda! ahora finalmente puedo terminar mi juego: D
Green_qaue
7

Casi lo está haciendo bien, pero debe usar su identificación de puntero para solicitar la X / Y en lugar de i

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

De la documentación de MotionEvent:

El orden en que aparecen los punteros individuales dentro de un evento de movimiento no está definido. Por lo tanto, el índice del puntero de un puntero puede cambiar de un evento al siguiente, pero se garantiza que la identificación del puntero permanecerá constante mientras el puntero permanezca activo. Utilice el método getPointerId (int) para obtener la identificación del puntero de un puntero para rastrearlo en todos los eventos de movimiento posteriores en un gesto. Luego, para sucesivos eventos de movimiento, use el método findPointerIndex (int) para obtener el índice de puntero para una identificación de puntero dada en ese evento de movimiento.

event.getX/Yrequieren una identificación de puntero, no i, porque no hay garantía de que estén en el mismo orden.

Además, hay otro problema sutil pero importante. Observe cómo la familia de funciones getAction () no toma un parámetro. Eso es un poco raro, ¿verdad? Obtener X / Y requiere la identificación del puntero, pero no la acción realizada. Eso insinúa un par de cosas importantes:

  • recibe una llamada al controlador táctil para cada acción de cada puntero y no una sola llamada por cuadro para todos los punteros
  • getX / Y observa el rastro del puntero hasta ahora y devuelve el último valor, mientras que getAction solo pregunta sobre el evento actual

Eso significa que recibe una llamada a su controlador táctil por acción de puntero (abajo / mover / arriba). Entonces dos dedos en movimiento = 2 llamadas. Un efecto secundario desagradable de su código es que aplica la acción de un evento a todos los punteros ...

Entonces, en lugar de recorrer las trazas, simplemente obtenga la acción pid / x / y / solo para el evento actual (para el movimiento simultáneo, recibirá otra llamada a su controlador un poco más tarde, como dije)

Aquí está mi código para manejar eventos:

 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;

}

Para volver a su código, debería poder simplificarlo de la siguiente manera. El ciclo se ha ido; de lo contrario, si tiene otras restricciones que no mencionó, siempre puede agregarlo nuevamente. Funciona al rastrear qué ID de puntero se utiliza para qué control (mover / disparar) cuando se activa DOWN y restablecerlos en UP. Como no usa gestos, puede restringir su interruptor () a ABAJO, ARRIBA, MOVER y FUERA.

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
fuente
gracias por esta gran respuesta, realmente aclara un par de cosas. ¿Podría explicar qué hace esta línea sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));y cuándo la llama?
Green_qaue
también cuando uso esta línea: int pid = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;eclipse me dice que agregue un supressWarning, ¿es eso normal? Perdón por todas las preguntas después de una respuesta tan detallada. MotionEvent es muy nuevo para mí, y no puedo entender la lógica por alguna razón
Green_qaue
agregó código actualizado, como puede ver, realmente no sé qué hacer pid, y el bucle sigue ahí, no funcionará sin él, ya que necesito obtenerlo i.
Green_qaue
@Max: sendTouchToGameEngine es simplemente una llamada para agregar este evento a la cola de mi motor de juego que se procesará durante la próxima actualización. Si no usa una cola para sondear su evento de entrada, corre el riesgo de que su función de llamada interfiera con el estado del motor del juego de maneras inesperadas, ya que TouchEvent proviene del hilo de la interfaz de usuario y, presumiblemente, la actualización de su motor de juego corre en un hilo diferente
ADB
@Max: Desafortunadamente, no sé sobre la advertencia. ¿Puedes decirnos cuál es?
ADB
0

Hay un poco de rareza con este código. No uso Android, así que tal vez estoy pensando que esto es algo que no es. Sin embargo, noté que obtienes el puesto dos veces, de dos maneras diferentes:

Primero lo obtienes así al comienzo de tu pointerCountciclo:

(int) event.getX(i)

Luego, dentro de su declaración de cambio, lo obtiene así:

(int) event.getX(id)

Observe que cambia de usar ia usar id.

Supongo que se supone que es el método anterior. Recomiendo reemplazar todas las instancias (int) event.getX(id)y usar el valor xque estableció al principio. Del mismo modo cambiar (int) event.getY(id)a y.

Intente intercambiar estas partes:

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

Luego, dentro de su interruptor, use esto:

    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
fuente
2
¿Le gustaría comentar por qué esto no es útil y merece un voto negativo? Es lo educado que hacer.
MichaelHouse
De acuerdo, si rechaza el voto. Hazle saber por qué. Tienes razón, es de un método anterior. He intentado un millón de cosas diferentes, así que olvidé eliminarlo.
Green_qaue
¿Puedes actualizar el código en tu pregunta para reflejar eso? Veo que eliminó la configuración de xy y, pero aún está obteniendo la posición idmás tarde.
MichaelHouse
aah, me tomó un tiempo entender tu respuesta. pero ¿cómo funcionará esto? si uso la misma variable ( x=event.getX(i)), entonces xtendré que almacenar 2 valores siempre que pointerCountsea ​​más de 1, ¿correcto? Y ese dosificador se siente bien.
Green_qaue
1
Por lo que entiendo eventes realmente una lista de eventos. Accede a los diferentes eventos al revisar la lista como lo estás haciendo. Entonces, para acceder a la xposición para el segundo evento que usa event.getX(1)(ya que comenzamos en 0). Hay varios correos xelectrónicos, está utilizando el parámetro para indicarle cuál desea. Estás recorriendo todos los eventos con tu forciclo, así que usa ese número como el evento actual que te interesa. Consulta mi sugerencia de cambio de código.
MichaelHouse
0

Tuve un problema similar una vez en un Huawei U8150. El multitoque de dos dedos en ese dispositivo era realmente malo, usando una aplicación de prueba multitáctil (tal vez era "Phone Tester" pero no estoy seguro) pude ver que tocar con el segundo dedo movía el primer punto táctil de muchos píxeles . Si ese es su problema, está relacionado con el hardware y no creo que pueda hacer mucho por ello :(

Perdón por mi mal ingles

Marco Martinelli
fuente
ese no es el problema
Green_qaue
ok, era solo un pensamiento
Marco Martinelli
0

Creo que su problema es que está asumiendo que, entre actualizaciones, el orden en que se organizan los punteros se mantiene igual. Es muy probable que ese no sea el caso.

Imagine una situación en la que toca con el dedo A. Habrá un pointerCount () de 1, y A será el único elemento sobre el que puede preguntar. Si agrega un segundo dedo, pointerCount () será 2, A estará en el índice 0, B estará en el índice 1. Si levanta el dedo A, pointerCount () volverá a ser 1 y B estará en el índice 0 . Si vuelve a tocar con el dedo A, A estará en el índice 1 y B estará en el índice 0 .

Esta es la razón por la cual se proporciona la ID del puntero, para que pueda rastrear toques individuales entre actualizaciones. Entonces, si al primer toque del dedo B se le asigna la ID 12, siempre tendrá esa ID, incluso cuando el dedo A se quite y se vuelva a agregar.

Entonces, si su código identifica un toque cerca de su joystick de disparo, debe verificar si ya hay un toque de 'disparo' en progreso. De lo contrario, la identificación del toque de disparo debe recordarse en alguna variable miembro que persista hasta la próxima actualización. En las actualizaciones posteriores, si tiene un toque de 'disparo' en progreso, repita los punteros y busque el puntero con la ID correcta, y es ese puntero el que necesita usar para rastrear actualizaciones, y todos los demás pueden ignorarse. Lo mismo para el movimiento táctil. Cuando se suelta el toque, borra la ID asociada con ese joystick para que un nuevo toque pueda tomar el control.

Incluso si un toque que comienza cerca de un joystick se aleja mucho de la posición táctil original, por su ID aún puede identificar correctamente qué joystick está controlando. Y como un buen efecto secundario, un segundo dedo perdido cerca de un joystick en particular no tendrá ningún efecto, ya que el joystick se unirá al primer dedo que lo activó hasta que se suelte.

MrCranky
fuente