retornando o ID multiTouch correto

9

Passei inúmeras horas lendo tutoriais e analisando todas as perguntas relacionadas ao multiTouch daqui e ao Stackoverflow. Mas eu simplesmente não consigo descobrir como fazer isso corretamente. Eu uso um loop para obter o meu pointerId, eu não vejo muitas pessoas fazendo isso, mas é a única maneira que eu consegui fazê-lo funcionar um pouco.

Tenho dois joysticks na tela, um para mover e outro para controlar a rotação dos sprites e o ângulo que ele dispara, como no Monster Shooter. Ambos funcionam bem.

Meu problema é que, quando movo meu sprite ao mesmo tempo que estou atirando, meu touchingPointmovimento é definido como o touchingPointdo meu disparo, já que o xe yé maior no touchingPointdo meu disparo ( moving-stickno lado esquerdo da tela, shooting-stickno lado direito) , meu sprite acelera, isso cria uma alteração indesejada na velocidade do meu sprite.

foi assim que resolvi com a sua ajuda! isto é para qualquer um que possa ter um problema semelhante:

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

Não postaria tanto código se não perdesse absolutamente o que estou fazendo de errado. Simplesmente não consigo entender bem como o multiTouching funciona.

movingPointmuda basicamente para o meu primeiro e segundo dedo. Eu o vinculo a uma caixa, mas, enquanto eu seguro um dedo dentro dessa caixa, ele muda seu valor com base em onde meu segundo dedo toca. Ele se move na direção certa e nada dá erro, o problema é a mudança de velocidade, é quase como se somar os dois pontos de contato.

máx.
fonte

Respostas:

4

Eu acho que isso vai funcionar para você.

Um erro que você cometeu foi a iteração sobre todos os indicadores para cada evento. É necessário apenas para eventos de movimentação.

Em segundo lugar, você realmente precisa colocar o valor do índice nas funções getX e getY, mas obtém o ID associado a esse índice para usar como referência aos objetos do jogo. Você atribui um ID ao seu joystick durante o Evento Inativo e, ao iterar pelos índices do ponteiro, verifique se o índice está associado ao ID do ponteiro que você atribuiu ao joystick durante o evento Inativo. Se estiver, verifique se ainda está dentro dos limites e atualize-o ou desative-o.

Não estou testando esse código, mas sei que funciona em conceito porque uso o método no meu próprio código. Deixe-me saber se há algum problema que você não consiga descobrir.

Primeiro, você precisará adicionar o seguinte à sua classe de joystick.

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

Altere seu onTouchEvent para isso.

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
fonte
Você senhor, é meu heroi. Vou adicionar o código atualizado à minha pergunta para que você possa ver como eu o resolvi com a sua ajuda! agora posso finalmente terminar meu jogo: D
Green_qaue
7

Você está quase acertando, mas deve usar o ID do ponteiro para solicitar o X / Y em vez de i

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

Na documentação do MotionEvent:

A ordem na qual os ponteiros individuais aparecem em um evento de movimento é indefinida. Assim, o índice do ponteiro de um ponteiro pode mudar de um evento para o próximo, mas é garantido que a identificação do ponteiro de um ponteiro permaneça constante enquanto o ponteiro permanecer ativo. Use o método getPointerId (int) para obter o ID do ponteiro e acompanhá-lo em todos os eventos de movimento subsequentes em um gesto. Em seguida, para eventos de movimento sucessivos, use o método findPointerIndex (int) para obter o índice do ponteiro para um determinado ID de ponteiro nesse evento de movimento.

event.getX/Yexigem um ID de ponteiro, não i, porque não há garantia de que eles estarão na mesma ordem.

Além disso, há outro problema sutil, mas importante. Observe como a família de funções getAction () não aceita um parâmetro. Isso é meio estranho, certo? Obter X / Y requer o ID do ponteiro, mas não a ação executada? Isso está sugerindo algumas coisas importantes:

  • você recebe uma chamada para manipular o toque para cada ação de cada ponteiro e não uma única chamada por quadro para todos os ponteiros
  • getX / Y examina o rastreamento do ponteiro até o momento e retorna o valor mais recente, enquanto getAction consulta apenas o evento atual

Isso significa que você recebe uma chamada para o manipulador de toque por ação do ponteiro (para baixo / mover / para cima). Então, dois dedos se movendo = 2 chamadas. Um efeito colateral desagradável do seu código é que ele aplica a ação de um evento a todos os ponteiros ...

Portanto, em vez de repetir os traços, basta obter a ação pid / x / y / apenas para o evento atual (para movimento simultâneo, você receberá outra chamada ao seu manipulador um pouquinho mais tarde, como eu disse)

