Estoy haciendo un juego que consta de muchos objetos en pantalla, uno de los cuales es el jugador. Necesito saber qué objetos están colisionando en cada iteración.
Hice algo como esto:
for (o in objects)
{
o.stuff();
for (other in objects)
if (collision(o, other))
doStuff();
bla.draw();
}
Esto tiene O (n ^ 2), que me han dicho que es malo. ¿Cómo hago esto de manera más eficiente? ¿Es posible? Estoy haciendo esto en Javascript, y n generalmente será inferior a 30, ¿será un problema si esto sigue igual?
Respuestas:
Con solo 30 objetos como máximo, no debería necesitar mucha optimización para no comparar los mismos dos pares entre sí más de una vez por cuadro. Que cubrirá el siguiente ejemplo de código. Pero si le interesan las diferentes optimizaciones que usaría un motor de física, continúe leyendo el resto de esta publicación.
Lo que necesitará es una implementación de partición espacial , como un Octree (para juegos 3D) o Quadtree (para juegos 2D). Estos dividen el mundo en subsecciones, y luego cada subsección se divide más en la misma mansión, hasta que se hayan subdividido en un tamaño mínimo. Esto le permite verificar rápidamente qué otros objetos están en la misma región del mundo que otro, lo que limita la cantidad de colisiones con las que debe verificar.
Además de la partición espacial, recomendaría crear un AABB ( cuadro delimitador alineado con el eje ) para cada uno de sus objetos de física. Esto le permite verificar el AABB de un objeto contra otro, lo cual es mucho más rápido que una verificación detallada por poli entre objetos.
Esto puede llevarse un paso más allá para objetos físicos complicados o grandes, donde puede subdividir la malla física en sí, dando a cada subforma su propio AABB con el que puede verificar solo si los AABB de dos objetos se superponen.
La mayoría de los motores de física desactivarán la simulación física activa en los cuerpos físicos una vez que se detengan. Cuando un cuerpo de física está desactivado, solo necesita verificar la colisión contra su AABB en cada cuadro, y si algo choca con el AABB, entonces se reactivará y realizará una verificación de colisión más granular. Esto mantiene bajos los tiempos de simulación.
Además, muchos motores de física usan 'islas de simulación', que es donde se agrupa un grupo de cuerpos de física que están muy juntos. Si todo en la isla de simulación está en reposo, entonces la misma isla de simulación está desactivada. El beneficio de la isla de simulación es que todos los cuerpos dentro de ella pueden dejar de verificar colisiones una vez que la isla está inactiva, y la única verificación de cada cuadro es ver si algo ingresó al AABB de la isla. Solo una vez que algo ingrese al AABB de la isla, cada uno de los cuerpos dentro de la isla deberá verificar las colisiones. La isla de simulación también se reactiva si algún cuerpo dentro de ella comienza a moverse nuevamente por sí solo. Si un cuerpo se mueve lo suficientemente lejos del centro del grupo, se elimina de la isla.
Al final te queda algo como esto (en pseudocódigo):
También recomendaría no tener tantos bucles dentro de bucles como este, la muestra anterior fue solo para que usted tenga la idea, lo dividiría en múltiples funciones que le brindan la misma funcionalidad que algo como lo que se muestra arriba.
Además, asegúrese de no alterar el contenedor AABBNodes mientras lo recorre, ya que eso podría significar verificaciones de colisión perdidas. Esto puede sonar a sentido común, pero se sorprendería de lo fácil que es hacer que las cosas reaccionen a colisiones que causan cambios que no anticiparía. Por ejemplo, si una colisión provoca que uno de los objetos en colisión cambie de posición lo suficiente como para eliminarlos del AABB del nodo Octree que estaba comprobando, podría alterar ese contenedor. Para resolver esto, recomiendo mantener una lista de todos los eventos de colisión que ocurren durante las verificaciones, y luego, después de completar todas las verificaciones, revise la lista y envíe cualquier evento de colisión.
fuente
Su ejemplo prueba cada par de objetos varias veces.
Tomemos un ejemplo muy simple con una matriz que contiene 0,1,2,3
Con tu código obtienes esto:
Ahora veamos el siguiente código:
Si usamos la matriz que contiene 0,1,2,3 una vez más, tenemos el siguiente comportamiento:
Con el segundo algoritmo tenemos 6 pruebas de colisión, mientras que el anterior solicitó 12 pruebas de colisión.
fuente
N(N-1)/2
comparaciones que todavía es el rendimiento O (N ^ 2).Diseñe su algoritmo según sus necesidades, pero mantenga los detalles de implementación encapsulados. Incluso en Javascript, se aplican los conceptos básicos de OOP.
Porque
N =~ 30, O(N*N)
no es una preocupación, y es probable que su búsqueda lineal sea tan rápida como cualquier otra alternativa. Pero no desea codificar suposiciones en su código. En pseudocódigo, tendrías una interfazEso describe lo que puede hacer su lista de artículos. Luego puede escribir una clase ArrayContainer que implemente esta interfaz. En Javascript, el código se vería así:
Y aquí hay un código de ejemplo que crea 300 cuadros de límite y obtiene todas las intersecciones. Si ha implementado ArrayContainer y QuadTreeContainer correctamente, lo único que necesitaría cambiar en su código es cambiar
var allMyObjects = new ArrayContainer()
avar allMyObjects = QuadTreeContainer()
.Seguí adelante y agité la implementación del ArrayContainer estándar aquí:
http://jsfiddle.net/SKkN5/1/
fuente
También debe considerar los tipos de objetos que pueden chocar sensiblemente.
Por ejemplo, es probable que el jugador deba verificarse por colisión con todo excepto sus propias balas. Sin embargo, los enemigos solo pueden necesitar comprobar contra las balas del jugador. Las balas casi seguramente no necesitan chocar entre sí.
Para implementar esto de manera eficiente, probablemente desee mantener listas separadas de objetos, una por tipo de objeto.
fuente