Problemas de colisión 2D Platformer AABB

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

Tengo un problema con la resolución de colisión AABB.


Resuelvo la intersección AABB resolviendo primero el eje X, luego el eje Y. Esto se hace para evitar este error: http://i.stack.imgur.com/NLg4j.png


El método actual funciona bien cuando un objeto se mueve hacia el jugador y el jugador tiene que ser empujado horizontalmente. Como puede ver en el .gif, los picos horizontales empujan al jugador correctamente.


Sin embargo, cuando las puntas verticales se mueven hacia el jugador, el eje X todavía se resuelve primero. Esto hace que "usar los picos como un ascensor" sea imposible.

Cuando el jugador se mueve hacia los picos verticales (afectados por la gravedad, cae dentro de ellos), es empujado en el eje Y, porque para empezar no hubo solapamiento en el eje X.


Algo que probé fue el método descrito en la primera respuesta de este enlace: detección de colisión de objetos rectangulares 2D

Sin embargo, los picos y los objetos en movimiento se mueven al cambiar su posición, no a la velocidad, y no calculo su próxima posición predicha hasta que se llama a su método Update (). No hace falta decir que esta solución tampoco funcionó. :(


Necesito resolver la colisión AABB de una manera que los dos casos descritos anteriormente funcionen según lo previsto.

Este es mi código fuente de colisión actual: http://pastebin.com/MiCi3nA1

Estaría realmente agradecido si alguien pudiera investigar esto, ya que este error ha estado presente en el motor desde el principio, y he estado luchando por encontrar una buena solución, sin ningún éxito. Esto realmente me hace pasar noches mirando el código de colisión y me impide llegar a la "parte divertida" y codificar la lógica del juego :(


Intenté implementar el mismo sistema de colisión que en la demostración de plataformas XNA AppHub (copiando y pegando la mayoría de las cosas). Sin embargo, el error de "salto" ocurre en mi juego, mientras que no ocurre en la demostración de AppHub. [error de salto: http://i.stack.imgur.com/NLg4j.png ]

Para saltar compruebo si el jugador está "en tierra", luego agrego -5 a Velocity.Y.

Dado que Velocity.X del jugador es más alto que Velocity.Y (consulte el cuarto panel en el diagrama), onGround se establece en verdadero cuando no debería ser, y así permite que el jugador salte en el aire.

Creo que esto no sucede en la demostración de AppHub porque el Velocity.X del jugador nunca será más alto que Velocity.Y, pero puedo estar equivocado.

Resolví esto antes resolviendo primero en el eje X, luego en el eje Y. Pero eso arruina la colisión con los picos como dije anteriormente.

Vittorio Romeo
fuente
55
En realidad no hay nada que salga mal. El método que estoy usando empuja primero en el eje X, luego en el eje Y. Es por eso que el jugador es empujado horizontalmente incluso en los picos verticales. Necesito encontrar una solución que evite el "problema de salto" y empuje al jugador en el eje poco profundo (el eje con menos penetración), pero no puedo encontrar ninguno.
Vittorio Romeo
1
Podrías detectar qué cara de la obstrucción está tocando el jugador y resolver esa
Ioachim
44
@Johnathan Hobbs, lee la pregunta. Vee sabe exactamente lo que está haciendo su código, pero no sabe cómo resolver un determinado problema. Pasar por el código no lo ayudará en esta situación.
AttackingHobo
44
@Maik @Jonathan: Nada está "mal" con el programa: él comprende exactamente dónde y por qué su algoritmo no hace lo que quiere. Simplemente no sabe cómo cambiar el algoritmo para hacer lo que quiere. Por lo tanto, el depurador no es útil en este caso.
BlueRaja - Danny Pflughoeft
1
@AttackingHobo @BlueRaja Estoy de acuerdo y he eliminado mi comentario. Ese era yo solo sacando conclusiones sobre lo que estaba sucediendo. Realmente me disculpo por hacer que tengas que explicar eso y por desviar al menos a una persona. Honestamente, puedes contar conmigo para que realmente absorba la pregunta correctamente antes de dejar una respuesta la próxima vez.
doppelgreener

Respuestas:

9

De acuerdo, descubrí por qué la demostración de plataformas XNA AppHub no tiene el error de "salto": la demostración prueba los mosaicos de colisión de arriba a abajo . Cuando se encuentra contra una "pared", el jugador puede estar superponiendo múltiples fichas. El orden de resolución es importante porque resolver una colisión también puede resolver otras colisiones (pero en una dirección diferente). La onGroundpropiedad solo se establece cuando la colisión se resuelve empujando al jugador hacia arriba en el eje y. Esta resolución no ocurrirá si las resoluciones anteriores empujaron al jugador hacia abajo y / u horizontalmente.

Pude reproducir el error de "salto" en la demostración de XNA cambiando esta línea:

for (int y = topTile; y <= bottomTile; ++y)

a esto:

for (int y = bottomTile; y >= topTile; --y)

(También modifiqué algunas de las constantes relacionadas con la física, pero esto no debería importar).

Quizás ordenar bodiesToCheckel eje y antes de resolver las colisiones en su juego solucionará el error de "salto". Sugiero resolver la colisión en el eje de penetración "superficial", como lo hace la demostración de XNA y sugiere Trevor . También tenga en cuenta que el reproductor de demostración XNA es dos veces más alto que las fichas colisionables, lo que hace más probable el caso de colisión múltiple.

Leftium
fuente
Esto suena interesante ... ¿Crees que ordenar todo por la coordenada Y antes de detectar colisiones podría resolver todos mis problemas? ¿Hay alguna trampa?
Vittorio Romeo
Aaa y encontré la "captura". Usando este método, el error de salto se corrige, pero si configuro Velocity.Y en 0 después de tocar el techo, ocurre nuevamente.
Vittorio Romeo
@Vee: se configura Position = nextPositioninmediatamente dentro del forbucle; de ​​lo contrario, onGroundaún se producen las resoluciones de colisión no deseadas (configuración ) El jugador debe ser empujado verticalmente hacia abajo (y nunca hacia arriba) cuando golpea el techo, por onGroundlo tanto , nunca debe establecerse. Así es como lo hace la demostración XNA, y no puedo reprobar el error "techo" allí.
Leftium
pastebin.com/TLrQPeBU : este es mi código: en realidad trabajo en la posición misma, sin usar una variable "nextPosition". Debería funcionar como lo hace en la demostración XNA, pero si mantengo la línea que establece Velocity.Y en 0 (línea 57) sin comentar, se produce el error de salto. Si lo quito, el jugador sigue flotando cuando salta al techo. ¿Se puede arreglar esto?
Vittorio Romeo
@Vee: su código no muestra cómo onGroundse configura, por lo que no puedo investigar por qué el salto está incorrectamente permitido. La demostración de XNA actualiza su isOnGroundpropiedad análoga en su interior HandleCollisions(). Además, después de establecerlo Velocity.Yen 0, ¿por qué la gravedad no comienza a mover al jugador hacia abajo nuevamente? Supongo que la onGroundpropiedad está configurada incorrectamente. Eche un vistazo a cómo se actualiza la demostración XNA previousBottomy isOnGround( IsOnGround).
Leftium
9

La solución más simple para usted será verificar ambas direcciones de colisión contra cada objeto en el mundo antes de resolver cualquier colisión, y simplemente resolver la menor de las dos "colisiones compuestas" resultantes. Esto significa que resuelve en la menor cantidad posible, en lugar de siempre resolver x primero, o siempre resolver y primero.

Su código se vería así:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

Gran revisión : al leer el comentario sobre otras respuestas, creo que finalmente noté una suposición no declarada, lo que hará que este enfoque no funcione (y que explique por qué no pude entender los problemas que algunos, pero no todos) la gente vio con este enfoque). Para elaborar, aquí hay un poco más de pseudocódigo, que muestra más explícitamente lo que se supone que las funciones a las que hice referencia antes deberían estar realmente haciendo:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

Esta no es una prueba "por par de objetos"; no funciona probando y resolviendo el objeto en movimiento contra cada mosaico de un mapa mundial individualmente (ese enfoque nunca funcionará de manera confiable, y falla de formas cada vez más catastróficas a medida que disminuye el tamaño de los mosaicos). En cambio, está probando el objeto en movimiento contra cada objeto en el mapa mundial simultáneamente, y luego resuelve basándose en colisiones contra todo el mapa mundial.

Así es como se asegura de que (por ejemplo) las fichas de pared individuales dentro de una pared nunca reboten al jugador hacia arriba y hacia abajo entre dos fichas adyacentes, lo que hace que el jugador quede atrapado en un espacio no existente "entre"; las distancias de resolución de colisión siempre se calculan hasta el espacio vacío en el mundo, no simplemente hasta el límite de una única ficha que podría tener otra ficha sólida encima.

Trevor Powell
fuente
1
i.stack.imgur.com/NLg4j.png : esto sucede si utilizo el método anterior.
Vittorio Romeo
1
Tal vez no entiendo su código correctamente, pero permítame explicar el problema en el diagrama: dado que el reproductor es más rápido en el eje X, la penetración X es> que la penetración Y. Colisión elige el eje Y (porque la penetración es menor) y empuja al jugador verticalmente. Esto no ocurre si el jugador es lo suficientemente lento como para que la penetración Y sea siempre> que la penetración X.
Vittorio Romeo
1
@Vee ¿A qué panel del diagrama te refieres, aquí, como dar un comportamiento incorrecto usando este enfoque? Supongo que el panel 5, en el que el jugador claramente penetra menos en X que en Y, lo que resultaría en ser empujado a lo largo del eje X, que es el comportamiento correcto. Solo obtienes el mal comportamiento si seleccionas un eje para la resolución basado en la velocidad (como en tu imagen), y no en base a la penetración real (como sugerí).
Trevor Powell
1
@Trevor: Ah, ya veo lo que estás diciendo. En ese caso, simplemente podría hacerloif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft
1
@BlueRaja Gran punto. Edité mi respuesta para usar menos condicionales, como usted sugiere. ¡Gracias!
Trevor Powell
6

Editar

Lo he mejorado

Parece que lo clave que cambié fue clasificar las intersecciones.

Las intersecciones son:

  • Golpes de techo
  • Golpes al suelo (empujar fuera del piso)
  • Colisiones de pared en el aire
  • Colisiones de pared a tierra

y los resuelvo en ese orden

Defina una colisión en el suelo cuando el jugador esté al menos 1/4 de camino en la ficha

Así que esta es una colisión en el suelo y el jugador ( azul ) se sentará en la parte superior del azulejo ( negro ) colisión en el suelo

Pero esto NO es una colisión en el suelo y el jugador se "deslizará" en el lado derecho de la ficha cuando aterrice en ella. no va a ser un jugador de colisión en tierra

Por este método, el jugador ya no quedará atrapado en los lados de las paredes.

bobobobo
fuente
Probé su método (vea mi implementación: pastebin.com/HarnNnnp ) pero no sé dónde establecer la Velocidad del jugador en 0. Intenté establecerlo en 0 si encrY <= 0 pero eso permite que el jugador se detenga en el aire mientras se desliza a lo largo de una pared y también le permite saltar repetidamente.
Vittorio Romeo
Sin embargo, esto funciona correctamente cuando el jugador interactúa con los picos (como se muestra en el .gif). Realmente agradecería que me ayudaras a resolver el problema del salto de pared (también quedar atrapado en las paredes). Tenga en cuenta que mis paredes están hechas de varios azulejos AABB.
Vittorio Romeo
Perdón por publicar otro comentario, pero después de copiar y pegar su código en un proyecto nuevo, cambiar sp a 5 y hacer que las fichas se reproduzcan en forma de pared, ocurre el error de salto (consulte este diagrama: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo
Ok, inténtalo ahora.
bobobobo
+1 para el ejemplo de código de trabajo (aunque faltan bloques de "pico" que se mueven horizontalmente :)
Leftium
3

Nota:
mi motor utiliza una clase de detección de colisión que verifica las colisiones con todos los objetos en tiempo real, solo cuando un objeto se mueve y solo en los cuadrados de la cuadrícula que ocupa actualmente.

Mi solución:

Cuando me encontré con problemas como este en mi plataforma 2D, hice algo como lo siguiente:

- (el motor detecta posibles colisiones, rápidamente)
- (el juego informa al motor para verificar las colisiones exactas de un objeto en particular) [devuelve el vector del objeto *]
- (el objeto observa la profundidad de penetración, así como la posición anterior en relación con la posición anterior de otro objeto, para determinar de qué lado deslizarse)
- (el objeto se mueve y promedia su velocidad con la velocidad del objeto (si el objeto se está moviendo))

ultifinitus
fuente
"(el objeto observa la profundidad de penetración, así como la posición anterior en relación con la posición anterior de otro objeto, para determinar de qué lado deslizarse)" esta es la parte en la que estoy interesado. ¿Puede dar más detalles? ¿Tiene éxito en la situación mostrada por el .gif y el diagrama?
Vittorio Romeo
Todavía tengo que encontrar una situación (aparte de las altas velocidades) en la que este método no funciona.
ultifinitus
Suena bien, pero ¿puedes explicar cómo resuelves las penetraciones? ¿Resuelve siempre en el eje más pequeño? ¿Comprueba el lado de la colisión?
Vittorio Romeo
No, de hecho, el eje más pequeño no es un buen camino (primario). EN MI HUMILDE OPINIÓN. Por lo general, resuelvo en función de qué lado estaba anteriormente fuera del lado del otro objeto, es el más común. Cuando eso falla, verifico en función de la velocidad y la profundidad.
ultifinitus
2

En los videojuegos que he programado, el enfoque era tener una función que dijera si tu posición es válida, es decir. bool ValidPos (int x, int y, int wi, int he);

La función comprueba el cuadro delimitador (x, y, wi, he) contra toda la geometría del juego y devuelve falso si hay alguna intersección.

Si quiere moverse, diga a la derecha, tome la posición del jugador, agregue 4 a x y verifique si la posición es válida; de lo contrario, verifique con + 3, + 2, etc. hasta que lo sea.

Si también necesita tener gravedad, necesita una variable que crezca siempre que no toque el suelo (golpeando el suelo: ValidPos (x, y + 1, wi, he) == verdadero, y es positivo aquí hacia abajo). si puedes mover esa distancia (es decir. ValidPos (x, y + gravedad, wi, he) devuelve verdadero) te estás cayendo, útil a veces cuando no deberías poder controlar tu personaje cuando caes.

Ahora, su problema es que tiene objetos en su mundo que se mueven, ¡así que antes que nada debe verificar si su posición anterior todavía es válida!

Si no es así, necesita encontrar una posición que sea. Si los objetos en el juego no pueden moverse más rápido que 2 píxeles por revolución del juego, deberías comprobar si la posición (x, y-1) es válida, luego (x, y-2) y luego (x + 1, y), etc. etc., se debe verificar todo el espacio entre (x-2, y-2) a (x + 2, y + 2). Si no hay una posición válida, significa que ha sido "aplastado".

HTH

Valmond

Valmond
fuente
Solía ​​usar este enfoque en mis días de Game Maker. Se me ocurren formas de acelerarlo / mejorarlo con mejores búsquedas (por ejemplo, una búsqueda binaria en el espacio que recorrió, aunque dependiendo de la distancia recorrida y la geometría, puede ser más lento), pero el principal inconveniente de este enfoque es que en lugar de hacer un solo chequeo contra todas las posibles colisiones, estás haciendo hasta lo que sea que tu cambio de posición sea cheques.
Jeff
"estás haciendo lo que sea que tu cambio de posición sea cheques" Lo siento, pero ¿qué significa exactamente eso? Por cierto, utilizará técnicas de partición espacial (BSP, Octree, quadtree, Tilemap, etc.) para acelerar el juego, pero siempre tendrá que hacerlo de todos modos (si el mapa es grande), la pregunta no es sobre eso, pero sobre el algoritmo utilizado para mover (correctamente) al jugador.
Valmond
@Valmond Creo que @Jeff significa que si su jugador se mueve 10 píxeles hacia la izquierda, puede tener hasta 10 detecciones de colisión diferentes.
Jonathan Connell
Si eso es lo que quiere decir, entonces tiene razón y debería ser así (¿quién podría decir si nos detendremos en +6 y no en +7?). Las computadoras son rápidas, usé esto en el S40 (~ 200mhz y un kvm no tan rápido) y funcionó de maravilla. Siempre puede intentar optimizar el algoritmo inicial, pero eso siempre le dará casos de esquina como el que tiene el OP.
Valmond
Eso es exactamente lo que quise decir
Jeff
2

Tengo algunas preguntas antes de comenzar a responder esto. Primero, en el error original en el que te quedaste atascado en las paredes, ¿eran esas fichas en las fichas individuales de la izquierda en lugar de una ficha grande? Y si lo fueran, ¿el jugador se quedaría atrapado entre ellos? En caso afirmativo a ambas preguntas, solo asegúrese de que su nueva posición sea válida . Eso significa que tendrás que verificar si hay una colisión sobre dónde le estás diciendo al jugador que se mueva. Así que resuelva el desplazamiento mínimo como se describe a continuación, y luego mueva a su jugador basándose en eso solo si puedeMuevete ahi. Casi demasiado debajo de la nariz: P Esto realmente introducirá otro error, al que llamo "casos de esquina". Esencialmente en términos de esquinas (como la parte inferior izquierda donde salen los picos horizontales en su .gif, pero si no hubiera picos) no resolvería una colisión, porque pensaría que ninguna de las resoluciones que genera conducen a una posición válida . Para resolver esto, simplemente mantenga un bool de si la colisión se ha resuelto, así como una lista de todas las resoluciones mínimas de penetración. Luego, si la colisión no se ha resuelto, repita cada resolución que generó y realice un seguimiento de las resoluciones máximas X e Y máximas (los máximos no tienen que provenir de la misma resolución). Luego resuelve la colisión en esos máximos. Esto parece resolver todos sus problemas, así como los que he encontrado.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Otra pregunta, ¿son los picos que muestra una ficha o fichas individuales? Si son fichas delgadas individuales, es posible que tenga que usar un enfoque diferente para las horizontales y verticales que lo que describo a continuación. Pero si son mosaicos enteros, esto debería funcionar.


Muy bien, entonces básicamente esto es lo que @Trevor Powell estaba describiendo. Como solo usa AABB, todo lo que tiene que hacer es determinar cuánto penetra un rectángulo en el otro. Esto le dará una cantidad en el eje X y en la Y. Elija el mínimo de los dos y mueva su objeto colisionador a lo largo de ese eje esa cantidad. Eso es todo lo que necesita para resolver una colisión AABB. NUNCA necesitará moverse a lo largo de más de un eje en una colisión de este tipo, por lo que nunca debe confundirse acerca de cuál mover primero, ya que solo se moverá al mínimo.

El software Metanet tiene un tutorial clásico sobre un enfoque aquí . También toma otras formas también.

Aquí hay una función XNA que hice para encontrar el vector de superposición de dos rectángulos:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(Espero que sea fácil de seguir, porque estoy seguro de que hay mejores formas de implementarlo ...)

isCollision (Rectangle) es literalmente solo una llamada al Rectángulo de XNA. Intersects (Rectangle).

He probado esto con plataformas móviles y parece funcionar bien. Haré algunas pruebas más más similares a su .gif para asegurarme e informar si no funciona.


Jeff
fuente
Soy un poco escéptico de ese comentario que hice sobre que no funciona con plataformas delgadas. No puedo pensar en una razón por la que no lo haría. También si quieres fuente, puedo subir un proyecto XNA para ti
Jeff
Sí, solo estaba probando esto un poco más y se remonta a su mismo problema de quedar atrapado en la pared. Es porque donde las dos fichas se alinean entre sí, le dice que se mueva hacia arriba debido a la inferior, y luego hacia afuera (justo en su caso) para la superior, negando efectivamente el movimiento de gravedad si su velocidad y es lo suficientemente lenta . Tendré que buscar una solución para esto, creo recordar haber tenido este problema antes.
Jeff
De acuerdo, se me ocurrió una manera extremadamente maliciosa de solucionar su primer problema. La solución consiste en encontrar la penetración mínima para cada AABB, resolviendo solo en la que tiene la X más grande, luego una segunda pasada resolviendo solo en la Y más grande. Se ve mal cuando te aplastan, pero levanta el trabajo y puedes ser empujado horizontalmente, y su problema original de pegado se ha ido. Es hackey, y no tengo idea de cómo se traduciría a otras formas. Otra posibilidad puede ser soldar las geometrías conmovedoras, pero ni siquiera lo he intentado
Jeff
Las paredes están hechas de baldosas de 16x16. Los picos son un azulejo de 16x16. Si resuelvo en el eje mínimo, se produce el error de salto (mira el diagrama). ¿Su solución "extremadamente hacky" funcionaría con la situación que se muestra en el .gif?
Vittorio Romeo
Sí, trabaja en el mío. ¿Cuál es el tamaño de tu personaje?
Jeff
1

Es sencillo.

Reescribe las espigas. Es culpa de los picos.

Sus colisiones deben ocurrir en unidades discretas y tangibles. El problema hasta donde puedo ver es:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

¡El problema es con el paso 2, no con el 3!

Si está tratando de hacer que las cosas se sientan sólidas, no debe permitir que los elementos se deslicen entre sí de esa manera. Una vez que esté en una posición de intersección, si pierde su lugar, el problema se vuelve más difícil de resolver.

Idealmente, los picos deberían verificar la existencia del jugador y, cuando se mueven, deberían empujarlo según sea necesario.

Una manera fácil de lograr esto es que el jugador tenga moveXy moveYfunciones que comprendan el paisaje y lo empujen a un cierto delta o lo más lejos que puedan sin golpear un obstáculo .

Normalmente serán llamados por el bucle de eventos. Sin embargo, también pueden ser llamados por objetos para empujar al jugador.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Obviamente, el jugador todavía tiene que reaccionar a los picos si se entra en ellas .

La regla general es que, después de que cualquier objeto se mueva, debe terminar en una posición sin intersección. De esta manera, es imposible obtener los problemas técnicos que estás viendo.

Chris Burt-Brown
fuente
en el caso extremo en el que moveYno se puede eliminar la intersección (porque hay otro muro en el camino), puede verificar nuevamente la intersección e intentar moveX o simplemente matarlo.
Chris Burt-Brown