¡Buenas!

Ya hace un par de semanas desde la última parte de esta serie, así que ya va siendo hora de continuarla con uno de los aspectos más importantes de todo videojuego: Las colisiones.

 

PARTE 5: Las colisiones (1)

Nuestro juego es sencillo, por lo que no me mataré haciendo comprobaciones de colisiones muy complejas. Lo reduciré todo a máscaras de colisión geométricas de dos tipos: Circulares y rectangulares. Principalmente porque son las figuras más sencillas de manejar.

 

DEFINICIÓN

Comencemos por lo más fundamental: ¿cómo definiremos las figuras geométricas en el juego?

 

1. Rectángulos y círculos

Usaremos dos funciones constructoras (lo que en otros lenguajes llamamos “clases”) que representarán a los rectángulos y los círculos, ambas funciones dentro de un mismo objeto que contendrá toda la geometría del juego. Además, cada figura tendrá ciertos parámetros que la definirán.

 

Definiremos los rectángulos por cuatro parámetros: La posición (x, y) del recángulo, y su tamaño (w, h).

Definiremos los círculos por tres parámetros: La posición (x, y) del centro del círculo, y su radio (r).

 

2. Funciones geométricas

Necesitaremos además dos funciones geométricas básicas: Una que nos permita calcular la distancia entre dos puntos, y otra que nos permita calcular el ánguo entre dos puntos.

 

3. Objeto Geometry

De acuerdo a los dos puntos anteriores, comenzaremos a programar nuestra geometría. Para ello, crearemos (y añadiremos al html) un nuevo script llamado geometry.js, para organizar mejor el código.

//todo esto va en geometry.js
//-----------OBJETO CONTENEDOR----------------
var Geometry = {

    //Las funciones constructoras de las figuras básicas
    rectangle: function(x, y, w, h) {
        this.left = x;
        this.top = y;
        this.width = w;
        this.height = h;
    },
    circle: function (x, y, r) {
        this.left = x;
        this.top = y;
        this.radius = r;
    },

    //Funciones normales para calcular distancia y ángulo entre dos puntos
    distance: function(x1, y1, x2, y2) {
        var vx = x2 - x1;
        var vy = y2 - y1;
        return Math.sqrt( vx*vx + vy*vy );
    },
    direction: function(x1, y1, x2, y2) {
        var vx = x2 - x1;
        var vy = y2 - y1;
        return Math.atan2( vy, vx );
    }
};

La forma en la que usamos estas funciones es algo así:

//Este código no irá en el juego, es sólo un ejemplo
var my_rect = new Geometry.rectangle(0, 0, 3, 4);
var my_circ = new Geometry.circle(1, 2, 3);

var dist = Geometry.distance(0, 0, 1, 1); //devuelve aprox. 1.41.
var dir = Geometry.direction(0, 0, 1, 1); //devuelve π/4.

 

COLISIONES

Viene la parte importante de este artículo: Crear una manera de comprobar si dos figuras se están solapando entre sí. Para ello, a los constructores circle y rectangle les añadiremos métodos para revisar una colisión con cada una de las entidades geométricas que usaremos en el juego: Un punto (no hice una clase para éste básicamente porque es muy simple), un círculo y un rectángulo.

 

1. Colisión con un punto

Para un círculo es muy fácil calcular esto: Se obtiene la distancia entre el centro del círculo y el punto deseado, y si ésta es menor que el radio, entonces HAY colisión.

Geometry.circle.prototype.collision_point = function (x, y) {
//Círculo colisiona con punto (x, y)
    return Geometry.distance( this.left, this.top, x, y ) < this.radius;
}

Para un rectángulo también es sencillo (aunque un tanto más complicado que el anterior): Las coordenadas (x,y) del punto deben ser mayores que las del rectángulo (left, top), y a su vez menores que la suma de las coordenadas y el tamaño del rectángulo (left + width, top + height).

Geometry.rectangle.prototype.collision_point = function(x, y) {
//Rectángulo colisiona con punto (x, y)
    return (
        (x > this.left) && 
        (x < this.left + this.width) && (y > this.top) &&
        (y < this.top + this.height)
    );
}

 

2. Colisión con otro de su mismo tipo

