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.
fuente
Respuestas:
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
onGround
propiedad 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:
a esto:
(También modifiqué algunas de las constantes relacionadas con la física, pero esto no debería importar).
Quizás ordenar
bodiesToCheck
el 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.fuente
Position = nextPosition
inmediatamente dentro delfor
bucle; de lo contrario,onGround
aú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, poronGround
lo tanto , nunca debe establecerse. Así es como lo hace la demostración XNA, y no puedo reprobar el error "techo" allí.onGround
se configura, por lo que no puedo investigar por qué el salto está incorrectamente permitido. La demostración de XNA actualiza suisOnGround
propiedad análoga en su interiorHandleCollisions()
. Además, después de establecerloVelocity.Y
en 0, ¿por qué la gravedad no comienza a mover al jugador hacia abajo nuevamente? Supongo que laonGround
propiedad está configurada incorrectamente. Eche un vistazo a cómo se actualiza la demostración XNApreviousBottom
yisOnGround
(IsOnGround
).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í:
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:
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.
fuente
if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
Editar
Lo he mejorado
Parece que lo clave que cambié fue clasificar las intersecciones.
Las intersecciones son:
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 )
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.
Por este método, el jugador ya no quedará atrapado en los lados de las paredes.
fuente
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))
fuente
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
fuente
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.
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:
(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.
fuente
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:
¡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
moveX
ymoveY
funciones 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.
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.
fuente
moveY
no 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.