When Worlds Collide simulando colisiones círculo-círculo

La mayor parte de la detección de colisiones en los juegos de computadora se realiza mediante la técnica AABB: muy simple, si dos rectángulos se intersecan, se ha producido una colisión. Es rápido, eficiente e increíblemente eficaz para objetos rectangulares. Pero ¿y si quisiéramos romper círculos juntos? ¿Cómo calculamos el punto de colisión y hacia dónde van los objetos? No es tan difícil como podrías pensar ...

Nota: Aunque este tutorial está escrito con AS3 y Flash, debería poder utilizar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos..

Vista previa de la imagen tomada de este tutorial clásico de Psdtuts +.


Paso 1: Crea algunas bolas

Voy a pasar por alto este paso, porque si no puedes crear sprites básicos, el resto del tutorial será un poco más allá de ti..

Basta con decir que tenemos un punto de partida. A continuación se muestra una simulación muy rudimentaria de bolas que rebotan alrededor de una pantalla. Si tocan el borde de la pantalla, rebotan.

Sin embargo, hay una cosa importante a tener en cuenta: a menudo, cuando creas sprites, la parte superior izquierda se establece en el origen. (0, 0) y la parte inferior derecha es (ancho, alto). Aquí, los círculos que hemos creado están centrados en el sprite..

Esto hace que todo sea mucho más fácil, ya que si los círculos no están centrados, para más o menos cada cálculo tenemos que compensarlo por el radio, realizar el cálculo y luego restablecerlo.

Puede encontrar el código hasta este punto en el v1 carpeta de la fuente de descarga.


Paso 2: Verificar si hay superposiciones

Lo primero que queremos hacer es verificar si nuestras bolas están cerca unas de otras. Hay algunas maneras de hacer esto, pero como queremos ser buenos programadores, vamos a comenzar con un chequeo de AABB..

AABB representa el cuadro delimitador alineado con el eje y se refiere a un rectángulo dibujado para ajustarse perfectamente a un objeto, alineado de modo que sus lados estén paralelos a los ejes..

Un cheque de colisión AABB hace no verifique si los círculos se superponen entre sí, pero nos avisa si están cerca El uno al otro. Ya que nuestro juego solo usa cuatro objetos, esto no es necesario, pero si estuviéramos ejecutando una simulación con 10,000 objetos, hacer esta pequeña optimización nos ahorraría muchos ciclos de CPU.

Así que, aquí vamos:

 if (firstBall.x + firstBall.radius + secondBall.radius> secondBall.x && firstBall.x < secondBall.x + firstBall.radius + secondBall.radius && firstBall.y + firstBall.radius + secondBall.radius > secondBall.y && firstBall.y < seconBall.y + firstBall.radius + secondBall.radius)  //AABBs are overlapping 

Esto debería ser bastante sencillo: estamos estableciendo cuadros delimitadores del tamaño del diámetro cuadrado de cada bola.

Aquí, se ha producido una "colisión", o más bien, las dos AABB se superponen, lo que significa que los círculos están cerca uno del otro, y potencialmente chocan.

