Problemas de colisão 2D Platformer AABB

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

Estou com um problema com a resolução de colisão AABB.


Eu resolvo a interseção AABB resolvendo primeiro o eixo X, depois o eixo Y. Isso é feito para evitar esse bug: http://i.stack.imgur.com/NLg4j.png


O método atual funciona bem quando um objeto se move para dentro do player e ele precisa ser empurrado horizontalmente. Como você pode ver no gif, os picos horizontais empurram o player corretamente.


Quando os picos verticais se movem para o player, no entanto, o eixo X ainda é resolvido primeiro. Isso torna impossível "usar os espigões como um elevador".

Quando o jogador se move para os picos verticais (afetados pela gravidade, cai neles), ele é empurrado no eixo Y, porque não havia sobreposição no eixo X para começar.


Algo que tentei foi o método descrito na primeira resposta deste link: detecção de colisão retangular de objetos em 2D

No entanto, os picos e objetos em movimento se movem alterando sua posição, não a velocidade, e eu não calculo sua próxima posição prevista até que o método Update () seja chamado. Escusado será dizer que esta solução também não funcionou. :(


Preciso resolver a colisão da AABB de uma maneira que os dois casos descritos acima funcionem como pretendido.

Este é o meu código-fonte de colisão atual: http://pastebin.com/MiCi3nA1

Ficaria muito grato se alguém pudesse investigar isso, já que esse bug está presente no mecanismo desde o início, e tenho lutado para encontrar uma boa solução, sem nenhum sucesso. Isso está me fazendo passar noites olhando o código de colisão e me impedindo de chegar à "parte divertida" e codificando a lógica do jogo :(


Tentei implementar o mesmo sistema de colisão da demonstração do XNA AppHub para plataformas (copiando e colando a maioria das coisas). No entanto, o erro de "salto" ocorre no meu jogo, enquanto não ocorre na demonstração do AppHub. [bug saltitante: http://i.stack.imgur.com/NLg4j.png ]

Para pular, verifico se o player está "onGround" e adiciono -5 ao Velocity.Y.

Como o Velocity.X do jogador é maior que o Velocity.Y (consulte o quarto painel do diagrama), onGround é definido como true quando não deveria ser e, portanto, permite que o jogador pule no ar.

Acredito que isso não aconteça na demonstração do AppHub porque o Velocity.X do jogador nunca será maior que o Velocity.Y, mas posso estar enganado.

Eu resolvi isso antes, resolvendo primeiro o eixo X, depois o eixo Y. Mas isso estraga a colisão com os espinhos, como afirmei acima.

Vittorio Romeo
fonte
5
Na verdade, não há nada dando errado. O método que estou usando pressiona primeiro o eixo X, depois o eixo Y. É por isso que o jogador é empurrado horizontalmente, mesmo nos picos verticais. Preciso encontrar uma solução que evite o "problema de pular" e empurre o jogador no eixo raso (o eixo com menor penetração), mas não consigo encontrar nenhuma.
Vittorio Romeo
11
Você pode detectar em qual face da obstrução o jogador está tocando e resolvê-la
Ioachim
4
@ John Hobbs, leia a pergunta. Vee sabe exatamente o que seu código está fazendo, mas não sabe como resolver um determinado problema. Percorrer o código não o ajudará nessa situação.
AttackingHobo
4
@Maik @Jonathan: Nada está "dando errado" com o programa - ele entende exatamente onde e por que seu algoritmo não faz o que ele quer. Ele simplesmente não sabe como alterar o algoritmo para fazer o que quer. Portanto, o depurador não é útil neste caso.
BlueRaja - Danny Pflughoeft
11
@AttackingHobo @BlueRaja Concordo e excluí meu comentário. Fui eu que tirei conclusões sobre o que estava acontecendo. Peço desculpas por fazer com que você explique isso e por desviar pelo menos uma pessoa. Honestamente, você pode contar comigo para realmente absorver adequadamente a pergunta antes que eu deixe qualquer resposta na próxima vez.
doppelgreener

Respostas:

9

OK, eu descobri por que a demonstração do XNA AppHub de plataformas não tem o bug de "pular": a demonstração testa os blocos de colisão de cima para baixo . Quando estiver contra uma "parede", o jogador pode estar sobrepondo várias peças. A ordem de resolução é importante porque resolver uma colisão também pode resolver outras colisões (mas em uma direção diferente). A onGroundpropriedade é definida apenas quando a colisão é resolvida empurrando o player para cima no eixo y. Esta resolução não ocorrerá se as resoluções anteriores empurrarem o player para baixo e / ou horizontalmente.

Consegui reproduzir o bug "saltando" na demonstração do XNA alterando esta linha:

for (int y = topTile; y <= bottomTile; ++y)

para isso:

for (int y = bottomTile; y >= topTile; --y)

(Também modifiquei algumas das constantes relacionadas à física, mas isso não deve importar.)

Talvez classificar bodiesToCheckno eixo y antes de resolver as colisões no seu jogo conserte o erro de "pular". Sugiro resolver a colisão no eixo de penetração "raso", como faz a demonstração do XNA e Trevor sugere . Observe também que o reprodutor de demonstração XNA tem o dobro da altura dos blocos capazes de colidir, tornando mais provável o caso de colisão múltipla.

Leftium
fonte
Isso parece interessante ... Você acha que ordenar tudo pela coordenada Y antes de detectar colisões poderia resolver todos os meus problemas? Existe algum problema?
Vittorio Romeo
Aaaand eu encontrei o "catch". Usando esse método, o erro de salto é corrigido, mas se eu definir Velocity.Y como 0 após atingir o teto, ocorrerá novamente.
Vittorio Romeo
@Vee: defina Position = nextPositionimediatamente dentro do forloop, caso contrário, as resoluções de colisão indesejadas (configuração onGround) ainda ocorrerão. O jogador deve ser empurrado verticalmente para baixo (e nunca para cima) ao atingir o teto, portanto onGround, nunca deve ser colocado. É assim que a demonstração do XNA faz isso, e não consigo reproduzir o bug do "teto" lá.
Leftium
pastebin.com/TLrQPeBU - este é o meu código: na verdade, trabalho na própria posição, sem usar uma variável "nextPosition". Ele deve funcionar como na demonstração do XNA, mas se eu mantiver a linha que define Velocity.Y como 0 (linha 57) descomentada, ocorre o erro de salto. Se eu removê-lo, o jogador continua flutuando ao pular no teto. Isso pode ser corrigido?
Vittorio Romeo
@ Ve: o seu código não mostra como onGroundestá definido, por isso não posso investigar por que o salto é permitido incorretamente. A demonstração XNA atualiza sua isOnGroundpropriedade análoga por dentro HandleCollisions(). Além disso, depois de definir Velocity.Y0, por que a gravidade não começa a mover o player novamente? Meu palpite é que a onGroundpropriedade está configurada incorretamente. Veja como a demonstração do XNA é atualizada previousBottome isOnGround( IsOnGround).
Leftium
9

A solução mais simples para você será verificar as duas direções de colisão em relação a todos os objetos do mundo antes de resolver qualquer colisão e apenas resolver a menor das duas "colisões compostas" resultantes. Isso significa que você resolve pela menor quantidade possível, em vez de sempre resolver x primeiro ou sempre resolver y primeiro.

Seu código seria algo parecido com isto:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

grande revisão : Ao ler comentários sobre outras respostas, acho que finalmente notei uma suposição não declarada, que fará com que essa abordagem não funcione (e explica por que não consegui entender os problemas que alguns - mas não todos - as pessoas viram com essa abordagem). Para elaborar, aqui está um pouco mais de pseudocódigo, mostrando de forma mais explícita o que as funções que eu referenciei antes deveriam realmente estar fazendo:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

Este não é um teste "por par de objetos"; não funciona testando e resolvendo o objeto em movimento em relação a cada bloco de um mapa do mundo individualmente (essa abordagem nunca funcionará de maneira confiável e falha de maneiras cada vez mais catastróficas à medida que o tamanho dos blocos diminui). Em vez disso, está testando o objeto em movimento contra todos os objetos no mapa do mundo simultaneamente e depois resolvendo com base em colisões contra o mapa do mundo inteiro.

É assim que você garante que (por exemplo) ladrilhos individuais dentro de uma parede nunca rebatem o jogador para cima e para baixo entre dois ladrilhos adjacentes, resultando em um jogador preso em algum espaço inexistente 'entre' eles; as distâncias da resolução de colisão são sempre calculadas até o espaço vazio no mundo, não apenas para o limite de um único bloco que pode ter outro bloco sólido em cima dele.

Trevor Powell
fonte
11
i.stack.imgur.com/NLg4j.png - isso acontece se eu usar o método acima.
Vittorio Romeo
11
Talvez eu não esteja entendendo seu código corretamente, mas deixe-me explicar o problema no diagrama: como o player é mais rápido no eixo X, a penetração de X é> maior que a penetração de Y. A colisão escolhe o eixo Y (porque a penetração é menor) e empurra o jogador na vertical. Isso não ocorre se o player for lento o suficiente para que a penetração em Y seja sempre> maior que a penetração em X.
Vittorio Romeo
11
@Vee A qual painel do diagrama você está se referindo, aqui, como dando um comportamento incorreto usando essa abordagem? Estou assumindo o painel 5, no qual o jogador está claramente penetrando menos em X do que em Y, o que resultaria em ser empurrado ao longo do eixo X - que é o comportamento correto. Você só obtém o mau comportamento se estiver selecionando um eixo para resolução com base na velocidade (como na sua imagem) e não com base na penetração real (como sugeri).
Trevor Powell
11
@ Trevor: Ah, entendo o que você está dizendo. Nesse caso, você poderia simplesmente fazer isso #if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja # Danny Pflughoeft
11
@BlueRaja Ótimo ponto. Editei minha resposta para usar menos condicionais, como você sugere. Obrigado!
Trevor Powell
6

Editar

Eu melhorei

Parece que a principal coisa que mudei é a classificação dos cruzamentos.

As interseções são:

  • Solavancos de teto
  • Acertos no solo (empurre para fora do chão)
  • Colisões de paredes no ar
  • Colisões na parede aterrada

e eu os resolvo nessa ordem

Defina uma colisão no chão como o jogador sendo pelo menos 1/4 de maneira no ladrilho

Portanto, essa é uma colisão no solo e o jogador ( azul ) ficará em cima do ladrilho ( preto ) colisão no solo

Mas isso NÃO é uma colisão no solo e o jogador "escorregará" no lado direito do ladrilho quando ele pousar nele não vai ser um jogador de colisão no solo vai escorregar

Por esse método, o jogador não será mais pego nas laterais das paredes

bobobobo
fonte
Eu tentei o seu método (consulte minha implementação: pastebin.com/HarnNnnp ), mas não sei onde definir a velocidade do player como 0. Tentei configurá-lo como 0 se encrY <= 0, mas isso permite que o player pare no ar enquanto desliza ao longo de uma parede e também permite que ele pule repetidamente na parede.
Vittorio Romeo
No entanto, isso funciona corretamente quando o jogador interage com os picos (como mostrado no .gif). Eu realmente apreciaria se você pudesse me ajudar a resolver o problema de pular paredes (também ficar preso nas paredes). Lembre-se de que minhas paredes são feitas de vários ladrilhos AABB.
Vittorio Romeo
Desculpe por postar outro comentário, mas, após copiar e colar seu código em um projeto novo, alterar sp para 5 e fazer com que os blocos despejem em forma de parede, o bug ocorre (consulte este diagrama: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo
Ok, tente agora.
bobobobo
+1 para trabalhar amostra de código (faltando na horizontal movendo blocos de "pico", embora :)
Leftium
3

Nota:
Meu mecanismo usa uma classe de detecção de colisão que verifica colisões com todos os objetos em tempo real, apenas quando um objeto se move e apenas nos quadrados da grade que ele ocupa atualmente.

Minha solução:

Quando me deparei com problemas como este no meu jogo de plataformas 2D, fiz algo como o seguinte:

- (o mecanismo detecta possíveis colisões, rapidamente)
- (o jogo informa o mecanismo para verificar colisões exatas para um objeto em particular) [retorna o vetor do objeto *]
- (o objeto observa a profundidade da penetração, bem como a posição anterior em relação à posição anterior de outro objeto, para determinar de que lado deslizar)
- (o objeto se move e calcula a média da velocidade com a velocidade do objeto (se o objeto estiver em movimento))

ultifinito
fonte
"(o objeto analisa a profundidade da penetração, bem como a posição anterior em relação à posição anterior de outro objeto, para determinar de que lado deslizar)" "É nessa parte que estou interessado. Você pode elaborar? É bem-sucedido na situação mostrada pelo .gif e pelo diagrama?
Vittorio Romeo
Eu ainda tenho que encontrar uma situação (excepto altas velocidades) que este método não funciona no.
ultifinitus
Parece bom, mas você pode realmente explicar como resolve as penetrações? Você resolve sempre no menor eixo? Você verifica o lado da colisão?
Vittorio Romeo
De fato, o eixo menor não é um bom caminho (primário) a seguir. NA MINHA HUMILDE OPINIÃO. Geralmente resolvo com base em qual lado estava anteriormente fora do outro objeto, é o mais comum. Quando isso falha, eu verifico com base na velocidade e profundidade.
Ultifinitus
2

Nos videogames que programei, a abordagem era ter uma função informando se sua posição é válida, ou seja. bool ValidPos (int x, int y, int wi, int ele);

A função verifica a caixa delimitadora (x, y, wi, he) em relação a toda geometria no jogo e retorna false se houver alguma interseção.

Se você quiser se mover, diga à direita, tome a posição do jogador, adicione 4 a xe verifique se a posição é válida; caso contrário, verifique com + 3, + 2 etc. até que seja.

Se você também precisa ter gravidade, precisa de uma variável que cresça enquanto não atingir o chão (atingindo o chão: ValidPos (x, y + 1, wi, he) == verdadeiro, y é positivo aqui em baixo). se você pode mover essa distância (ou seja, ValidPos (x, y + gravidade, wi, ele) retorna verdadeiro), você está caindo, útil às vezes quando não deve ser capaz de controlar seu personagem ao cair.

Agora, seu problema é que você tem objetos em seu mundo que se movem, então primeiro você precisa verificar se sua antiga posição ainda é válida!

Caso contrário, você precisa encontrar uma posição que seja. Se os objetos no jogo não puderem se mover mais rápido do que 2 pixels por rotação do jogo, você precisará verificar se a posição (x, y-1) é válida, (x, y-2) e (x + 1, y) etc. etc. todo o espaço entre (x-2, y-2) e (x + 2, y + 2) deve ser verificado. Se não houver uma posição válida, significa que você foi "esmagado".

HTH

Valmond

Valmond
fonte
Eu costumava usar essa abordagem nos meus dias de Game Maker. Posso pensar em maneiras de acelerá-lo / melhorá-lo com pesquisas melhores (digamos, uma pesquisa binária no espaço que você percorreu, embora dependendo da distância que você percorreu e da geometria, talvez seja mais lenta), mas a principal desvantagem dessa abordagem é que, em vez de fazer um teste contra todas as colisões possíveis, você está fazendo o que quer que sua mudança de posição seja verificações.
9111 Jeff
"você está fazendo o que quer que seja sua mudança de posição" Desculpe, mas o que exatamente isso significa? É claro que você usará técnicas de particionamento de espaço (BSP, Octree, quadtree, Tilemap etc.) para acelerar o jogo, mas você sempre precisará fazer isso de qualquer maneira (se o mapa for grande), a questão não é sobre isso, mas sobre o algoritmo usado para mover (corretamente) o jogador.
Valmond
@ Valmond Acho que @ Jeff significa que, se o seu jogador estiver movendo 10 pixels para a esquerda, poderá ter até 10 detecções de colisão diferentes.
11137 Jonathan Connell
Se é isso que ele está querendo dizer, então ele está certo e deve ser assim (quem pode saber se ficamos parados em +6 e não +7?). Os computadores são rápidos, usei isso no S40 (~ 200mhz e um kvm não tão rápido) e funcionou como um encanto. Você sempre pode tentar otimizar o algo inicial, mas isso sempre fornecerá casos de canto como o que o OP tem.
11309 Valmond
Foi exatamente o que eu quis dizer
Jeff
2

Tenho algumas perguntas antes de começar a responder. Primeiro, no bug original em que você ficou preso nas paredes, esses ladrilhos nos ladrilhos individuais esquerdos eram opostos a um ladrilho grande? E se eles estavam, o jogador estava preso entre eles? Se sim a ambas as perguntas, verifique se a sua nova posição é válida . Isso significa que você terá que verificar se há uma colisão para onde está dizendo ao jogador para se mover. Resolva o deslocamento mínimo, conforme descrito abaixo, e mova seu jogador com base nisso apenas se ele pudervá para lá. Quase sob o nariz: P Isso realmente introduzirá outro bug, que eu chamo de "casos de canto". Essencialmente, em termos de cantos (como o canto inferior esquerdo, onde os picos horizontais saem no seu .gif, mas se não houvesse picos) não resolveria uma colisão, porque pensaria que nenhuma das resoluções geradas leva a uma posição válida . Para resolver isso, basta manter um boletim se a colisão foi resolvida, bem como uma lista de todas as resoluções mínimas de penetração. Posteriormente, se a colisão não tiver sido resolvida, faça um loop em todas as resoluções que você gerou e acompanhe as resoluções X e Y máximas (os máximos não precisam ter a mesma resolução). Em seguida, resolva a colisão com esses valores máximos. Isso parece resolver todos os seus problemas e os que encontrei.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Outra pergunta, são os picos que você mostra um bloco ou blocos individuais? Se forem ladrilhos finos individuais, talvez seja necessário usar uma abordagem diferente para os horizontais e verticais do que eu descrevo abaixo. Mas se eles são peças inteiras, isso deve funcionar.


Tudo bem, então basicamente é isso que @Trevor Powell estava descrevendo. Como você está usando apenas AABBs, tudo o que você precisa fazer é descobrir quanto um retângulo penetra no outro. Isso fornecerá uma quantidade no eixo X e no Y. Escolha o mínimo dentre os dois e mova seu objeto colidindo ao longo desse eixo nessa quantidade. É tudo o que você precisa para resolver uma colisão AABB. Você NUNCA precisará se mover ao longo de mais de um eixo em tal colisão, para nunca se confundir com o que se mover primeiro, pois você só moverá o mínimo.

O software Metanet possui um tutorial clássico sobre uma abordagem aqui . Também entra em outras formas.

Aqui está uma função XNA que eu fiz para encontrar o vetor de sobreposição de dois retângulos:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Espero que seja simples de seguir, porque tenho certeza de que existem maneiras melhores de implementá-lo ...)

isCollision (Rectangle) é literalmente apenas uma chamada para Rectangle.Intersects (Rectangle) do XNA.

Eu testei isso com plataformas móveis e parece funcionar bem. Farei mais alguns testes mais parecidos com o seu .gif para ter certeza e informar se não funcionar.


Jeff
fonte
Sou meio cético em relação ao comentário que fiz sobre ele não funcionar com plataformas finas. Não consigo pensar em uma razão pela qual isso não aconteceria. Também se você quiser fonte, eu posso fazer upload de um projeto XNA para você
Jeff
Sim, eu estava testando isso um pouco mais e isso remonta ao seu mesmo problema de ficar preso na parede. É porque, onde os dois blocos se alinham, ele pede para você subir por causa do menor e depois sair (exatamente no seu caso) para o superior, negando efetivamente o movimento de gravidade se sua velocidade y for lenta o suficiente . Vou ter que procurar uma solução para isso, parece que me lembro de ter esse problema antes.
91111 Jeff
Ok, criei uma maneira extremamente hacky de contornar o seu primeiro problema. A solução envolve encontrar a penetração mínima para cada AABB, resolvendo apenas aquela com o maior X e, em seguida, uma segunda passagem resolvendo apenas a maior Y. Parece ruim quando você está sendo esmagado, mas aumenta o trabalho e pode ser pressionado horizontalmente e seu problema de aderência original desapareceu. É hackey, e não tenho idéia de como isso se traduziria para outras formas. Outra possibilidade pode ser a de soldar as geometrias tocar, mas eu nem sequer tentaram que
Jeff
As paredes são feitas de azulejos 16x16. Os picos são um bloco de 16x16. Se eu resolver no eixo mínimo, o erro de salto ocorre (veja o diagrama). Sua solução "extremamente hacky" funcionaria com a situação mostrada no arquivo .gif?
Vittorio Romeo
Sim, trabalha no meu. Qual é o tamanho do seu personagem?
91111 Jeff
1

É simples.

Reescreva os picos. A culpa é dos espinhos.

Suas colisões devem ocorrer em unidades tangíveis e discretas. O problema, tanto quanto eu posso ver, é:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

O problema está na etapa 2, não na 3 !!

Se você estiver tentando fazer com que as coisas pareçam sólidas, não deixe que os itens se encaixem assim. Quando você estiver em uma posição de interseção, se perder o seu lugar, o problema se torna mais difícil de resolver.

Idealmente, os picos devem verificar a existência do jogador e, quando eles se movem, devem empurrá- lo conforme necessário.

Uma maneira fácil de conseguir isso é para o jogador de ter moveXe moveYfunções que compreendem a paisagem e vai empurrar o jogador por um determinado delta ou tão longe quanto possível sem bater um obstáculo .

Normalmente eles serão chamados pelo loop de eventos. No entanto, eles também podem ser chamados por objetos para empurrar o jogador.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Obviamente, o jogador ainda precisa reagir aos picos se ele vai para eles .

A regra geral é que, depois que qualquer objeto se move, ele deve terminar em uma posição sem interseção. Dessa forma, é impossível obter as falhas que você está vendo.

Chris Burt-Brown
fonte
no caso extremo em que moveYnão é possível remover o cruzamento (porque outra parede está no caminho), você pode verificar novamente o cruzamento e tentar o moveX ou apenas matá-lo.
22811 Chris Burt-Brown