Colisiones en videojuegos

En este artículo vamos a explicar cómo detectar colisiones en nuestros videojuegos de una forma rápida, optimizada y, al mismo tiempo, realista. La detección de colisiones es una de las partes más importantes de cualquier juego: según qué objetos colisionen, deberemos ejecutar unas acciones u otras. Esto implica que en cada ciclo del bucle de animación tenemos que comprobar las colisiones entre casi todos los objetos de la pantalla.

Incluso en un videojuego aparentemente sencillo, como un Asteroids, es fácil tener 10 disparos y 8 fragmentos de roca moviéndose al mismo tiempo. Eso supone 80 comprobaciones por ciclo que se convierten en 4.800 comprobaciones por segundo a 60 fps. El número crece enseguida y lo peor es que, la mayor parte del tiempo, los objetos no están chocando entre sí, lo que se traduce en un cálculo completamente innecesario y un gasto de rendimiento absurdo.

Por este motivo debemos buscar una forma precisa y eficiente de comprobar las colisiones en nuestros videojuegos, evitando operaciones costosas y priorizando siempre el rendimiento. Por suerte, los objetos gráficos suelen estar definidos dentro de un área rectangular en pantalla, lo cual facilita mucho los cálculos.

Colisión básica

Dado que la mayoría de las veces los objetos no están colisionando, una solución clásica consiste en realizar primero un cálculo muy rápido para descartar colisiones, evitando al máximo operaciones matemáticas complejas. Para ello, podemos crear una función básica, directa y extremadamente rápida que detecte las colisiones entre dos objetos de nuestros videojuegos, basándose únicamente en su área rectangular.

  function collision(obj1, obj2) {
    return obj1.x < obj2.x + obj2.width && obj1.x + obj1.width > obj2.x &&
               obj1.y < obj2.y + obj2.height && obj1.y + obj1.height > obj2.y;
  }

Esta función collision(obj1, obj2) asume que los objetos tienen definida su área rectangular mediante las variables (x, y, width, height). Es la forma más rápida de comprobar si dos objetos colisionan, verificando que no están separados en ninguno de sus ejes. Para ello, evaluamos simultáneamente los cuatro lados de ambos rectángulos.

Si alguna de estas comprobaciones es falsa, significa que los objetos están completamente separados en al menos uno de los ejes, por lo que no existe colisión. Si todas las condiciones se cumplen, los rectángulos se solapan en alguna zona, aunque sea una mínima intersección: en ese caso sí hay colisión.

Con esta función podemos detectar las colisiones de nuestros videojuegos de forma muy rápida y con un número mínimo de operaciones. Sin embargo, presenta un inconveniente: cuando los objetos solo se tocan por las esquinas, puede producir colisiones poco realistas. Sobre todo si usamos imágenes que no ocupan todo su rectángulo. Por ejemplo, dos círculos dentro de sus rectángulos pueden “chocar” aunque solo rocen sus esquinas gráficas, creando una mala experiencia de colision.

Colisiones básicas en videojuegos
Colisión básica entre dos objetos

Para resolver este problema y conseguir colisiones más realistas vamos a crear otra función más precisa que refine la detección de colisiones.

Colisión compleja

La siguiente función hit(obj1, obj2) se encarga de detectar colisiones en nuestros videojuegos de forma realista utilizando un círculo «imaginario» dentro del área rectangular de cada objeto. Esto evita el problema de las colisiones poco realistas de las esquinas cuando usamos el rectángulo.

function hit(obj1, obj2) {
    if (!collision(obj1, obj2)) return false;

    // Radio de cada objeto
    let r1 = obj1.width >> 1;
    let r2 = obj2.width >> 1;

    // Centro de cada objeto
    let cx1 = obj1.x + r1;
    let cy1 = obj1.y + r1;
    let cx2 = obj2.x + r2;
    let cy2 = obj2.y + r2;

    // Distancia (x, y) entre los centros
    let dx = cx1 - cx2;
    let dy = cy1 - cy2;
    let dist = Math.sqrt(dx * dx + dy * dy);

    // Colisión si la distancia es menor que la suma de los radios
    return dist < (r1 + r2);
}

Lo primero que hacemos es llamar a collision(obj1, obj2). Si los rectángulos ni siquiera se tocan, ya hemos terminado: devolvemos false sin más cálculos. Si los rectángulos se solapan, entonces tiene sentido hacer una comprobación más precisa. Para ello:

  • Calculamos el radio de cada objeto.
  • Obtenemos el centro de cada objeto.
  • Calculamos la distancia (x, y) entre los centros usando Pitágoras.
  • Si esa distancia es menor que la suma de los radios, las esferas se están tocando: hay colisión.
Colisiones complejas en videojuegos
Colisión compleja entre dos objetos

Este método es muy rápido y suficientemente preciso para la mayoría de juegos. Evita colisiones irreales por esquinas, reduce cálculos innecesarios y sigue siendo extremadamente eficiente.

Colisiones en videojuegos

Las colisiones en los videojuegos es un aspecto muy importante y que se realiza prácticamente en cada ciclo de animación. Por este motivo es fundamental optimizar los cálculos y no malgastar el rendimiento de la CPU, manteniendo una tasa de fotogramas por segundo lo más alta posible.

En este artículo hemos aprendido a utilizar una primera función ultrarápida para descartar colisiones en nuestros videojuegos con áreas rectangulares y, sólo si es necesario, realizar un cálculo más preciso mediante esferas. Este es uno de los pilares en cuanto a la optimización de colisiones en videojuegos y por eso muchos juegos crean gráficos a partir de sprites «cuadrados».

En algunos juegos se usan elipses en lugar de círculos para ajustar todavía más la colisión, ya que un elipse permite tener un “radio” distinto en el eje X y en el eje Y. Es un cálculo más complejo y normalmente innecesario, por lo que en este artículo nos quedamos con la versión circular, que es más rápida y suficiente para la mayoría de gráficos 2D.

En videojuegos complejos hay más técnicas dedicadas a optimizar el tema de las colisiones y mejorar el rendimiento, pero eso lo abordaremos en futuros artículos.

¡ Espero que este artículo sea de vuestro interés !

Deja un comentario