Cada figura, a su vez, debe ser capaz de detectar una colisión con otra figura de su mismo tipo; es decir, revisar si un círculo choca con otro círculo, o si un rectángulo choca con otro rectángulo.

 

Para comprobar si un círculo colisiona con otro, se debe obtener la distancia entre sus centros y después compararla con la suma de sus radios: Si la distancia es menor que la suma de sus radios, entonces HAY colisión.

Geometry.circle.prototype.collision_circle = function(circle){
//Círculo colisiona con círculo
    if(!(circle instanceof Geometry.circle)) {
        //Mostrar advertencia si no se pasa un círculo como parámetro
        console.warn('El valor argumentado NO es un círculo');
        return false;
    }

    var r_sum = this.radius + circle.radius;
    var p_dis = Geometry.distance( this.left, this.top, circle.left, circle.top );
    return r_sum > p_dis;

};

Por otra parte, para que dos rectángulos colisionen se deben cumplir cuatro condiciones:

  • La coordenada horizontal del primer retángulo debe ser menor que la suma de la coordenada horizontal y el anchor del segundo.
  • La suma de la coordenada horizontal y el anchor del primer rectángulo debe ser mayor que la coordenada horizontal del segundo.
  • La coordenada vertical del primer rectángulo debe estar arriba (debe ser menor) que la suma de la coordenada vertical y la altura del segundo.
  • La suma de la coordenada vertical y la altura del primer rectángulo debe estar abajo (debe ser mayor) que la coordenada vertical del segundo.
Geometry.rectangle.prototype.collision_rectangle = function(rect){
//Rectángulo colisiona con rectángulo
    if(! (rect instanceof Geometry.rectangle) ) {
        //Mostrar advertencia si no se pasa un rectángulo como parámetro
        console.warn('El valor argumentado NO es un rectángulo');
        return false;
    }

    var t_right  = this.left + this.width;
    var t_bottom = this.top  + this.height;
    
    var r_right  = rect.left + rect.width;
    var r_bottom = rect.top  + rect.height;

    return (
        (this.left < r_right) && (t_right > rect.left) &&
        (this.top < r_bottom) && (t_bottom > rect.top)
    );
};

 

3. Colisión con figuras de tipo distinto

La última comprobación es la de una colisión círculo-rectángulo. Ésta es algo más compleja, pero tampoco nada del otro mundo: Lo primero que hay que hacer es obtener el punto DENTRO del rectángulo más cercano al centro del círculo. Después, calcularemos la distancia desde el centro del círculo a este punto, y si el resultado es menor al radio, entonces HAY colisión.

Geometry.rectangle.prototype.collision_circle = function(circle) {
//Rectángulo colisiona con círculo

    if(! (circle instanceof Geometry.circle) ) {
        //Mostrar advertencia si no se pasa un círculo como parámetro
        console.warn('El valor argumentado NO es un círculo');
        return false;
    }
    // Limitar un valor a cierto rango
    function clamp(val, min, max) {
        return Math.max(min, Math.min(max, val))
    }
    var border_x = clamp(circle.left, this.left, this.left + this.width);
    var border_y = clamp(circle.top, this.top, this.top + this.height);

    var border_dist = Geometry.distance( circle.left, circle.top, border_x, border_y );
    return border_dist < circle.radius;
}

Con este código haremos algo especial: Lo añadiremos también al constructor del círculo, de esta forma:

Geometry.circle.prototype.collision_rectangle = function(rectangle) {
//Círculo colisiona con rectángulo
    if(! (rectangle instanceof Geometry.rectangle)) {
        //Mostrar advertencia si no se pasa un rectángulo como parámetro
        console.warn('El valor argumentado NO es un rectángulo');
        return false;
    }
    return rectangle.collision_circle(this);
}

De modo en que una comprobación rectángulo-círculo me dé exactamente el mismo resultado que una círculo-rectángulo.

 


 

De esta manera, hemos programado la “base” de nuestro sistema de colisiones: Las comprobaciones geométricas.

 

Este artículo no tendrá un efecto visible en el juego, porque básicamente no hemos modificado nada en él, pero aún así dejo el código fuente de lo que se hizo: Click aquí.

 

¡Y esto fue todo por hoy! Ya me cansé de escribir :v. En próximas ocasiones integraré estas comprobaciones geométricas a algo que tenga efecto de verdad en el juego.

 

¡Saludos!