Problemas de detecção de colisão usando AABB

8

Eu implementei uma rotina simples de detecção de colisão usando AABB's entre meu sprite de jogo principal e várias plataformas (consulte o código abaixo). Funciona muito bem, mas agora estou introduzindo a gravidade para fazer meu personagem cair e isso mostrou alguns problemas com a rotina do meu CD.

Eu acho que o cerne da questão é que minha rotina de CD move o sprite de volta ao longo do eixo em que ele penetrou a menor quantidade no outro sprite. Portanto, se ele estiver mais no eixo Y do que no X, ele será movido de volta ao longo do eixo X.

No entanto, agora meu sprite (herói) está caindo a uma taxa de 30 pixels por quadro (é uma tela de 1504 de altura - eu preciso que caia tão rápido quanto eu quero tentar simular a gravidade 'normal', qualquer velocidade mais lenta parece estranha ) Estou recebendo esses problemas. Vou tentar mostrar o que está acontecendo (e o que acho que está causando isso - embora não tenha certeza) com algumas fotos: (Código abaixo das fotos).

Gostaria de receber algumas sugestões sobre como contornar esse problema.

insira a descrição da imagem aqui

Para esclarecimento, na foto acima, quando digo que a posição está corrigida 'incorretamente', isso talvez seja um pouco enganador. O código em si está funcionando corretamente para como ele é escrito, ou dito de outra forma, o próprio algoritmo se comportando como eu esperaria, mas preciso alterar o comportamento para impedir que esse problema aconteça, espero que esclareça meus comentários na imagem .

insira a descrição da imagem aqui

My Code

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }
BungleBonce
fonte
Para elaborar, por que você move os sprites de volta para o outro eixo: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Tom 'Blue' Piddock
Oi @Blue, porque o sprite deve ser corrigido ao longo do eixo menos penetrante, por isso, se o sprite tiver penetrado mais a plataforma no eixo X do que o Y, é o Y que deve ser corrigido, pois é o menor dos dois.
BungleBonce 13/05
possível duplicata de Detecção de Colisão, correção jogador
Anko
Eu li essa pergunta antes da @Anko - ela não me dá as respostas que eu preciso. Daí eu compus minha própria pergunta (não convenceu o autor da questão está pedindo exatamente a mesma coisa, embora seja difícil ter certeza) :-)
BungleBonce

Respostas:

1

durante minhas experiências com a tela HTML5 e o AABB, encontrei exatamente o que você está enfrentando. Isso aconteceu quando tentei criar uma plataforma a partir de caixas adjacentes de 32x32 pixels.

Soluções que eu tentei por ordem de minha preferência pessoal

1 - Divida os movimentos por eixo

O meu atual, e acho que vou continuar meu jogo com ele. Mas não se esqueça de verificar o que chamo de Phantom AABB, se você precisar de uma solução rápida sem precisar modificar muito o seu design atual.

Meu mundo do jogo tem uma física muito simples. Ainda não há conceito de forças, apenas deslocamento. A gravidade é um deslocamento constante para baixo. Sem aceleração (ainda).

O deslocamento é aplicado por eixo. E nisso consiste esta primeira solução.

Cada quadro de jogo:

player.moves.y += gravity;
player.moves.x += move_x;

move_x é definido de acordo com o processamento de entrada, se não for pressionada a tecla de seta, move_x = 0. Valores abaixo de zero significam à esquerda, caso contrário, à direita.

Processe todos os outros sprites em movimento e decida os valores do componente "movimentos", eles também têm o componente "movimentos".

Nota: após a detecção e resposta da colisão, o componente de movimento deve ser definido como zero, ambas as propriedades x e y, porque no próximo tique, a gravidade será adicionada novamente. Se você não redefinir, você terminará com um movimento.y de duas vezes a gravidade desejada e, no próximo tick, será três vezes.

Em seguida, insira o código de aplicação de deslocamento e de detecção de colisão.

