Colisiones entre objetos en dos dimensiones

En muchos juegos 2D se utilizan sprites, es decir, imágenes prediseñadas que se dibujan y mueven por la pantalla para representar a los personajes y demás elementos del juego. Además, suele ser necesario detectar cuando un elemento colisiona con otro para, por ejemplo, quitar vidas, destruirlo, o símplemente sujetarse en una plataforma sin caerse.

Detectar cuando realmente se están tocando dos sprites requeriría comprobar si cada pixel visible (no los transparentes que suelen rodear al dibujo) de un sprite está en contacto con algún pixel visible del otro sprite con lo que, computacionalmente hablando, requeriría mucho tiempo.

Para poder detectar colisiones de una forma eficiente normalmente se recurre a suponer que cada sprite está inscrito en alguna figura geométrica que, aunque sea más grande que el sprite, permita aproximar su forma. Esto simplifica la detección de la colisiones entre elementos pues basta con hacer unos pocos cálculos matemáticos para determinar si las distintas figuras geométricas están solapándose o no.

Se suelen usar círculos, rectángulos o incluso combinaciones de ambas para representar de forma aproximada el área ocupada por cada sprite como se puede ver en las siguietes imágenes.

Aproximación mediante círcunferencias
Aproximación mediante rectángulos
Aproximación mediante combinación de círcunferencias y rectángulos

Colisiones entre círculos

Si se decide usar círculos para representar el área ocupada por los elementos del juego necesitaremos conocer el centro y el radio del círculo asociado a cada elemento. Se debe elegir el menor radio cuyo círculo permita cubrir todo el elemento. La colisión se produce si la distancia entre centros es menor que la suma de los radios.

int r1 = 50; int x = 160; int y = 160; int r2 = 20; void frame() { background(0); stroke(255,255,255); noFill(); ellipse( x, y, r1*2, r1*2); line( 160,160, mouseX, mouseY ); ellipse( mouseX, mouseY, r2*2, r2*2 ); double d = sqrt( pow( mouseX-x, 2) + pow( mouseY-y, 2) ); stroke(255); fill(255); text("r1 = 50 r2 = 20 distancia = " + round(d), 20, 20); if ( d < r1 + r2 ) { stroke(255,255,255); fill(255,0,0); ellipse( x, y, r1*2, r1*2 ); ellipse( mouseX, mouseY, r2*2, r2*2 ); } } void main() { animate(frame); }

 


// círculo 1 con centro en (cx1,cy1) y radio r1
// círculo 2 con centro en (cx2,cy2) y radio r2
distancia = sqrt( (cx1 - cx2)*(cx1 - cx2) + (cy1 - cy2)*(cy1 - cy2) );
if ( distancia < r1 + r2 ) {
	// Colisión detectada
}
		

Colisiones entre rectángulos

Hay muchas posiciones en las que dos rectángulos se solapan por lo que, normalmente, se comprueban sólo las cuatro situaciones en las que seguro que no se solapan y si ninguna se cumple es porque hay solape. La idea es simple, si un rectángulo está más a la derecha que la parte más a la derecha del otro, es imposible que estén solapados. Y esta comprobación se puede hacer comparando sólo las coordenadas horizontales de ambos rectángulos. Lo mismo podemos hacer para comprobar que por la izquierda no hay solape, y de forma similar por arriba y por abajo. Cuatro comparaciones simples que, de ser cierta alguna hace innecesarias el resto de comprobaciones.

int w1 = 100; int h1 = 80; int x1 = 160 - w1/2; int y1 = 160 - h1/2; int w2 = 80; int h2 = 40; int x2 = 0; int y2 = 0; void frame() { background(0); stroke(255,255,255); noFill(); x2 = mouseX; y2 = mouseY; rect( x1, y1, w1, h1); rect( x2, y2, w2, h2); if ( !( x1 > x2+w2 || x1+w1 < x2 || y1 > y2+h2 || y1+h1 < y2 ) ) { stroke(255,255,255); fill(255,0,0); rect( x1, y1, w1, h1); rect( x2, y2, w2, h2); } } void main() { animate(frame); }

 


// Rectángulo 1 con esquina superior izquierda en (x1,y1) ancho w1 y alto h1
// Rectángulo 2 con esquina superior izquierda en (x2,y2) ancho w2 y alto h2
Si ( x1 > x2+w2 ) ==> No hay colisión
Si ( x1+w1 < x2 ) ==> No hay colisión
Si ( y1 > y2+h2 ) ==> No hay colisión
Si ( y1+h1 < y2 ) ==> No hay colisión
En otro caso ==> Hay colisión
		

Colisiones entre un círculo y un rectángulo

La colisión entre un círculo y un rectángulo se produce cuando la distancia entre el centro del círculo (cx,cy) y el punto del perímetro del rectángulo (px,py) que esté más cerca del mismo sea menor que el radio (R) de el círculo. Ejecuta el programa que tienes abajo y observa como px es igual a cx siempre que cx esté en el intervalo definido por el propio lado mayor del rectángulo. Asumiendo que la esquina superior izquierda del rectángulo está en (x,y) y que la anchura del mismo es w y su altura h, tendremos que x <= px <= x + w.

Del mismo modo podemos calcular la coordenada vertical py, y una vez hecho sólo queda calcular la distancia a (cx,cy) y comprobar si esta es menor que el radio del círculo, en cuyo caso, habremos detectado la colisión.

int cx; int cy; int r = 20; int w = 150; int h = 130; int x = 160 - w/2; int y = 160 - h/2; void frame() { cx = mouseX; cy = mouseY; background(0); stroke(255,255,255); noFill(); rect( x, y, w, h); ellipse( cx, cy, r*2, r*2 ); int px = cx; // En principio son iguales if ( px < x ) px = x; if ( px > x + w ) px = x + w; int py = cy; if ( py < y ) py = y; if ( py > y + h ) py = y + h; ellipse( cx, cy, 2,2); stroke(255,0,0); line( px, py, cx, cy ); fill( 0, 255, 0 ); stroke(0,255,0); ellipse( px, py, 3, 3); double d = sqrt( pow( cx - px, 2) + pow( cy - py, 2) ); if ( d < r ) { stroke(255,255,255); fill(255,0,0); rect( x, y, w, h); ellipse( cx, cy, r*2, r*2 ); } } void main() { animate(frame); }

 


// Círculo con centro en (cx,cy) y radio r
// Rectángulo con esquina superior izquierda en (x,y) ancho w y algo h
// Punto (en verde) del perímetro del rectángulo más cercano a la circunferencia en (px,py)
px = cx; // En principio son iguales
if ( px < x ) px = x;
if ( px > x + w ) px = x + w;
py = cy;
if ( py < y ) py = y;
if ( py > y + h ) py = y + h;
distancia = sqrt( (cx - px)*(cx - px) + (cy - py)*(cy - py) );
if ( distancia < r ) {
	// Colisión detectada
}