theDarkDesigns (c) 2003
Detección
de colisión en tile based games
(continuación del tutorial escrito por Silencer) Por
Leandro Pelorosso (Wolfgang)
Este tutorial procurará ser la segunda parte del creado por Silencer
de theDarkDesigns sobre Tile based games. El mismo dejó
inconclusa una parte fundamental de todo juego: el jugador y la colisión.
Este será el primer tutorial que escribo, asi que disculpen
si no llega a ser del todo entendible o agradable, prometo hacer mi mejor
esfuerzo.
Antes de comenzar con los bloques, vamos a hablar un rato del mapa.
Es altamente recomendable haber leído anteriormente el tutorial
citado.
Nuestro mapa es en realidad una matriz, una grilla, en cuyas
componentes se me dice que clase de bloque hay, en caso de que haya uno,
por ejemplo:
Donde los '1' representan un bloque fisico, digno de ser contemplado
en la colisión, y los '0' bloques vacios.
Entonces, tenemos que la grilla es una matriz, de, por ejemplo,
chars.
char
mapa[MAP_HEIGHT][MAP_WIDTH]; // el mapa!
donde MAP_HEIGHT
y MAP_WIDTH
serán enteros, indicando el ancho del mapa. Definimos entonces:
const
MAP_HEIGHT =16;//Alto en bloques del mapa
const
MAP_WIDTH =20; //Ancho en bloques del mapa
Bien, vamos a suponer que ya tenemos el mapa almacenado en la matriz,
(cosa explicada en el tutorial anterior, si tenes dudas hechale un ojo
al fuente de ejemplo o consultame, es muy sencillo en verdad)
Comenzaremos viendo como incorporar a un personaje en la escena, y
luego veremos que hacemos con la colisión.
Para almacenar los datos del juegador, utilizaremos una estructura
con algunos miembros. Pero como uso algunas estructuras a modo de variables,
las defino antes:
struct
point2f
{
float
x,y;
};
-------------
struct
point2i
{
int
x,y;
};
-------------
struct
datablock
{
point2i
_pos;
};
Ahora si, definimos la estrucuta del jugador.
struct
s_player //estructura jugador
{
point2f
pos; // la posicion de nuestro cadrado jugador
en pantalla
point2f
center; //el centro de nuestro cuadrado del
juegador
float
p_dX,p_dY; //desplazamientos, ya veran
para que son :p
point2i
in_matrix; //la posicion de nuestro jugador
en la matriz
datablock
data_block[8]; //numero de bloques que tenemos
alrededor
float
speed; //velocidad de nuestro jugador
};
Ya todo tendrá mucho sentido :) .
Como seguro estarás pensando, el jugador es simplemente un bloquecito
mas, que lo dibujamos aparte, entonces para graficarlo es muy facil, es
como cuando dibujas el mapa. Mirá:
drawQuad(player.pos.x,
player.pos.y,b_width,b_height);
Definiendo anteriormente:
float
b_width = 16; //Ancho en pixels del bloque
float
b_height = 15; //Alto en pixels del bloque
Si estas pensando : "¿y este de donde sacó esta porquería?,
Estas en tu derecho. drawQuad es una función que dibuja un bloque
en la posición indicada por los primeros dos parametros, y del ancho
y alto indicado en pixeles en los dos parametros restantes. Es importande
decir que player.pos.x y player.pos.y corresponden a la esquina superior
izquierda del jugador. Para que quede mas claro:
- "wiii!, ya tenemos dibujado al
personaje en la escena...."
- "pero che.... no se mueve, ¿de
que me sirve así?"
- "es muy cierto eso.. ahi vamos!"
Ahora que tenemos al personaje en la escena, necesitamos ver como hacemos
para manejar la entrada de datos, vamos a usar eventos similares a key_down
y key_up en Visual basic. ( si estas programando en VB, usá justamente
esos).
void
pressKey(int key, int x, int y) //equivalente
a key_down en VB.
{
switch
(key) //aca vemos que apretamos.
{
case GLUT_KEY_LEFT : key_left=1;break; //si
apretamos para la izquierda
case GLUT_KEY_RIGHT : key_right=1;break; //si
apretamos para la derecha
case GLUT_KEY_UP : key_up=1;break; //si apretamos
para arriba
case GLUT_KEY_DOWN : key_down=1;break; //si
apretamos para abajo
}
}
Lo que dice aqui es basicamente esto: si apretaste la flechita
para la izquierda, entonces, seteamos key_left en 1, si apretaste la de
la derecha, entonces ponemos key_right en 1, y así con las demas.
Si estas programando en Visual Basic, Lo que tenes que haces es, en
el evento Key_down, hacer cuatro if, o un select case, por ejemplo :
if
KeyCode = vbKeyLeft Then key_left =1
if
KeyCode = vbKeyRight Then key_right =1
y asi con los demás.
Ahora, para saber que ya no estamos apretando la tecla, es basicamente
lo mismo:
void
releaseKey(int key, int x, int y) //equivalente
al key_up del VB
{
switch
(key)
{
case GLUT_KEY_LEFT : key_left=0;break; //si
dejamos de apretar la izquierda
case GLUT_KEY_RIGHT : key_right=0;break; //si
dejamos de apretar la derecha
case GLUT_KEY_UP : key_up=0;break; //si dejamos
de apretar para arriba
case GLUT_KEY_DOWN : key_down=0;break; //si
dejamos de apretar para abajo
}
}
Si estas programando en VB, pones el codigo en el evento key_up:
if
KeyCode = vbKeyLeft Then key_left =0
if
KeyCode = vbKeyRight Then key_right =0
y asi con los demás.
- "genial, sabemos que tecla se
esta apretando, y?"
- "ya, ya, ya casi hacemos que
se mueva"
Ok. Ya sabemos que tecla de desplazamiento se apretó. Ahora,
comenzamos a mover a nuestro jugador.
//
seteamos las variables de desplazamiento a cero
player.p_dX=0;
player.p_dY=0;
//
dependiendo de lo que apretemos cambiamos los desplazamientos
if(key_left==1)
player.p_dX=-player.speed;
if(key_right==1)
player.p_dX=+player.speed;
if(key_up==1)
player.p_dY=-player.speed;
if(key_down==1)
player.p_dY=+player.speed;
- "Pero este chabon me esta re jodiendo!,
me tira el codigo asi nomas!, yo no entiendo nada"
- "Bancá loco! (pateo la
mesa), paso a explicar esta porqueria :)"
Lo que estamos haciendo primeramente es setear los desplazamientos de
nuestro jugador en las coordenadas X e Y en 0. Según la tecla que
esté apretando, hacemos que este desplazamiento tome como valor
la velocidad asignada al jugador, tomando el signo según corresponda
mover hacia la izquierda (-) o derecha (+) en coordenada X,
o bien arriba (-) o abajo (+) en coordenada Y.
Fijate que no estamos cambiando la posicion del jugador, solo estamos
cambiando su desplazamiento en los ejes. Luego sumaremos a la posicion
X e Y sus correspondientes desplazamientos. Este metodo raro es para facilitar
la detección de colision luego.
Vamos a olvidarnos por un rato de la colisión, y hagamos que
nuestro jugador finalmente se mueva:
//sumamos
los desplazamientos a la posición
player.pos.x+=player.p_dX;
player.pos.y+=player.p_dY;
Si ahora ejecutamos nuestro programa, el jugador se moverá libremente
en el mapa, sin detectar ningun tipo de colisión. Facil ¿verdad?.
Bien, ahora olvidate de estas ultimas dos lineas, era solo para que veas
como haremos que se mueva, estas las utilizaremos despues.
Antes de comenzar con algo de teoria, vamos a calcular un par de cosas
que necesitaremos luego:
//calculamos
el centro del jugador
player.center.x=player.pos.x+b_width/2;
player.center.y=player.pos.y+b_height/2;
Bien, creo que no hay mucho que explicar aqui. Simplemente calculamos
la posicion del centro de nuestro jugador a partir de su posicion en pantalla
(recorda que player.pos.x y player.pos.y son la posicion de la esquina
superior izquierda del player) y del ancho del bloque. (si no se entiende
consultame, todo bien).
Calcularemos ahora la posicion del jugador respecto a la grilla, es
decir, que cuadradito "teoricamente" esta ocupando nuestro jugador en la
grilla. Veamos esto mas graficamente:
Fijate que en realidad, el problema esta en hallar en que cuadradito
de la grilla se encuentra el centro de nuestro jugador. Bien ¿y
como hacemos esto? facil.
//calculamos
la posicion en la matriz del jugador
player.in_matrix.x
= (player.center.x/b_width);
player.in_matrix.y
= (player.center.y/b_height);
Recordá que player.center.x y player.center.y son las coordenadas
del centro de nuestro jugador y b_width y b_height son el ancho y el largo
de nuestros bloques en pixeles.
Bien, solo resta hallar una cosa mas y estaremos listos para detectar
colisión.
A modo de optimización, solo detectaremos colision con los bloques
mas cercanos a nosotros, por ejemplo, imaginate que nuestro jugador es
la tecla 5 de nuestro pad nuemerico, entonces detectaremos colision solo
con los bloques de los bordes, es decir, con el 1,2,3,4,6,7,8 y 9. Veamoslo
mas graficamente:
La informacion de la posicion de los bloques lindantes van almacenadas
en el array player.data_block[n].
Por ejemplo, imaginate que queremos saber la posicion en la grilla
del cuadrado que esta sobre nosotros (el 8 en el pad numerico), es decir,
sobre el cuadrado que "teoricamente" ocupamos (cuadrado verde). Fijate
que es muy simple en realidad, mirá: la coordenada X en la grilla
es la misma que la nuestra, y la Y es menor que la nuestra en una unidad,
es decir Y-1. Entonces:
//para
hallar las coordenadas del bloque que tenemos encima. (8)
player.data_block[1]._pos.x=
player.in_matrix.x+0;
player.data_block[1]._pos.y=
player.in_matrix.y-1;
Bien, este mismo proceso debemos realizar con los otros 7 bloques
restantes. Aca te muestro el codigo de todos ellos:
//calculamos
los bloques lindantes al jugador para la colisión
player.data_block[0]._pos.x=
player.in_matrix.x-1; player.data_block[0]._pos.y= player.in_matrix.y+1;
player.data_block[1]._pos.x=
player.in_matrix.x+0; player.data_block[1]._pos.y= player.in_matrix.y+1;
player.data_block[2]._pos.x=
player.in_matrix.x+1; player.data_block[2]._pos.y= player.in_matrix.y+1;
player.data_block[3]._pos.x=
player.in_matrix.x-1; player.data_block[3]._pos.y= player.in_matrix.y+0;
player.data_block[4]._pos.x=
player.in_matrix.x+1; player.data_block[4]._pos.y= player.in_matrix.y+0;
player.data_block[5]._pos.x=
player.in_matrix.x-1; player.data_block[5]._pos.y= player.in_matrix.y-1;
player.data_block[6]._pos.x=
player.in_matrix.x+0; player.data_block[6]._pos.y= player.in_matrix.y-1;
player.data_block[7]._pos.x=
player.in_matrix.x+1; player.data_block[7]._pos.y= player.in_matrix.y-1;
Entonces tenemos que en player.data_block[0
a 7]._pos tenemos almacenada la coordenada x e y en
la grilla de cada uno de los bloques lindantes. Ya vas a entender bien
para que sirve :) .
Bien!, ya estamos listos para ver la teoria de la colisión! :D
Notemos primeramente, que nuestro problema se centra basicamente en
ver cuando dos rectangulos de mismo tamaño chocan entre si. Veamos
esta situación mas graficamente:
Notemos que en este caso, es el bloque A quien está dentro de
B, diremos que A esta colisionando con B.
Bien, fijate que esta pasando en nuestro dibujo. La distancia en X
entre el centro de A y el centro de B es menor que el ancho de un bloque,
y la distancia en Y entre el centro de A y el centro de B es menor que
el alto de un bloque. La conclusion que se desprende es la siguiente:
Si (centro_del_bloque_A.x - centro_del_bloque_B.x < ancho_del_bloque)
y (centro_del_bloque_A.y - centro_del_bloque_B.y < alto_del_bloque)
entonces estan colisionando!.
-"Barbaro!, entonces necesitamos
el centro de los bloquecitos lindantes, pues el de nuestro jugador ya lo
tenemos."
-"Exacto!, veamos como calcularlos"
//
adquirimos el centro del bloque de colision en cuestión
o_center.x=((player.data_block[t]._pos.x)*b_width)+b_width/2;
o_center.y=((player.data_block[t]._pos.y)*b_height)+b_height/2;
Creo que no hay mucho que decir. Es muy parecido a como calculamos el
centro de nuestro jugador!.
Voy a explicar el proceso con la coordenada X, con la Y es lo mismo.
Lo que estamos haciendo es multiplicar la cantidad de cuadraditos
que tenemos a nuestra izquierda (que es en realidad la posicion X en la
grilla del bloque en cuestion) por el ancho de los bloquecitos. Asi objenemos
la coordenada X en pantalla del bloque, y luego sumamos la mitad de b_width
(ancho de los bloques) para obtener el centro.
Muy bien, veamos entonces como saber que chocamos:
//si
va a chocar en general
if
((fabs(o_center.x - player.center.x)<b_width)
&&
(fabs(o_center.y - player.center.y)<b_height)) EJECUTAR_ACCION;
(Recorda que float
fabs(float) devuelve el valor
absoluto de lo que le pasas como parametro)
Si lo estas codeando en VB sería algo asi:
If
Abs(o_center.x - player.center.x) < b_width And (Abs(o_center.y - player.center.y)
< b_height) Then
End
If
Solo con esta simple linea sabemos si player esta colisionando con algun
bloque.
Hagamos entonces que si estan chocando, el jugador no se mueva mas,
es decir, que su desplazamiento sea cero. Ahora el metodo raro de manejo
empieza a tener alguna utilidad. :) mirá:
//si
va a chocar en general
if
((fabs(o_center.x - player.center.x)<b_width)
&&
(fabs(o_center.y - player.center.y)<b_height))
{
player.p_dX
= 0;
player.p_dY
= 0;
};
Si probas esto, vas a ver que cuando nuestro jugador choca con otro
bloque, el mismo se queda detenido. Pero, hay un problema muy grave...
el jugador está DENTRO del bloque!, nustro sistema no evitó
que el player entre dentro del otro cuadradito, y ademas, el jugador ya
no se mueve mas! :(
Bueno, la verdad no se como arreglarlo... mentira :P.
Lo que tenemos que hacer, es una suerte de detección de colisión
a futuro, osea, antes de mover al jugador, hacemos que detecte si va a
chocar o no, si va a pasar, entonces dejamos de mover al jugador en esa
dirección.
Te preguntarás como hacer eso, es muy facil. Cuando detectamos
colision, usamos como coordenadas del centro del player las reales mas
el desplazamiento en X e Y. (ie: (player.center.x+player.p_dX))
//si
va a chocar en general
if
((fabs(o_center.x - player.center.x- player.p_dX)<b_width)
&&(fabs(o_center.y
- player.center.y-player.p_dY )<t;b_height))
{
hacer EVACION_DE_MOVIMIENTO aqui!
};
Muy bien, ya sabemos entonces que el jugador va a chocar en el proximo
movimiento que haga. Pero no sabemos si va a chocar de costado o bien
hacia arriba o hacia abajo. Esto debemos saberlo para hacer que el player
se deje de mover para el lado en que no puede hacerlo, pero que si pueda
desplazarse en las direcciones restantes.
Esto no es muy complicado en verdad. Simplemente debemos hacer el mismo
testeo nuevamente, pero, si queremos detectar si la colision se produce
de costado, sumamos UNICAMENTE el desplazamiento en X al player.center.x,
y dejamos el player.center.y
sin modificacion alguna.
(Esto que sigue iría dentro
de EVACION_DE_MOVIMIENTO)
//si
yendo para los costados en particular va a chocar
if
((fabs(o_center.x - player.center.x-player.p_dX)<b_width)
&&(fabs(o_center.y
- player.center.y)<b_height))/font>
{
hacer
arreglo de alineación en X aqui.
player.p_dX
=0; //hacemos que no se mueva en el eje X
}
Traduciendo un poco lo que dice aqui: Si con el desplazamiento en X
que se tiene por haber apretado alguna de las teclas para los costados
el player va a chocar, entonces hacemos que el desplazamiento en X sea
cero, para que el jugador no se mueva para ese lado.
(lo del arreglo de alineación lo voy a explicar en un minuto)
Hacemos lo mismo si el jugador se mueve para arriba o para abajo:
//
si yendo para arriba o abajo en particular va a chocar
if
((fabs(o_center.x - player.center.x)<b_width)
&&(fabs(o_center.y
- player.center.y-player.p_dY )<t;b_height))
{
hacer
arreglo de alineación en Y aqui.
player.p_dY
=0;
}
Barbaro!, ya tenemos un sistemita de colisión que anda CASI de
pelos. A primera vista no vas a notar ningun problema, pero existe uno,
y muy grave. Imaginate que estamos moviendonos para la izquierda
y detectamos que vamos a chocar, entonces hacemos que el desplazamiento
en X sea cero y nuestro player deja de moverse en esa dirección.
Muy lindo, pero es muy probable que esté quedando una distancia
muy pequeñita entre el bloque y mi jugador. Más graficamente:
Este problema es en verdad muy facil de solucionar. Hacemos que cuando
choque contra el bloque de la izquierda, la posicion X de nuestro jugador
será la del bloque con quien chocamos mas el ancho de un bloque.
De una manera similar solucionamos el problema si el choque es hacia
la derecha.
La posicion X de nuestro jugador será la del bloque con quien
chocamos menos el ancho de un bloque.
Como las posiciones de los bloques de mapa los tenemos dados por el
centro (lo calculamos arriba), el codigo sería el siguiente:
//derecha
if(player.p_dX>0)
player.pos.x=o_center.x-(b_width+b_width/2);
//izquierda
if(player.p_dX<0)
player.pos.x=o_center.x+b_width/2;
De una manera similar lo hacemos para arriba y abajo:
//baja
if(player.p_dY>0)
player.pos.y =o_center.y-b_height-b_height/2;
//
sube
if(player.p_dY<0)
player.pos.y=o_center.y+b_height/2;
Ahora que tenemos detectada la colision, y evitamos que el jugador se
mueva cuando no debe hacerlo. Podemos modificar la posicion del jugador:
//sumamos
los desplazamientos a la posición
player.pos.x+=player.p_dX;
player.pos.y+=player.p_dY;
Muy bien!, ya esta listo!. Veamos como se vé el código
de chequeo de colisión completo:
------------------------------------------------------------------------------------------------------
//
AQUI ARRIBA VAN TODAS LAS COSAS QUE CALCULAMOS ANTES //
//
HECHALE UN OJO AL FUENTE DE EJEMPLO, EN LA SUB <IDLE> //
//
chequeo de colisión
for
(int t=0;t<8;t++) //barremos todos los
bloques lindantes
{
//si es un bloque digno de ser considerado
en la colision
if (mapa[player.data_block[t]._pos.y][player.data_block[t]._pos.x] !='0')
{
// adquirimos el centro del bloque de colision
en cuestión
o_center.x=((player.data_block[t]._pos.x)*b_width)+b_width/2;
o_center.y=((player.data_block[t]._pos.y)*b_height)+b_height/2;
//si va a chocar en general
if ((fabs(o_center.x - player.center.x- player.p_dX)<b_width)
&&(fabs(o_center.y - player.center.y-player.p_dY )<b_height))
{
// si yendo para arriba o abajo en particular
va a chocar
if ((fabs(o_center.x - player.center.x)<b_width)
&&(fabs(o_center.y - player.center.y-player.p_dY )<b_height))
{
//baja
if(player.p_dY>0) player.pos.y =o_center.y-b_height-b_height/2;
// sube
if(player.p_dY<0) player.pos.y=o_center.y+b_height/2;
player.p_dY =0; //hacemos que no nos podamos
mover en Y
}
// si yendo para los costados en particular
va a chocar
if ((fabs(o_center.x - player.center.x-player.p_dX)<b_width)
&&(fabs(o_center.y - player.center.y)<b_height))
{
//derecha
if(player.p_dX>0) player.pos.x=o_center.x-(b_width+b_width/2);
//izquierda
if(player.p_dX<0) player.pos.x=o_center.x+b_width/2;
player.p_dX =0; //hacemos que no nos podamos
mover en X
}
}
}
------------------------------------------------------------------------------------------------------
Barbaro, es un codigo re chiquito por ser de detección de colisión
:D
Muy bien, espero se haya entendido algo. Si queda alguna duda, pueden
mandar mail, yo responderé mas que gustoso.
Aqui pueden bajar un codigo de ejemplo hecho en Vc++ con OpenGl: Ejemplo.zip
Ahora si, la parte mas divertida de todo tutorial, los greetz!! :)
Y estos son para:
- Luci - Silencer - Nahog - FaQ - Numlock - Okasion - Body
- Darquiel -
--------------------------------------------------------------------------------------------------------
Si quieren contactarme:
mail: vertexar@yahoo.com (escriban, por favor!, no me llega mas
que propaganda :( )
icq: 136373996 (en epoca de facultad nunca estoy)
msn: vertexar@hotmail.com (en epoca de facultad nunca estoy)
!Quiero ver sus proyectos!, A ver cuando juego un tile based game de
ustedes! :D
Saludos!. Hasta el proximo tutorial!
-------------------------
Wolfgang of dade (c) 2003
"programo, luego existo"
|