O componente de movimentos não é realmente a posição atual do sprite. O sprite ainda não foi movido, mesmo que o move.x ou y seja alterado. O componente "movimentos" é uma maneira de salvar o deslocamento a ser aplicado na posição do sprite na parte correta do ciclo do jogo.

Processe primeiro o eixo y (move.y). Aplique moves.y para sprite.position.y. Em seguida, aplique o método AABB para verificar se o sprite em movimento que está sendo processado se sobrepõe a uma plataforma AABB (você já entendeu como fazer isso). Se ele se sobrepõe, mova o sprite de volta ao eixo y aplicando penetration.y. Sim, ao contrário da minha outra resposta, agora este método de evolução evoluiu ignora a penetração.x, para esta etapa do processo. E usamos a penetração. Mesmo que seja maior que a penetração.x, nem estamos verificando.

Em seguida, processe o eixo x (move.x). Aplique moves.x em sprite.position.x. E faça o oposto do que antes. Desta vez, você moverá o sprite apenas no eixo horizontal aplicando penetration.x. Como antes, você não se importa se penetration.x for menor ou maior que penetration.y.

O conceito aqui é que, se o sprite não estiver se movendo e inicialmente não estiver colidindo, ele permanecerá assim no próximo quadro (sprite.moves.xe ey são zero). O caso especial em que outro objeto do jogo se teletransporta magicamente para uma posição que se sobrepõe ao sprite que começa a ser processado é algo que tratarei mais tarde.

Quando um sprite começa a se mover. Se ele estiver se movendo apenas no eixo x, então estamos interessados ​​apenas se ele penetrar em algo pela esquerda ou pela direita. Como o sprite não está se movendo para baixo, e sabemos disso porque a propriedade y do componente de movimentos é zero, nem verificamos o valor calculado para a penetração.y, vemos apenas em penetration.x. Se existe penetração no eixo do movimento, aplicamos a correção, caso contrário, simplesmente deixamos o sprite se mover.

E o deslocamento diagonal, não necessariamente em um ângulo de 45 graus?

O sistema proposto pode lidar com isso. Você só precisa processar cada eixo separadamente. Durante sua simulação. Diferentes forças são aplicadas ao sprite. Mesmo que seu motor ainda não entenda as forças. Essas forças se traduzem em um deslocamento arbitrário no plano 2D, esse deslocamento é um vetor 2D em qualquer direção e comprimento. A partir deste vetor, você pode extrair o eixo y e o eixo x.

O que fazer se o vetor de deslocamento for muito grande?

Você não quer isso:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

Como nossos novos movimentos de aplicação lógica processam um eixo primeiro e depois o outro, um deslocamento grande pode acabar sendo visto pelo código de colisão como um L grande, isso não detectará corretamente colisões com objetos que estão cruzando seu vetor de deslocamento.

A solução é dividir grandes deslocamentos em pequenos passos, idealmente a largura ou altura do sprite ou metade da largura ou altura do sprite. Para obter algo parecido com isto:

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

E quanto a teletransportar sprites?

Se você representar o teletransporte como um deslocamento grande, usando o mesmo componente de movimentos que um movimento normal, não há problema. Caso contrário, é necessário pensar em uma maneira de marcar o sprite de teletransporte a ser processado pelo código de colisão (adicionando a uma lista dedicada a esse objetivo?). Durante o código de resposta, você pode deslocar diretamente o sprite permanente que estava no a maneira como o teletransporte ocorreu.

2 - AABB fantasma

Tenha um AABB secundário para cada sprite afetado pela gravidade que comece no fundo do sprite, tenha a mesma largura do AABB do sprite e 1 pixel de altura.

A idéia aqui é que esse AABB fantasma esteja sempre colidindo com a plataforma abaixo do sprite. Somente quando esse AABB fantasma não se sobrepõe, é permitido que a gravidade seja aplicada ao sprite.

