Problemas de detección de colisión usando AABB

8

Implementé una rutina simple de detección de colisiones usando AABB entre mi sprite principal del juego y varias plataformas (consulte el código a continuación). Funciona muy bien, pero ahora estoy introduciendo la gravedad para hacer que mi personaje se caiga y se muestran algunos problemas con mi rutina de CD.

Creo que el quid de la cuestión es que mi rutina de CD mueve el sprite hacia atrás a lo largo del eje en el que ha penetrado la menor cantidad en el otro sprite. Entonces, si está más en el eje Y que en el X, entonces lo moverá hacia atrás a lo largo del eje X.

Sin embargo, ahora mi sprite (héroe) ahora está cayendo a una velocidad de 30 píxeles por fotograma (es una pantalla de 1504 de altura; necesito que caiga tan rápido como quiero intentar simular la gravedad 'normal', cualquier más lento solo se ve raro ) Estoy teniendo estos problemas. Intentaré mostrar lo que está sucediendo (y lo que creo que lo está causando, aunque no estoy seguro) con algunas imágenes: (Codifique las imágenes a continuación).

Agradecería algunas sugerencias sobre cómo solucionar este problema.

ingrese la descripción de la imagen aquí

Para aclarar, en la imagen de arriba a la derecha, cuando digo que la posición se corrige 'incorrectamente', eso puede ser un poco engañoso. El código en sí está funcionando correctamente para la forma en que está escrito, o dicho de otra manera, el algoritmo en sí mismo si se comporta como lo esperaría, pero necesito cambiar su comportamiento para evitar que este problema suceda, espero que aclare mis comentarios en la imagen .

ingrese la descripción de la imagen aquí

Mi código

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }
BungleBonce
fuente
Para elaborar, ¿por qué es que mueves sprites de nuevo en el otro eje: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Tom 'Blue' Piddock
Hola @Blue, porque el sprite debe corregirse a lo largo del eje menos penetrante, por lo que si el sprite ha penetrado más en la plataforma en el Eje X que en el Y, es el Y el que debe corregirse, ya que es el menor de los dos.
BungleBonce
posible duplicado de Detección
Anko
He leído esa pregunta antes de @Anko, no me da las respuestas que necesito. Por lo tanto, compuse mi propia pregunta (No estoy convencido de que el autor de la pregunta pregunte exactamente lo mismo, aunque es difícil estar seguro) :-)
BungleBonce

Respuestas:

1

Durante mis experimentos con HTML5 Canvas y AABB encontré exactamente lo que estás experimentando. Esto sucedió cuando intenté hacer una plataforma desde cajas adyacentes de 32x32 píxeles.

Soluciones que intenté por orden de mi preferencia personal

1 - Divide movimientos por eje

Mi actual, y creo que continuaré mi juego con él. Pero asegúrese de verificar lo que llamo Phantom AABB si necesita una solución rápida sin tener que modificar mucho su diseño actual.

Mi mundo del juego tiene una física muy simple. No existe un concepto de fuerzas (todavía), solo desplazamiento. La gravedad es un desplazamiento constante hacia abajo. Sin aceleración (todavía).

Los desplazamientos se aplican por eje. Y en esto consiste esta primera solución.

Cada cuadro del juego:

player.moves.y += gravity;
player.moves.x += move_x;

move_x se establece de acuerdo con el procesamiento de entrada, si no se presiona la tecla de flecha, entonces move_x = 0. Los valores inferiores a cero significan izquierda, de lo contrario a la derecha.

Procese todos los demás sprites móviles y decida los valores de su componente "movimientos", también tienen el componente "movimientos".

Nota: después de la detección y respuesta de colisión, el componente de movimiento debe establecerse en cero, ambas propiedades x e y, porque al siguiente tic la gravedad se agregará nuevamente. Si no reinicia, terminará con movimientos.y de dos veces la gravedad deseada, y en el siguiente tic será tres veces.

Luego ingrese el código de aplicación de desplazamiento y de detección de colisión.

