Como evito que o personagem do meu jogo de plataformas se prenda nos azulejos?

9

Atualmente, tenho um jogo de plataformas com ladrilhos para o terreno (gráficos emprestados do Cave Story). O jogo foi escrito do zero usando XNA, então não estou usando um mecanismo existente ou físico.

As colisões de lado a lado são descritas exatamente como descrito nesta resposta (com SAT simples para retângulos e círculos), e tudo funciona bem.

Exceto quando o jogador colide com uma parede enquanto cai / pula. Nesse caso, eles pegam um ladrilho e começam a pensar que atingiram um piso ou teto que não está realmente lá.

Imagem da captura de personagem

Nesta captura de tela, o jogador está se movendo para a direita e caindo para baixo. Então, após o movimento, as colisões são verificadas - e primeiro, o personagem do jogador está colidindo com a peça 3 do chão e empurrada para cima. Segundo, ele descobriu que estava colidindo com o ladrilho ao lado dele e empurrado para o lado - o resultado final é que o personagem do jogador pensa que está no chão e não está caindo, e 'pega' o ladrilho enquanto ele o encontra. .

Eu poderia resolver isso definindo os ladrilhos de cima para baixo, o que o faz cair suavemente, mas o caso inverso acontece e ele atinge um teto que não existe quando se pula contra a parede.

Como devo abordar a resolução disso, para que o personagem do jogador possa cair ao longo da parede como deveria?

doppelgreener
fonte
Semelhante a esta pergunta? gamedev.stackexchange.com/questions/14486/…
MichaelHouse
@ Byte56 A pergunta não é a mesma, mas sua resposta pode ajudar.
Doppelgreener
Uma observação: tive muito pouco tempo nas últimas duas semanas para experimentar as respostas aqui. Implementei a resolução de colisões mundiais conforme respondida na pergunta @ Byte56 vinculada, que possui bugs separados em altas velocidades e ainda não foi possível testar as respostas para essa pergunta. Vou tentar quando puder e aceitar uma resposta quando puder, ou postar minha própria, se eu resolver a questão.
doppelgreener

Respostas:

8

A abordagem mais simples e à prova de falhas é simplesmente não verificar colisões nas arestas ocultas. Se você tiver dois ladrilhos de parede, um diretamente acima do outro, a borda inferior do ladrilho superior e a borda superior do ladrilho inferior não devem ser verificadas quanto à colisão contra o jogador.

Você pode determinar quais arestas são válidas durante uma passagem simples sobre o mapa de blocos, armazenando arestas externas como sinalizadores em cada local de bloco. Você também pode fazer isso em tempo de execução verificando blocos adjacentes durante a detecção de colisão.

Existem versões mais avançadas dessa mesma técnica que facilitam e agilizam o suporte a blocos não quadrados também.

Eu recomendo que você leia os artigos abaixo. São apresentações simples decentes para fazer física básica e detecção de colisão em plataformas modernas (como Cave Story). Se você quer um Mario mais antigo, há mais alguns truques com os quais se preocupar, mas a maioria envolve lidar com saltos e animações, em vez de colisão.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

Sean Middleditch
fonte
Você pode esclarecer como ignorar algumas arestas durante a colisão? Normalmente, vejo colisões sendo detectadas como a interseção de dois retângulos (os limites do jogador e os limites do ladrilho). Quando você diz para não verificar a colisão com arestas individuais, isso significa que o algoritmo de colisão não é uma interseção retângulo-retângulo, mas outra coisa? Sua descrição faz sentido, mas não tenho certeza de como seria a implementação.
David Gouveia
Sim, basta fazer uma colisão de retângulo. Simplificado apenas porque a linha está sempre alinhada ao eixo. Você pode simplesmente decompor sua caixa de seleção.
Sean Middleditch
5

Aqui a ordem que eu faria as coisas ....

1) Salvar atual X, Y do caractere

2) Mova a direção X

3) Verifique todos os cantos do caractere, obtendo os dados do bloco e verifique se são sólidos, fazendo o seguinte: (X e Y são a posição do caractere)

  • para o canto superior esquerdo: piso (X / tileWidth), piso (Y / tileHeight);
  • no canto superior direito: floor ((X + characterWidth) / tileWidth), floor (Y / tileHeight);
  • para o canto inferior esquerdo: floor (X + / tileWidth), floor ((Y + characterHeight) / tileHeight);
  • no canto inferior direito: floor ((X + characterWidth) / tileWidth), floor ((Y + characterHeight) / tileHeight);

... para obter dados corretos do bloco a partir dos dados do mapa

