Construindo um Plattformer - Como determinar se um jogador pode pular?

16

Estou criando um jogo simples do Plattformer Jump n 'Run Style. Eu não uso peças - em vez disso, tenho formas geométricas para minhas entidades de nível (e o jogador também é um). Terminei meu código de detecção de colisão e tudo funciona bem até agora.

Em seguida, eu queria implementar o salto. Basta verificar se o jogador pressiona a tecla apropriada e adicionar alguma velocidade para cima. Funciona bem. Mas funciona mesmo que o jogador esteja no ar, o que não é o que eu quero. ;-)

Então, eu tenho que verificar se o jogador está em alguma coisa. Minha primeira idéia foi verificar se houve uma colisão no último quadro e marcar o jogador como "capaz de pular", mas isso seria acionado se o jogador atingisse uma parede no ar. Como minhas habilidades matemáticas não são tão boas, peço ajuda - até dicas sugeririam como implementar isso.

Obrigado!

Malax
fonte

Respostas:

14

Duas opções vêm à mente:

  • O primeiro pensamento é marcar a geometria com um ID e depois verificar se a colisão está com a geometria marcada como piso. Isso oferece o máximo controle de superfícies saltáveis, mas a um custo no tempo de criação do nível.
  • O segundo é verificar o normal da colisão, se estiver apontando para cima, permita o salto. Ou dentro de uma margem superior, depende se você possui pisos inclinados. Isso é flexível e não requer marcação, mas se você tiver pisos e paredes inclinados, poderá saltar onde não desejar.
wkerslake
fonte
A verificação normal parece ser uma ótima idéia para mim. Obrigado por postar isso.
Christopher Horenstein 26/10/10
+1 para verificação normal. Isso garante que um piso possa passar facilmente para uma parede sem causar nenhum problema.
Soviut 26/10/10
1
+1 Verifique definitivamente a colisão-normal. Ter diferentes tipos de geometria mundial é bom e permite um design de nível mais flexível, mas não resolve o problema principal. Uma "parede" também pode ser do tipo "chão", dependendo se você estiver correndo ou pisando nela. É aí que a colisão normal ajudará.
bummzack
Acho a solução normal muito boa e resolveria meu problema. Até a outra resposta bem avaliada é boa e tal - mas isso responde melhor à minha pergunta. Agradeça ao seu wkerslake!
Malax 27/10/10
8

Você certamente deve implementar algum tipo de tipo de superfície. Pense nisso, como você vai conseguir se consegue subir uma escada se não sabe se seu personagem acabou de colidir com uma parede ou uma escada? Você pode simplesmente usar o OOP para gerenciar uma hierarquia de tipos usando herança, mas eu sugiro que você use "categorias" implementadas usando um tipo enumerado:

Aqui está a idéia: Uma enumeração "Colisões" possui um sinalizador para cada categoria. Por exemplo:

namespace Collisions
{
    enum Type
    {
        None   = 0,
        Floor  = 1 << 0,
        Ladder = 1 << 1,
        Enemy  = 1 << 2,
        ... // And whatever else you need.

        // Then, you can construct named groups of flags.
        Player = Floor | Ladder | Enemy
    };
}

Com esse método, você poderá testar se o jogador justificou alguma coisa que deveria gerenciar, para que seu mecanismo possa chamar um método "colidido" da entidade:

void Player::Collided( Collisions::Type group )
{
   if ( group & Collisions::Ladder )
   {
      // Manage Ladder Collision
   }
   if ( group & Collisions::Floor )
   {
      // Manage Floor Collision
   }
   if ( group & Collisions::Enemy )
   {
      // Manage Enemy Collision
   }
}

O método usa sinalizadores bit a bit e o operador "Or" bit a bit para garantir que cada grupo tenha um valor diferente, com base no valor binário da categoria. Esse método funciona bem e é facilmente escalável para que você possa criar grupos de colisão alfandegária. Cada entidade (jogador, inimigo, etc.) do seu jogo possui alguns bits chamados de "filtro", que são usados ​​para determinar com o que ela pode colidir. Seu código de colisão deve verificar se os bits correspondem e reagem de acordo, com algum código que pode se parecer com:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}
Frédérick Imbeault
fonte
Existe uma razão para usar const ints em vez de um enum? No caso degenerado de um único sinalizador ou grupo nomeado, o tipo enumerado é mais legível e você obtém habilidades adicionais de verificação de tipo na maioria dos compiladores.
Bem, sim, o enum não pode (eu acho) usar operadores binários para criar novos grupos personalizados. A razão pela qual fiz essas const const é que eu uso uma classe Config com membros estáticos públicos por motivos de legibilidade, mantendo a segurança dos parâmetros de configuração.
Frédérick Imbeault 26/10/10
Pode. A enumeração "rvalues" pode ser operações bit a bit em outros valores constantes (acho que pode ser qualquer expressão constante, na verdade, mas estou com preguiça de verificar o padrão). Valores não estáticos são alcançados exatamente da mesma maneira que o seu exemplo, mas são digitados com mais precisão. Não tenho idéia do que você quer dizer com "segurança", mas exatamente a mesma sintaxe sem a sobrecarga associada a uma classe pode ser alcançada com um espaço para nome.
Atualizei seus exemplos de código para usar um tipo enumerado.
Talvez isso facilite a leitura. Aprovo as alterações que você fez, o método que usei estava correto, mas não havia ajustado o código para a explicação, obrigado pelas correções. Para o espaço para nome, você está certo, mas isso não é verdade para todos os idiomas (idiomas de script, por exemplo). A "Segurança" é que minhas opções de configuração são estáticas, portanto não podem ser modificadas, mas o uso de uma enumeração evita isso também.
Frédérick Imbeault 26/10/10
1

Se você considerar o pé do seu personagem sempre sob o personagem por uma certa distância, e se você não estiver se afastando da superfície, seu personagem estará no chão.

Em pseudo-código aproximado:

bool isOnGround(Character& chr)
{
   // down vector is opposite from your characters current up vector.
   // if you want to support wall jumps, animate the vecToFoot as appropriate.
   vec vecToFoot = -chr.up * chr.footDistanceFromCentre;

// if feet are moving away from any surface, can't be on the ground if (dot(chr.velocity, down) < 0.0f) return false;

// generate line from character centre to the foot position vec linePos0 = chr.position; vec linePos1 = chr.position + vecToFoot;

// test line against world. If it returns a hit surface, we're on the ground if (testLineAgainstWorld(line0, line1, &surface) return true;

return false; }

jpaver
fonte
Isso não funcionará se você quiser adicionar recursos como saltos duplos ou saltos de parede, eu acho?
Frédérick Imbeault 26/10/10
Não acredito que o OP tenha mencionado saltos duplos ou saltos na parede, não é?
jpaver
Revisei o código para mostrar como abstrair o vetor para o pé do centro do personagem. Se você animar isso, e os pés terminarem de lado e baterem em uma parede, você será capaz de apoiar o salto na parede.
jpaver
Esta é basicamente uma maneira menos flexível de verificar a colisão normal. (Menos flexível porque você pode trivialmente distinguir um salto da parede de um salto andar de um salto teto apenas verificando a direção do normal.)
2
Nunca chame uma variável char, nem mesmo em pseudo-código.
rightfold 27/10/10