El componente de movimientos no es realmente la posición actual del sprite. El sprite aún no se ha movido, incluso si se ha movido move.x o y. El componente "movimientos" es una forma de guardar el desplazamiento que se aplicará a la posición del sprite en la parte correcta del bucle del juego.

Procese primero el eje y (mueve.y). Aplica movimientos.y a sprite.position.y. Luego aplique el método AABB para ver si el sprite en movimiento que se está procesando se superpone a una plataforma AABB (ya entendió cómo hacerlo). Si se superpone, mueve el sprite nuevamente en el eje y aplicando penetración. Sí, a diferencia de mi otra respuesta, ahora este método de movimiento evolucionado ignora la penetración.x, para este paso del proceso. Y usamos penetration.y incluso si es mayor que penetration.x, ni siquiera lo estamos comprobando.

Luego procese el eje x (moves.x). Aplica movimientos.x a sprite.position.x. Y haz lo contrario que antes. Esta vez moverás el sprite solo en el eje horizontal aplicando penetración.x. Como antes, no le importa si penetration.x es menor o mayor que penetration.y.

El concepto aquí, es que si el sprite no se mueve, y inicialmente no estaba colisionando, permanecerá así en el siguiente cuadro (sprite.moves.x e y son cero). El caso especial en el que otro objeto del juego se teletransporta mágicamente a una posición que se superpone al comienzo del sprite procesado es algo que manejaré más adelante.

Cuando un sprite comienza a moverse. Si se mueve solo en el eje x, entonces solo nos interesa si penetra algo por la izquierda o la derecha. Como el sprite no se mueve hacia abajo, y sabemos esto porque la propiedad y del componente de movimientos es cero, ni siquiera verificamos el valor calculado para la penetración. Y, solo vemos en penetración.x. Si existe penetración en el eje de movimiento, aplicamos corrección, de lo contrario simplemente dejamos que el sprite se mueva.

¿Qué pasa con el desplazamiento diagonal, no necesariamente en un ángulo de 45 grados?

El sistema propuesto puede manejarlo. Solo necesita procesar cada eje por separado. Durante tu simulación. Se aplican diferentes fuerzas al sprite. Incluso si su motor aún no comprende las fuerzas. Estas fuerzas se traducen en un desplazamiento arbitrario en el plano 2D, este desplazamiento es un vector 2D en cualquier dirección y de cualquier longitud. De este vector puede extraer el eje y y el eje x.

¿Qué hacer si el vector de desplazamiento es demasiado grande?

No quieres esto:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

A medida que nuestros nuevos movimientos que aplican la lógica procesan primero un eje y luego el otro, el código de colisión puede terminar viendo un gran desplazamiento como una gran L, esto no detectará correctamente las colisiones con objetos que se cruzan con su vector de desplazamiento.

La solución es dividir grandes desplazamientos en pequeños pasos, idealmente el ancho o la altura del sprite, o la mitad del ancho o la altura del sprite. Para obtener algo como esto:

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

¿Qué hay de teletransportarse sprites?

Si representa la teletransportación como un desplazamiento grande, utilizando el mismo componente de movimientos que un movimiento normal, entonces no hay problema. Si no lo hace, debe pensar en una forma de marcar el sprite de teletransportación para que sea procesado por el código de colisión (¿agregarlo a una lista dedicada a este propósito?), Durante el código de respuesta puede desplazar directamente el sprite permanente que estaba en la forma en que ocurrió la teletransportación.

2 - Phantom AABB

Tenga un AABB secundario para cada sprite afectado por la gravedad que comienza en la parte inferior del sprite, tiene el mismo ancho que el AABB del sprite y una altura de 1 píxel.

La idea aquí es que este AABB fantasma siempre está colisionando con la plataforma debajo del sprite. Solo cuando este AABB fantasma no se superpone, se permite aplicar gravedad al sprite.