4) Se qualquer um dos blocos for sólido, será necessário forçar X para a melhor posição possível restaurando o X salvo e ...

  • se você estiver se movendo para a esquerda: X = piso (X / tileWidth) * tileWidth;
  • se você estiver se movendo para a direita: X = ((piso ((X + largura do caractere) / largura do caractere) + 1) * largura do caractere) + 1) * largura do mosaico) - largura do caractere;

5) Repita 2-4 usando Y

Se seu personagem for mais alto que um ladrilho, você precisará verificar mais ladrilhos. Para fazer isso, você precisa fazer um loop e adicionar tileHeight à posição characterY para obter o tile

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Se o caractere for maior que 1 bloco, faça o mesmo, mas substitua characterY, characterHeight, tileHeight, etc por characterX, characterWidth, etc

John
fonte
2

E se você verificasse a possibilidade de uma colisão como parte do método de movimento do jogador e rejeitasse o movimento que fazia o jogador deslizar para outro objeto? Isso impediria o jogador de "entrar" em um bloco e, portanto, ele não poderia estar "em cima" do bloco abaixo dele.

SomeGuy
fonte
1

Encontrei quase exatamente o mesmo problema em um jogo em que estou trabalhando atualmente. A maneira como resolvi foi armazenar a área da parte sobreposta ao testar colisões e, em seguida, lidar com essas colisões com base em sua prioridade (maior área primeiro), depois verificar duas vezes cada colisão para ver se ela já havia sido resolvida por uma anterior. tratamento.

No seu exemplo, a área da colisão do eixo x seria maior que o eixo y, portanto seria tratada primeiro. Antes de tentar lidar com a colisão do eixo y, ele verificaria e não veria mais colisão após ser movido no eixo x. :)

Nick Badal
fonte
1

Uma solução simples, mas não perfeita, é alterar a forma da caixa de colisão do jogador para que não fique quadrada. Corte os cantos para torná-lo semelhante ao octógono ou algo semelhante. Dessa forma, quando ele colide com um azulejo como os da parede, ele desliza para fora e não é pego. Isso não é perfeito, porque você ainda pode notá-lo pegando um pouco nas curvas, mas ele não fica preso e é muito fácil de implementar.

Amplify91
fonte
1

Um problema muito semelhante foi abordado neste tutorial: Introdução ao desenvolvimento de jogos . A parte relevante do vídeo é às 1:44 , explicando com diagramas e trechos de código.

O aviso 14-tilemap.py exibe o problema de recorte, mas é corrigido em 15-blocker-sides.py

Não sei explicar exatamente por que o último exemplo de tutorial não é cortado enquanto o código é, mas acho que tem algo a ver com:

  • Resposta de colisão condicional com base no tipo de bloco (quais arestas estão realmente ativas).
  • O jogador está sempre 'caindo'. Embora o jogador pareça estar apoiado em um ladrilho, na verdade ele está caindo repetidamente no ladrilho e sendo empurrado de volta para o topo. (Oself.resting membro no código é usado apenas para verificar se o jogador pode pular. Apesar disso, não consigo fazer um pulo 'duplo' de uma parede. Teoricamente, porém, é possível)
Leftium
fonte
0

Outro método (ao qual eu respondi a pergunta relacionada ao Byte56) seria verificar se a resolução de colisão coloca o personagem em um local vazio ou não. Portanto, no seu problema, você colide com o teto do retângulo interno para movê-lo para cima, o que seria ilegal (pois você ainda está em colisão com outro bloco). Em vez disso, você só o move se for movido para um espaço livre (como a colisão do bloco superior o moveria para a esquerda) e, depois de encontrar essa colisão, estará pronto.

A resposta que eu dei tinha código, mas ficou muito complicado. Acompanharia todas as colisões dentro desse prazo e levaria apenas a que levasse a um espaço livre. No entanto, se não houvesse, acho que tentei redefinir a posição do personagem para sua última posição, no entanto, isso é realmente feio e talvez você prefira implementar algo como o Mario original, onde ele apenas se move em uma direção ou algo quando não há resolução de espaço livre é possível. Além disso, você pode classificar a lista de resoluções de colisão e mover apenas para o espaço livre com a menor distância (acho que a solução seria a mais preferível, embora não tenha codificado para isso).

Jeff
fonte
0

Eu só fiz3 SAT em 3D, então talvez eu não esteja entendendo direito. Se eu entendi o seu problema, isso pode ter ocorrido devido ao contato com um eixo de contato que não deveria ser possível, resultando no jogador agindo como se houvesse um piso. Uma solução alternativa talvez seja usar uma forma de círculo para o seu personagem, então você só terá eixos de contato na direção do eixo x ao bater em uma parede.

Mikael Högström
fonte