Aqui está o meu código para manipular 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 voltar ao seu código, você poderá simplificá-lo da seguinte maneira. O loop desapareceu, caso contrário, se você tiver outras restrições que não mencionou, sempre poderá adicioná-lo novamente. Ele funciona rastreando qual ID de ponteiro é usado para qual controle (mover / disparar) quando DOWN é acionado e redefinindo-os em UP. Como você não usa gestos, você pode restringir sua troca () a ABAIXO, CIMA, MOVER e FORA.

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
fonte
obrigado por esta ótima resposta, realmente esclarece algumas coisas. Você poderia explicar o que essa linha sendTouchToGameEngine(pid, actionCode, (int)event.getX(pid), (int)event.getY(pid));faz e quando você chama?
Green_qaue 24/09/12
também quando uso esta linha: int pid = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;eclipse me diz para adicionar um supressWarning, isso é normal? Desculpe por todas as perguntas após uma resposta tão detalhada. MotionEvent é muito novo para mim, e não consigo entender a lógica por algum motivo
Green_qaue
adicionado código atualizado, como você pode ver, eu realmente não sei o que fazer pid, e o loop ainda está lá, não funcionará sem ele, pois eu preciso i.
Green_qaue 24/09/12
@ Max: o sendTouchToGameEngine é simplesmente uma chamada para adicionar esse evento à minha fila de mecanismo de jogo para ser processado durante a próxima atualização. Se você não usar uma fila para pesquisar seu evento de entrada, corre o risco de sua função de chamada mexer com o estado do mecanismo de jogo de maneiras inesperadas, pois o TouchEvent vem do thread da interface do usuário e, presumivelmente, a atualização do mecanismo de jogo é executado em um segmento diferente
ADB
@ Max: Eu não sei sobre o aviso, infelizmente. Você pode nos dizer qual é?
BAD
0

Há um pouco de estranheza nesse código. Eu não uso o Android, então talvez eu esteja pensando que isso é algo que não é. No entanto, notei que você está recebendo a posição duas vezes, de duas maneiras diferentes:

Primeiro você obtém assim no início do seu pointerCountloop:

(int) event.getX(i)

Em seguida, dentro da instrução switch, você obtém o seguinte:

(int) event.getX(id)

Observe que você alterna de usar ipara usar id.

Suponho que seja o método anterior. Eu recomendo substituir todas as instâncias (int) event.getX(id)e usar o valor que xvocê definiu no início. Da mesma forma, trocando (int) event.getY(id)para y.

Tente trocar essas partes:

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

Então, dentro do seu switch, use o seguinte:

    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
fonte
2
Gostaria de comentar por que isso não é útil e digno de voto negativo? É a coisa educada a fazer.
MichaelHouse
Concordado, se você fizer o voto negativo. Deixe ele saber o porquê. Você está certo é de um método anterior. Eu tentei cerca de um milhão de coisas diferentes, então eu esqueci de removê-lo.
Green_qaue 21/09/12
Você pode atualizar o código na sua pergunta para refletir isso? Vejo que você removeu a configuração de xe y, mas você ainda está obtendo a posição idmais tarde.
MichaelHouse
aah, demorei um pouco para entender sua resposta. mas como isso vai funcionar? se eu usar a mesma variável ( x=event.getX(i)), então xterá que armazenar 2 valores sempre que pointerCountfor maior que 1, correto? E isso não parece certo.
Green_qaue 21/09/12
11
Pelo que entendi, eventé realmente uma lista de eventos. Você acessa os diferentes eventos percorrendo a lista como está fazendo. Portanto, para acessar a xposição do segundo evento que você usa event.getX(1)(já que estamos começando em 0). Existem vários xs, você está usando o parâmetro para dizer qual deseja. Você está percorrendo todos os eventos com seu forloop, portanto, use esse número como o evento atual em que está interessado. Veja minha sugestão de alteração de código.
MichaelHouse
0

Eu tive um problema semelhante uma vez em um Huawei U8150. O multitoque de dois dedos nesse dispositivo era muito ruim, usando um aplicativo de teste multitoque (talvez fosse "Phone Tester", mas não tenho certeza). Pude ver que o toque com o segundo dedo movia o primeiro ponto de toque de muitos pixels . Se esse é o seu problema, não está relacionado ao hardware e acho que você não pode fazer muito por isso :(

Desculpe pelo meu inglês ruim

Marco Martinelli
fonte
esse não é o problema
Green_qaue
ok, foi só um pensamento
Marco Martinelli
0

Acredito que seu problema é que você está assumindo que, entre as atualizações, a ordem em que os ponteiros são organizados permanece a mesma. Provavelmente não será esse o caso.

Imagine uma situação em que você toque com o dedo A. Haverá um apontadorCount () igual a 1 e A será o único elemento que você poderá perguntar. Se você adicionar um segundo dedo, o pointerCount () será 2, A estará no índice 0, B estará no índice 1. Se você aumentar o dedo A, pointerCount () será 1 novamente e B estará no índice 0 . Se você tocar novamente com o dedo A, A estará no índice 1 e B estará no índice 0 .

É por isso que o ID do ponteiro é fornecido, para que você possa rastrear toques individuais entre as atualizações. Portanto, se o primeiro toque do dedo B receber o ID 12, ele sempre terá esse ID, mesmo quando o dedo A for removido e adicionado novamente.

Portanto, se o seu código identificar um toque próximo ao joystick de tiro, ele deverá verificar se já existe um toque de 'tiro' em andamento. Caso contrário, o ID do toque de tiro deve ser lembrado em alguma variável de membro que persista na próxima atualização. Nas atualizações subseqüentes, se você tiver um toque de 'disparo', percorra os ponteiros e procure o ponteiro com o ID correto; é esse ponteiro que você precisa usar para rastrear atualizações e todos os outros podem ser ignorados. O mesmo para o toque do movimento. Quando o toque é liberado, você limpa o ID associado a esse joystick para que um novo toque possa controlá-lo.

Mesmo que um toque que comece perto de um joystick se afaste da posição de toque original, por seu ID, você ainda poderá identificar corretamente o joystick que está controlando. E, como um bom efeito colateral, um segundo dedo perdido próximo a um joystick específico não terá efeito, porque o joystick será vinculado ao primeiro dedo que o acionou até que seja liberado.

MrCranky
fonte