No necesita calcular el vector de penetración para el AABB fantasma y la plataforma. Solo le interesa una prueba de colisión simple, si se superpone a una plataforma, no se puede aplicar la gravedad. Su sprite puede tener una propiedad booleana que le dice si está sobre una plataforma o en el aire. Estás en el aire cuando tu AABB fantasma no está colisionando con una plataforma debajo del jugador.

Esta:

player.position.y += gravity;

Se convierte en algo como esto:

if (player.onGround == false) player.position.y += gravity;

Muy simple y confiable.

Dejas tu implementación de AABB como está.

El AABB fantasma puede tener un bucle dedicado que los procese, que solo compruebe las colisiones simples y no pierda el tiempo calculando el vector de penetración. Este bucle debe ser anterior al código de colisión y respuesta normal. Puede aplicar gravedad durante este ciclo, ya que los sprites en el aire aplican gravedad. Si el AABB fantasma es en sí mismo una clase o estructura, puede tener una referencia al sprite al que pertenece.

Esto es muy similar a la otra solución propuesta donde verificas si tu jugador está saltando. Doy una posible forma de detectar si el jugador tiene los pies sobre una plataforma.

Hatoru Hansou
fuente
Me gusta la solución Phantom AABB aquí. Ya he aplicado algo similar para solucionar el problema. (Pero no usando un AABB fantasma) - Estoy interesado en aprender más sobre su método inicial anterior, pero estoy un poco confundido por ello. ¿El componente Moves.x (y .y) es básicamente la cantidad por la que se moverá el sprite (si se le permite moverse)? Además, no estoy claro cómo ayudaría con mi situación directamente. Le agradecería que editara su respuesta para mostrar un gráfico de cómo podría ayudar con mi situación (como en mi gráfico anterior). ¡Gracias!
BungleBonce
Sobre el componente de movimientos. Representa el desplazamiento futuro, sí, pero no verifica si permite el movimiento o no, en realidad aplica el movimiento, por eje, primero y, luego x, y luego ve si chocó con algo, si usa la penetración vector para retroceder. ¿Por qué guardar el desplazamiento para un procesamiento posterior difiere de aplicar realmente el desplazamiento cuando hay una entrada del usuario para procesar o algo sucedió en el mundo del juego? Intentaré poner esto en imágenes cuando edite mi respuesta. Mientras tanto, veamos si alguien más viene con una alternativa.
Hatoru Hansou
Ah, ok. Creo que entiendo lo que estás diciendo ahora. Entonces, en lugar de mover X e Y y luego verificar la colisión, con su método, primero mueve X, luego verifica la colisión, si choca, corrija a lo largo del eje X. Luego, mueva Y y compruebe la colisión, si hay una colisión, ¿se mueve a lo largo del eje X? ¿De esta manera siempre conocemos el eje correcto de impacto? ¿Tengo esto bien? ¡Gracias!
BungleBonce
Advertencia: usted escribió "Entonces mueva Y y verifique la colisión, si hay una colisión, entonces muévase a lo largo del eje X". Su eje Y. Probablemente un error tipográfico. Sí, ese es mi enfoque actual. Para desplazamientos muy grandes, más que el ancho o la altura de tu sprite, debes dividir en pasos más pequeños, y eso es lo que quiero decir con la analogía L. Tenga cuidado de no aplicar un gran desplazamiento en el eje yy luego una gran x, debe dividirlos en pequeños pasos e intercalarlos para obtener un comportamiento correcto. Algo que importa solo si se permiten grandes desplazamientos en tu juego. En el mío, sé que ocurrirán. Así que me preparé para eso.
Hatoru Hansou
Brillante @HatoruHansou, nunca pensé en hacer las cosas de esta manera, ciertamente lo intentaré, muchas gracias por tu ayuda, marcaré tu respuesta como aceptada. :-)
BungleBonce
5

Combinaría los mosaicos de la plataforma en una sola entidad de plataforma.

Por ejemplo, supongamos que toma las 3 fichas de las imágenes y las combina en una sola entidad, su entidad tendría un cuadro de colisión de:

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

