Construyendo un Plattformer - ¿Cómo determinar si un jugador puede saltar?

16

Estoy construyendo un juego simple de Plattformer Jump n 'Run Style. No uso fichas, en cambio tengo formas geométricas para mis entidades de nivel (y el jugador también es uno). Terminé mi código de detección de colisión y todo funciona bien hasta ahora.

Luego, quería implementar el salto. Simplemente verificando si el jugador presiona la tecla apropiada y agrega algo de velocidad hacia arriba. Funciona bien. Pero funciona incluso si el jugador está en el aire, que no es lo que quiero. ;-)

Entonces, tengo que verificar si el jugador se para sobre algo. Mi primera idea fue verificar si hubo una colisión en el último cuadro y marcar al jugador como "capaz de saltar", pero esto incluso se dispararía si el jugador golpea una pared en el aire. Como mis habilidades matemáticas no son tan buenas, pido ayuda, incluso las sugerencias harían cómo implementar esto.

¡Gracias!

Malax
fuente

Respuestas:

14

Se me ocurren dos opciones:

  • Lo primero que se piensa es etiquetar la geometría con una ID y luego verificar para ver si la colisión es con geometría etiquetada como piso. Esto ofrece el mayor control de las superficies saltables pero a un costo en el tiempo de creación de nivel.
  • El segundo es verificar la normalidad de la colisión, si está apuntando hacia arriba, entonces permite saltar. O dentro de cierto margen, depende de si tiene pisos inclinados. Esto es flexible y no requiere ningún etiquetado, pero si ha inclinado pisos y paredes, puede saltar un poco donde no lo desee.
Wkerslake
fuente
El cheque normal me parece una gran idea. Gracias por publicar eso.
Christopher Horenstein
+1 para verificación normal. Esto asegura que un piso pueda pasar sin problemas a una pared sin causar ningún problema.
Soviut
1
+1 Definitivamente verifica la colisión normal. Tener diferentes tipos de geometría mundial está bien y permite un diseño de nivel más flexible, pero no resuelve su problema principal. Un "muro" también podría ser del tipo "terreno", dependiendo de si se está topando con él o parado sobre él. Ahí es donde ayudará la colisión normal.
bummzack
La solución normal me parece muy agradable y resolvería mi problema. Incluso la otra respuesta de alta calificación es agradable y tal, pero esto responde mejor a mi pregunta. Gracias a tu wkerslake!
Malax
8

Seguramente debería implementar algún tipo de tipo de superficie. Piénselo, ¿cómo se las arreglará si puede subir una escalera si no puede saber si su personaje simplemente chocó una pared o una escalera? Simplemente puede usar OOP para administrar una jerarquía de tipos usando herencia, pero le sugiero que use "categorías" implementadas usando un tipo enumerado:

Aquí está la idea: una enumeración de "Colisiones" tiene una bandera para cada categoría. Por ejemplo:

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
    };
}

Con este método, podrá probar si el jugador colisionó con algo que debería manejar, por lo que su motor puede llamar un método "colisionado" de la entidad:

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
   }
}

El método utiliza indicadores a nivel de bits y el operador "O" a nivel de bits para garantizar que cada grupo tenga un valor diferente, en función del valor binario de la categoría. Este método funciona bien y es fácilmente escalable para que pueda crear grupos de colisión personalizados. Cada entidad (jugador, enemigo, etc.) en su juego tiene algunos bits llamados "filtro", que se utilizan para determinar con qué puede colisionar. Su código de colisión debe verificar para ver si los bits coinciden y reaccionar en consecuencia, con algún código que podría verse así:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}
Frédérick Imbeault
fuente
¿Hay alguna razón para usar constantes en lugar de una enumeración? En el caso degenerado de un solo indicador o grupo con nombre, el tipo enumerado es más legible y obtienes capacidades adicionales de verificación de tipo en la mayoría de los compiladores.
Bueno, sí, la enumeración no puede (creo) usar operadores binarios para crear nuevos grupos personalizados. La razón por la que realicé estas tesis es que uso una clase Config con miembros estáticos públicos por razones de legibilidad, manteniendo la seguridad de los parámetros de configuración.
Frédérick Imbeault
Puede. Los "valores" de la enumeración pueden ser operaciones bit a bit en otros valores constantes (creo que en realidad puede ser cualquier expresión constante, pero soy demasiado vago para verificar el estándar). Los valores no estáticos se logran exactamente de la misma manera que su ejemplo, pero se escriben con mayor precisión. No tengo idea de lo que quiere decir con "seguridad", pero se puede lograr exactamente la misma sintaxis sin la sobrecarga asociada con una clase con un espacio de nombres.
Actualicé sus ejemplos de código para usar un tipo enumerado.
Tal vez hace pensar más fácil de leer. Apliqué los cambios que realizó, el método que utilicé era correcto pero no había ajustado el código para la explicación explicativa, gracias por las correcciones. Para el espacio de nombres, tiene toda la razón, pero esto no es cierto para todos los idiomas (por ejemplo, los lenguajes de secuencias de comandos). La "seguridad" es que mis opciones de configuración son estáticas, por lo que no se pueden modificar, pero el uso de una enumeración también evita esto.
Frédérick Imbeault
1

Si consideras que el pie de tu personaje está siempre debajo del personaje por una cierta distancia, y si no te alejas de la superficie, entonces tu personaje está en el suelo.

En pseudocó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
fuente
Esto no funcionará si desea agregar características como saltos dobles o saltos de pared, creo.
Frédérick Imbeault
No creo que el OP mencionara saltos dobles o saltos de pared, ¿verdad?
jpaver
Revisé el código para mostrar cómo abstraer el vector a tu pie desde el centro del personaje. Si animas esto y los pies terminan de lado y chocan contra una pared, podrás soportar el salto de pared.
jpaver
Esto es básicamente una forma menos flexible de verificar la colisión normal. (Menos flexible porque puede distinguir trivialmente un salto de pared de un salto de piso de un salto de techo simplemente comprobando la dirección normal.)
2
Nunca llame a una variable char, ni siquiera en pseudocódigo.
2010