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.
fonte
Respostas:
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
onGround
propriedade é 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:
para isso:
(Também modifiquei algumas das constantes relacionadas à física, mas isso não deve importar.)
Talvez classificar
bodiesToCheck
no 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.fonte
Position = nextPosition
imediatamente dentro dofor
loop, caso contrário, as resoluções de colisão indesejadas (configuraçãoonGround
) ainda ocorrerão. O jogador deve ser empurrado verticalmente para baixo (e nunca para cima) ao atingir o teto, portantoonGround
, nunca deve ser colocado. É assim que a demonstração do XNA faz isso, e não consigo reproduzir o bug do "teto" lá.onGround
está definido, por isso não posso investigar por que o salto é permitido incorretamente. A demonstração XNA atualiza suaisOnGround
propriedade análoga por dentroHandleCollisions()
. Além disso, depois de definirVelocity.Y
0, por que a gravidade não começa a mover o player novamente? Meu palpite é que aonGround
propriedade está configurada incorretamente. Veja como a demonstração do XNA é atualizadapreviousBottom
eisOnGround
(IsOnGround
).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:
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:
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.
fonte
if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
Editar
Eu melhorei
Parece que a principal coisa que mudei é a classificação dos cruzamentos.
As interseções são:
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 )
Mas isso NÃO é uma colisão no solo e o jogador "escorregará" no lado direito do ladrilho quando ele pousar nele
Por esse método, o jogador não será mais pego nas laterais das paredes
fonte
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))
fonte
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
fonte
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.
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:
(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.
fonte
É 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, é:
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
moveX
emoveY
funçõ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.
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.
fonte
moveY
nã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.