Usar eso para colisión te dará la y como el eje menos penetrante en la mayor parte de la plataforma y al mismo tiempo te permitirá tener fichas específicas dentro de la plataforma.

Subrayado, cero
fuente
Hola @UnderscoreZero: gracias, este es un camino a seguir, lo único es que actualmente estoy almacenando mis mosaicos en 3 conjuntos separados, uno para los mosaicos del borde izquierdo, uno para los mosaicos del medio y otro para los mosaicos del borde derecho. Creo que podría combinarlos todos en una matriz con el propósito de CD, simplemente no estoy seguro de cómo podría escribir código para hacer eso.
BungleBonce
Otra opción que podría probar que utilizan muchos motores de física es que una vez que el objeto ha aterrizado en un plano, deshabilitan la gravedad del objeto para evitar que se caiga. Una vez que el objeto recibe la fuerza suficiente para alejarlo del avión o se cae por el costado del avión, reactivan la gravedad y realizan la física como de costumbre.
UnderscoreZero
0

Problemas como estos son comunes con los nuevos métodos de detección de colisiones.

No sé demasiado acerca de su configuración de colisión actual, pero así es como debería ir:

En primer lugar, haga estas variables:

  1. Haz una velocidad X e Y
  2. Hacer una gravedad
  3. Haz un salto Velocidad
  4. Hacer un salto

En segundo lugar, haga un temporizador para calcular el delta que afecte la velocidad del objeto.

Tercero, haz esto:

  1. Compruebe si el jugador está presionando el botón de salto y no está saltando, si es así, agregue jumpSpeed ​​a la velocidad Y.
  2. Resta la gravedad de la velocidad Y cada actualización para simular la gravedad
  3. Si la altura y + del jugador es menor o igual que la y de la plataforma, establezca la velocidad Y en 0 y coloque la Y del jugador en la altura y + de la plataforma.

Si haces esto, no debería haber ningún problema. ¡Espero que esto ayude!

LiquidFeline
fuente
Hola @ user1870398, gracias, pero mi juego ni siquiera tiene saltos en este momento, la pregunta está relacionada con los problemas que surgen del uso del método del 'eje menos penetrante' para corregir la posición del sprite. El problema es que la gravedad está empujando a mi personaje más lejos en mis plataformas a lo largo del eje Y de lo que se superpone en el eje X, por lo tanto, mi rutina (como es correcto para este tipo de algoritmo) está corrigiendo el eje X, por lo que no puedo moverse a lo largo de la plataforma (como la rutina de CD ve los mosaicos individuales y los trata como tales a pesar de que se presentan como una plataforma).
BungleBonce
Entiendo tu problema. Es solo que no estás corrigiendo adecuadamente tu movimiento. Necesita una variable isJumping . Si presionas el botón de salto y no estás saltando, lo configuras como verdadero, si tocas el suelo , configúralo como falso , mueve la Y del jugador a la posición correcta ( plataforma.y + jugador.alta ), y establece Saltar a falso. A continuación, justo después de verificar esas cosas, haga if (left_key) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) . Sé que aún no ha implementado el salto, por lo que simplemente siempre se ha establecido el salto en falso.
LiquidFeline
No tengo saltos, pero si una variable 'ifJumping' siempre se establece en falso, ¿en qué se diferencia esto de no tener una? (por ejemplo, en los juegos donde no hay saltos) Estoy un poco confundido sobre cómo un booleano 'salta' podría ayudar en mi situación; estaría agradecido si pudiera explicar un poco más. ¡Gracias!
BungleBonce
No es necesario Simplemente mejor implementarlo mientras todavía lo está programando. De todos modos, la razón de isJumping es organizar su código, y evita problemas al agregar gravedad solo mientras el jugador está saltando. Solo haz lo que te recomendé. Este método es el que usa la mayoría de las personas. ¡Incluso Minecraft lo usa! ¡Así es como se simula efectivamente la gravedad! :)
LiquidFeline