Você não precisa calcular o vetor de penetração para o AABB fantasma e a plataforma. Você está interessado apenas em um teste de colisão simples; se ele se sobrepõe a uma plataforma, a gravidade não pode ser aplicada. Seu sprite pode ter uma propriedade booleana que informa se está sobre uma plataforma ou no ar. Você está no ar quando o seu AABB fantasma não está colidindo com uma plataforma abaixo do player.

Este:

player.position.y += gravity;

Torna-se algo assim:

if (player.onGround == false) player.position.y += gravity;

Muito simples e confiável.

Você deixa sua implementação do AABB como está.

O AABB fantasma pode ter um loop dedicado que os processa, que apenas verifica colisões simples e não perde tempo calculando o vetor de penetração. Esse loop deve estar antes do código normal de colisão e resposta. Você pode aplicar a gravidade durante esse loop, pois os sprites no ar aplicam a gravidade. Se o AABB fantasma é em si uma classe ou estrutura, ele pode ter uma referência ao sprite ao qual pertence.

Isso é muito semelhante à outra solução proposta, na qual você verifica se o seu jogador está pulando. Estou dando uma maneira possível de detectar se o jogador está de pé sobre uma plataforma.

Hatoru Hansou
fonte
Eu gosto da solução Phantom AABB aqui. Eu já apliquei algo semelhante para contornar o problema. (Mas não usando um AABB fantasma) - Estou interessado em aprender mais sobre o seu método inicial acima, mas estou um pouco confuso com ele. O componente Moves.x (e .y) é basicamente a quantidade pela qual o sprite será movido (se for permitido mover-se)? Além disso, não estou claro como isso ajudaria diretamente na minha situação. Ficaria grato se você pudesse editar sua resposta para mostrar um gráfico, como isso poderia ajudar na minha situação (como no gráfico acima) - obrigado!
BungleBonce 15/05
Sobre o componente de movimentos. Representa deslocamento futuro, sim, mas você não verifica se permite ou não movimento, na verdade, aplica o movimento, pelo eixo, primeiro y, depois x, e depois vê se colidiu com alguma coisa, se usa a penetração vetor para voltar. Por que salvar deslocamento para processamento posterior difere de aplicar o deslocamento quando há entrada do usuário para processar ou algo aconteceu no mundo do jogo? Vou tentar colocar isso em imagens quando editar minha resposta. Enquanto isso, vamos ver se alguém vem com uma alternativa.
Hatoru Hansou
Ah, ok - acho que entendo o que você está dizendo agora. Portanto, em vez de mover x & y e, em seguida, verificar a colisão, com seu método, você move X primeiro e depois verifica a colisão, se ela colidir e, em seguida, corrigir ao longo do eixo X. Em seguida, mova Y e verifique a colisão; se houver uma colisão, mova-se ao longo do eixo X? Dessa forma, sempre sabemos o eixo correto do impacto? Eu entendi direito ?! Obrigado!
BungleBonce 16/05
Aviso: você escreveu "Em seguida, mova Y e verifique a colisão, se houver uma colisão, depois mova-se no X-Axis?" Seu eixo Y. Provavelmente um erro de digitação. Sim, essa é a minha abordagem atual. Para deslocamentos muito grandes, além da largura ou altura do sprite, é necessário dividir em etapas menores, e é isso que quero dizer com a analogia L. Cuidado para não aplicar um deslocamento grande no eixo y e, em seguida, um x grande, é necessário dividir em pequenas etapas e intercalar para obter o comportamento correto. Algo para se importar apenas se grandes deslocamentos forem permitidos no seu jogo. Na minha, eu sei que eles vão ocorrer. Então eu me preparei para isso.
Hatoru Hansou
Brilhante @HatoruHansou, nunca pensei em fazer as coisas dessa maneira, certamente tentarei - muito obrigado pela sua ajuda, vou marcar sua resposta como aceita. :-)
BungleBonce
5

Eu combinaria os blocos de plataforma em uma única entidade de plataforma.