Una vez que sabemos que las bolas están cerca, podemos ser un poco más complejos. Usando la trigonometría, podemos determinar la distancia entre los dos puntos:

 distance = Math.sqrt (((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ; si (distancia) < firstBall.radius + secondBall.radius)  //balls have collided 

Aquí, estamos usando el teorema de Pitágoras, a ^ 2 + b ^ 2 = c ^ 2, Para averiguar la distancia entre los centros de los dos círculos..

No sabemos inmediatamente las longitudes de una y segundo, pero sí sabemos las coordenadas de cada bola, por lo que es trivial hacer ejercicio:

 a = firstBall.x - secondBall.x; b = firstBall.y - secondBall.y;

Entonces resolvemos para do Con un poco de reordenamiento algebraico: c = Math.sqrt (a ^ 2 + b ^ 2) - De ahí esta parte del código:

 distance = Math.sqrt (((firstBall.x - secondBall.x) * (firstBall.x - secondBall.x)) + ((firstBall.y - secondBall.y) * (firstBall.y - secondBall.y))) ;

Luego verificamos este valor contra la suma de los radios de los dos círculos:

 si (distancia) < firstBall.radius + secondBall.radius)  //balls have collided 

¿Por qué comprobamos contra los radios combinados de los círculos? Bueno, si miramos la imagen de abajo, podemos ver que, sin importar en qué ángulo estén tocando los círculos, si están tocando la línea. do es igual a r1 + r2.

Así que si do es igual o menor que r1 + r2, Entonces los círculos tienen que estar tocando. Sencillo!

Tenga en cuenta también que, para calcular correctamente las colisiones, es probable que desee mover todos sus objetos primero y luego realizar la detección de colisiones en ellos. De lo contrario, puede tener una situación en la que Bola1 actualizaciones, comprobaciones de colisiones, colisiones, luego Bola2 actualizaciones, ya no está en la misma área que Bola1, e informa de ninguna colisión. O, en términos de código:

 para (n = 0; n 

es mucho mejor que

 para (n = 0; n   

Puede encontrar el código hasta este punto en el v2 carpeta de la fuente de descarga.


Paso 3: Calcular los puntos de colisión

Esta parte no es realmente necesaria para colisiones de bolas, pero es bastante buena, así que la estoy lanzando. Si solo quieres que todo rebote, siéntete libre de saltar al siguiente paso.

A veces puede ser útil determinar el punto en el que dos bolas han chocado. Si quieres, por ejemplo, agregar un efecto de partículas (tal vez una pequeña explosión), o estás creando algún tipo de guía para un juego de billar, entonces puede ser útil conocer el punto de colisión..

Hay dos maneras de resolver esto: la manera correcta y la manera incorrecta.

La forma incorrecta, que utilizan muchos tutoriales, es promediar los dos puntos:

 collisionPointX = (firstBall.x + secondBall.x) / 2 collisionPointY = (firstBall.y + secondBall.y) / 2

Esto funciona, pero solo si las bolas son del mismo tamaño..

La fórmula que queremos usar es un poco más complicada, pero funciona para bolas de todos los tamaños:

 collisionPointX = ((firstBall.x * secondBall.radius) + (secondBall.x * firstBall.radius)) / (firstBall.radius + secondBall.radius); collisionPointY = ((firstBall.y * secondBall.radius) + (secondBall.y * firstBall.radius)) / (firstBall.radius + secondBall.radius);

Esto utiliza el radio de las bolas para darnos las verdaderas coordenadas x e y del punto de colisión, representado por el punto azul en la demostración de abajo..

Puede encontrar el código hasta este punto en el v3 carpeta de la fuente de descarga.


Paso 4: Bouncing Apart

Ahora, sabemos cuándo nuestros objetos chocan entre sí, y conocemos su velocidad y sus ubicaciones x e y. ¿Cómo podemos averiguar dónde viajan a continuación??

Podemos hacer algo llamado colisión elástica.

Tratar de explicar con palabras cómo funciona una colisión elástica puede ser complicado: la siguiente imagen animada debería aclarar las cosas..


Imagen de http://en.wikipedia.org/wiki/Elastic_collision

Simplemente, estamos usando más triángulos.

Ahora, podemos determinar la dirección que toma cada bola, pero puede haber otros factores en juego. Giro, fricción, el material con el que están hechas las bolas, masa y muchos otros factores que se pueden aplicar para hacer la colisión "perfecta". Solo vamos a preocuparnos por uno de estos: masa..

En nuestro ejemplo, vamos a asumir que el radio de las bolas que estamos usando también es su masa. Si estuviéramos luchando por el realismo, esto sería incorrecto ya que, suponiendo que todas las bolas estuvieran hechas del mismo material, la masa de las bolas sería proporcional a su área o volumen, dependiendo de si deseaba considerarlas o no. Discos o esferas. Sin embargo, dado que este es un juego simple, su radio será suficiente.

Podemos usar la siguiente fórmula para calcular el cambio en la velocidad x de la primera bola:

 newVelX = (firstBall.speed.x * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.x)) / (firstBall.mass + secondBall.mass);

Entonces, vamos a repasar esto simplemente:

  • Supongamos que ambas bolas tienen la misma masa (digamos 10).
  • La primera bola se mueve a 5 unidades / actualización (a la derecha). La segunda bola se está moviendo -1 unidades / actualización (a la izquierda).
 NewVelX = (5 * (10-10) + (2 * 10 * -1)) / (10 + 10) = (5 * 0) + (-20) / 20 = -20/20 = -1

En este caso, suponiendo una colisión frontal, la primera bola comenzará a moverse a -1 unidad / actualización. (A la izquierda). Debido a que las masas de las bolas son iguales, la colisión es directa y no se ha perdido energía, las bolas tendrán velocidades de "intercambio". Cambiar cualquiera de estos factores obviamente cambiará el resultado.

(Si usa el mismo cálculo para calcular la nueva velocidad de la segunda bola, encontrará que se está moviendo a 5 unidades / actualización, a la derecha).

Podemos usar esta misma fórmula para calcular las velocidades x / y de ambas bolas después de la colisión:

 newVelX1 = (firstBall.speed.x * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.x)) / (firstBall.mass + secondBall.mass); newVelY1 = (firstBall.speed.y * (firstBall.mass - secondBall.mass) + (2 * secondBall.mass * secondBall.speed.y)) / (firstBall.mass + secondBall.mass); newVelX2 = (secondBall.speed.x * (secondBall.mass - firstBall.mass) + (2 * firstBall.mass * firstBall.speed.x)) / (firstBall.mass + secondBall.mass); newVelY2 = (secondBall.speed.y * (secondBall.mass - firstBall.mass) + (2 * firstBall.mass * firstBall.speed.y)) / (firstBall.mass + secondBall.mass);

Esperemos que sea evidente que cada cálculo es el mismo, simplemente reemplazando los valores en consecuencia.

Una vez hecho esto, tenemos las nuevas velocidades de cada bola. Entonces, ¿hemos terminado? No exactamente.

Anteriormente, nos aseguramos de hacer todas las actualizaciones de nuestra posición a la vez, y entonces comprobar si hay colisiones. Esto significa que cuando verificamos si hay bolas en colisión, es muy probable que una bola esté "en" otra, por lo que cuando se llama a la detección de colisión, tanto la primera bola como la segunda registrarán esa colisión, lo que significa que nuestros objetos pueden obtener atrapados juntos.

(Si las bolas se dirigen juntas, la primera colisión invertirá sus direcciones, por lo que se separarán, y la segunda colisión invertirá su dirección nuevamente, haciendo que se muevan juntas).

Hay varias formas de lidiar con esto, como la implementación de un Booleano que verifica si las bolas ya han chocado con este marco, pero la forma más sencilla es simplemente mover cada bola por la nueva velocidad. Esto significa, en principio, que las bolas deben moverse por la misma velocidad que se movieron juntas, colocándolas a una distancia igual a la del cuadro antes de que colisionen..

 firstBall.x = firstBall.x + newVelX1; firstBall.y = firstBall.y + newVelY1; secondBall.x = secondBall.x + newVelX2; secondBall.y = secondBall.y + newVelY2;

Y eso es!

Puedes ver el producto final aquí:

Y el código fuente hasta esta parte está disponible en el v4 carpeta de la fuente de descarga.

¡Gracias por leer! Si desea obtener más información sobre los métodos de detección de colisiones por sí mismos, consulte esta sesión. También te puede interesar este tutorial sobre quadtrees y este tutorial sobre la prueba del eje de separación..