Por exemplo, suponha que você pegue os três blocos das fotos e os combine em uma única entidade, sua entidade teria uma caixa de colisão de:

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

Usar isso para colisão fornecerá o y como o eixo menos penetrante na maior parte da plataforma e ainda permitirá que você tenha blocos específicos dentro da plataforma.

UnderscoreZero
fonte
Oi @UnderscoreZero - obrigado, este é um caminho a percorrer, a única coisa é que atualmente estou armazenando meus ladrilhos em 3 matrizes separadas, uma para ladrilhos da borda esquerda, para ladrilhos do meio e outra para ladrilhos da direita - acho que eu poderia combiná-los todos em uma matriz para fins de CD, apenas não sei como eu poderia escrever código para fazer isso.
BungleBonce 13/05
Outra opção que você pode tentar usar muitos motores de física é que, uma vez que o objeto pousa em um plano, eles desabilitam a gravidade do objeto para impedir que ele tente cair. Uma vez que o objeto recebe força suficiente para afastá-lo do avião ou cai do lado do avião, eles reativam a gravidade e realizam a física normalmente.
precisa
0

Problemas como esses são comuns com novos métodos de detecção de colisões.

Não sei muito sobre sua configuração de colisão atual, mas aqui está como deve ser:

Primeiro, faça estas variáveis:

  1. Faça uma velocidade X e Y
  2. Faça uma gravidade
  3. Faça um salto
  4. Faça um salto

Segundo, faça um cronômetro para calcular o delta para afetar a velocidade do objeto.

Terceiro, faça o seguinte:

  1. Verifique se o jogador está pressionando o botão de salto e não está pulando; caso contrário, adicione jumpSpeed ​​à velocidade Y.
  2. Subtraia a gravidade da velocidade Y a cada atualização para simular a gravidade
  3. Se o jogador y + altura for menor que ou igual ao y da plataforma, defina a velocidade Y como 0 e coloque o Y do jogador na posição y + da altura do jogador.

Se você fizer isso, não deverá haver nenhum problema. Espero que isto ajude!

LiquidFeline
fonte
Oi @ user1870398, obrigado, mas meu jogo nem sequer está pulando no momento, a questão relacionada aos problemas que surgem com o uso do método do 'eixo menos penetrante' para corrigir a posição do sprite. O problema é que a gravidade está puxando meu personagem mais para dentro das minhas plataformas ao longo do eixo Y do que sobrepõe-se ao eixo X. Portanto, minha rotina (como está correta neste tipo de algoritmo) está corrigindo o eixo X, portanto, não posso mova-se pela plataforma (como a rotina do CD vê os blocos individuais e os trata como tal, mesmo que eles sejam apresentados como uma plataforma).
BungleBonce 13/05
Eu entendi o seu problema. Só que você não está corrigindo corretamente o seu movimento. Você precisa de uma variável isJumping . Se você pressionar o botão de pular e não estiver pulando, você o definirá como verdadeiro; se atingir o chão, definirá como falso; mova o Y do jogador para a posição correta ( platform.y + player.height ) e defina isJumping como false. Em seguida, logo após verificar essas coisas, faça se (left_key) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) . Eu sei que você ainda não implementou o salto, então simplesmente sempre isJumping está definido como falso.
LiquidFeline
Eu não tenho salto, mas se uma variável 'ifJumping' é sempre definida como false, como isso é diferente de não ter um? (digamos em jogos onde não há salto) Estou apenas um pouco confuso sobre como um booleano 'isjumping' poderia ajudar na minha situação - ficaria agradecido se você pudesse explicar um pouco mais - obrigado!
BungleBonce
Não é necessário. É melhor implementá-lo enquanto você ainda o está programando. De qualquer forma, o motivo do isJumping é organizar seu código e evitar problemas, apenas adicionando gravidade enquanto o jogador está pulando. Basta fazer o que eu recomendei. Este método é o que a maioria das pessoas usa. Até o Minecraft usa! É assim que a gravidade é efetivamente simulada! :)
LiquidFeline