Objectiu
Tal com ja us vam comentar en la darrera entrega de la serie d’articles sobre la programació d’un joc tipus Pong, ara toca millorar les col·lisions aplicant una tècnica utilitzada en el món dels videojocs i denominada caixes de col·lisions.
Primer us presentarem un petit exemple que hem adaptat del llibre Modern MSX BASIC Game Development, del Raul Portales, del seu capítol 5 “Collision detection” de BASIC a C. Aquí us presentarem la tècnica i la compararem amb les col·lisions per Hardware. Podeu trobar més informació sobre el llibre a l’enllaç [1] al final de l’article.
Per acabar us explicarem com hem modificat el codi del darrer article Pong (la pala de la CPU) per tal de substituir la gestió de les col·lisions per col·lisions per caixes.
Introducció
Captura de pantalla de l’exemple de col·lisions que hem preparat
L’exemple que us proposem per entendre les col·lisions és el de més amunt. Consta de dos sprites en forma de C. Aquesta forma s’ha escollit per exemplificar les col·lisions de caixa amb les de sprite, ja que amb les de esprite podem encaixar una figura dins d’una altra sense que aquestes col·lisionin.
Hem creat també un parell de semàfors a la part inferior de la pantalla per fer gràfica la col·lisió quan aquesta es produeix tant en la modalitat de caixes com de sprite, software o hardware respectivament.
Una mica de teoria. Què són les caixes de col·lisions?
La idea és imaginar una caixa al voltant dels sprites que volem controlar-ne el solapament. Quan les caixes es solapen és quan hi ha col·lisió dels sprites.
Figura amb amb caixes de col·lisió al voltat de sprites
Per tal de crear les caixes imaginàries utilitzem les coordenades (X1,Y1) i (X1 + W1,Y1 +H1) pel cas de la primera caixa que engloba a en Joe, al protagonista del joc Bricks. Aquí W1 és l’amplada i H1 és l’alçada de la caixa.
Sinó coneixes el Bricks por trobar-lo a l’enllaç [2] al final de l’article.
D'igual forma farem el mateix per la caixa imaginaria del personatge Sam, també del joc Bricks, (X2,Y2) i (X2+W2,Y2+H2). Aquí d’igual forma W2 és l’amplada i H2 l’alçada de la caixa.
Ara que ja tenim les caixes definides caldrà veure com ho fem per saber si aquestes es toquen o sobre posen. Per fer-ho utilitzem la següent comprovació:
Primer comprovarem que les coordenades de l’eix X de la primera caixa no sobre passin les de la segona caixa.
X1+W1>=X2 AND X1<=X2+W2
Després comprovarem que les coordenades Y de la primera caixa no sobre passin les de la segona caixa.
Y1+H1>=Y2 AND Y1<=Y2+H2
Si es donen les quatre situacions anteriors aleshores podem afirmar que els sprites han estan col·lisionant.
X1+W1>=X2 AND X1<=X2+W2 AND Y1+H1>=Y2 AND Y1<=Y2+H2
Posem-ho en pràctica amb un exemple
Posarem la teoria en pràctica amb un exemple de col·lisions. En aquest exemple tal com hem comentat, comparerem les col·lisions per caixes amb les per Hardware.
Per fer-ho primer crearem un parell d’objectes que podrem moure per la pantalla un amb els cursors i l’altre amb les tecles A,D,W i S per tal d’aproximar-los i provocar les col·lisions.
Captura de pantalla de les figures
Els objectes estan oberts per un extrem expressament per a comprovar la diferència que hi ha entre les dues aproximacions pel control de les col·lisions presentades.
En concret veurem com en les col·lisions per Hardware podem introduir un objecte dins un altre pels extrems oberts ja que sols col·lisionen a nivell de pixel. En canvi en introduir un objecte dins un altre pels extrems oberts col·lisionaran a nivell de caixes ja que la comprovació presentada a la secció anterior es complirà.
Us deixo les definicions dels objectes:
/* --------------------------------------------------------- */
/* SPRITE square with right opening */
/* ========================================================= */
static const unsigned char left_object_pattern_1[] = {
0b00000000,
0b00000000,
0b00111111,
0b00111111,
0b00110000,
0b00110000,
0b00110000,
0b00110000
};
static const unsigned char left_object_pattern_2[] = {
0b00110000,
0b00110000,
0b00110000,
0b00110000,
0b00111111,
0b00111111,
0b00000000,
0b00000000
};
static const unsigned char left_object_pattern_3[] = {
0b00000000,
0b00000000,
0b11111100,
0b11111100,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
static const unsigned char left_object_pattern_4[] = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b11111100,
0b11111100,
0b00000000,
0b00000000
};
/* --------------------------------------------------------- */
/* SPRITE square with left opening. */
/* ========================================================= */
static const unsigned char right_object_pattern_1[] = {
0b00000000,
0b00000000,
0b00111111,
0b00111111,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
static const unsigned char right_object_pattern_2[] = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00111111,
0b00111111,
0b00000000,
0b00000000
};
static const unsigned char right_object_pattern_3[] = {
0b00000000,
0b00000000,
0b11111100,
0b11111100,
0b00001100,
0b00001100,
0b00001100,
0b00001100
};
static const unsigned char right_object_pattern_4[] = {
0b00001100,
0b00001100,
0b00001100,
0b00001100,
0b11111100,
0b11111100,
0b00000000,
0b00000000
};
Per fer l’exemple més visual hem decidit incorporar uns indicadors a l’estil dels semàfors. En concret un semàfor que es posarà en vermell con hi hagi una col·lisió hardware i un altre que es posarà en vermell quan hi hagi una col·lisió software.
Captura de pantalla dels semàfors
Us deixo les definicions del semàfors:
/* --------------------------------------------------------- */
/* SPRITE traffic light */
/* ========================================================= */
static const unsigned char semaphore_pattern_1[] = {
0b00000000,
0b00000111,
0b00001111,
0b00011111,
0b00111111,
0b01111111,
0b01111111,
0b01111111
};
static const unsigned char semaphore_pattern_2[] = {
0b01111111,
0b01111111,
0b00111111,
0b00011111,
0b00001111,
0b00000111,
0b00000000,
0b00000000
};
static const unsigned char semaphore_pattern_3[] = {
0b00000000,
0b11000000,
0b11100000,
0b11110000,
0b11111000,
0b11111100,
0b11111100,
0b11111100
};
static const unsigned char semaphore_pattern_4[] = {
0b11111100,
0b11111100,
0b11111000,
0b11110000,
0b11100000,
0b11000000,
0b00000000,
0b00000000
};
Els dos objectes es poden moure amunt, avall, cap a l’esquerra i cap a la dreta per tal de fer-los col·lisionar.
Captura de pantalla dels objectes
L’objecte blau el podreu moure amb els cursors i el gris amb les tecles W (amunt), S (avall), A (esquerra) i D (dreta).
Us deixo el tros de codi relacionat amb el moviment dels objectes:
// Up
if(stick==1)
{
ylefobj=(ylefobj-1);
PutSprite (1,0,xlefobj,ylefobj,1);
}
// w
if(key==119)
{
yrigobj=(yrigobj-1);
PutSprite (2,4,xrigobj,yrigobj,1);
}
// Right
if(stick==3)
{
xlefobj=(xlefobj+1);
PutSprite (1,0,xlefobj,ylefobj,1);
}
// d
if(key==100)
{
xrigobj=(xrigobj+1);
PutSprite (2,4,xrigobj,yrigobj,1);
}
// Down
if(stick==5)
{
ylefobj=(ylefobj+1);
PutSprite (1,0,xlefobj,ylefobj,1);
}
// s
if(key==115)
{
yrigobj=(yrigobj+1);
PutSprite (2,4,xrigobj,yrigobj,1);
}
// Left
if(stick==7)
{
xlefobj=(xlefobj-1);
PutSprite (1,0,xlefobj,ylefobj,1);
}
// a
if(key==97)
{
xrigobj=(xrigobj-1);
PutSprite (2,4,xrigobj,yrigobj,1);
}
Però anem al que ens interessa. Com ho hem fet per gestionar el tema de les col·lisions en aquest exemple?
Tal com us hem explicat al començament farem ús dels dos tipus de col·lisions les Hardware i les Software.
Us deixo el tros de codi encarregat de gestionar les col·lisions Hardware que farà ús de la funció de la llibreria vdp_sprites.h de FUSION-C SptireCollision():
// Hardware collision
if (SpriteCollision()) {
PutText(230,10,"1",0);
SC5SpriteColors(3,LineColorsLayer13);
} else {
PutText(230,10," ",0);
SC5SpriteColors(3,LineColorsLayer12);
}
Seguidament us deixo el tros de codi encarregat de gestionar les col·lisions per software. Aquí s’ha simplificat el càlcul presentat a la introducció teòrica.
// Software collision
if ((abs(xlefobj - xrigobj) < 32) && (abs(ylefobj - yrigobj) < 32) ) {
PutText(230,10,"2",0);
SC5SpriteColors(4,LineColorsLayer13);
} else {
PutText(230,10," ",0);
SC5SpriteColors(4,LineColorsLayer12);
}
Tot seguit us expliquem el perquè de la simplificació del càlcul en aquest cas particular.
El càlcul inicial era el següent:
IF X1+W1>=X2 AND X1<=X2+W2 AND Y1+H1>=Y2 AND Y1<=Y2+H2
Figura on es es mostren les caixes de col·lisió dels objectes
En ser els objectes del mateix tamany podem simplificar al càlcul canviant H1, H2 per H i W1, W2 per W.
De la mateixa manera podem comprovar el solapament vertical restat les coordenades X dels dos objectes en valor absolut i igual amb les coordenades Y, quedant de la següent manera:
IF ABS(X1-X2)<W AND ABS(Y1-Y2)<H
Figura on es mostren les caixes de col·lisió en col·lisió
Modifiquem les col·lisions al Pong del passat article
Recuperem ara el darrer article del Pong per a modificar-ne la gestió de col·lisions que en vam fer i incorporar la tècnia de col·lisions per caixes.
El fragment de codi de més avall és el del article original. En aquell cas controlàvem les col·lisions de la pilota amb les pales dels jugadors a través de la funció SpriteCollision() de la llibreria vdp_sprites.h de FUSION-C. La funció ens ens retorna un 1 quan detecta col·lisió entre Sprites.
Tal com vam comentar tot i ser un punt a favor pels MSX de l’època que el VDP tingués detecció de col·lisions a nivell de pixel, té alguns problemes potencial. Té un limitació important la de no saber quins són els sprites que han col·lisionat. Amb la tècnica de col·lisions per caixes aquests problemes potencials i la limitació comentada desapareixen.
void DrawSprite(void)
{
// Collision Detection
// Collision with CPU Pad or Player Pad Sptite
if (SpriteCollision()) {
if(TheBall.x>235 && DirX>0) {
DirX*=-1;
}
if (TheBall.x<30 && DirX<0) {
DirX*=-1;
}
}
…
}
Al fragment de codi de més avall podem veure la funció anterior on hem canviat la gestió de les col·lision per la tècnica de caixes de col·lisions. A diferència de l’exemple introductori aquí no hem pogut simplificar el càlcul ja que la pilota i les pales dels jugadors i les seves caixes de col·lisions tenen diferents dimensions.
Podem veure que hem hagut de diferenciar entre el cas que la pilota col·lisioni amb la pala del jugador x <= 128 o que sigui amb la pala de la cpu x > 128 segons la ubicació de les caixes de col·lisions dels sprites ja que tenen tamanys diferents.
void DrawSprite(void)
{
// Collision Detection
// Collision with CPU Pad or Player Pad Sptite
// If the ball's x < 128 we will look at the collision with the left paddle
if ( (TheBall.x <= 128) && (PlyPad.x + 6 >= TheBall.x) && (PlyPad.x <= TheBall.x + 6) && (PlyPad.y + 16 >= TheBall.y) && (PlyPad.y <= TheBall.y + 6) ) {
DirX*=-1;
}
// If the ball's x > 128 we will look at the collision with the right paddle
if ( (TheBall.x > 128) && (TheBall.x + 6 >= CpuPad.x) && (TheBall.x <= CpuPad.x + 16) && (TheBall.y + 6 >= CpuPad.y) && (TheBall.y <= CpuPad.y + 16) ) {
DirX*=-1;
}
…
}
Conclusions
Després de la lectura d’aquest article ja tens les nocions bàsiques per tal de provar la tècnica de les caixes de col·lisions ens els teus projectes. Et proposem que per a practicar agafis uns sprites definits per tu i els incorporis a l'exemple introductori. Pot ser que hagis de modificar el càlcul ja que els que t’hem proposat nosaltres tenen el mateix tamany.
Quan ho tinguis ja pots aplicar-ho al teu projecte i tenir-ho ja sempre present com a una nova tècnica a aplicar.
Esperem que t’hagi servit d’ajuda. Ens veiem al proper article.
Clica aquí per a veure l'exemple funcionant.
Clica aquí per provar la versió retocada del Pong.
Clica aquí per accedir al codi de l’exemple.
Clica aquí per accedir al codi de la versió retocada del Pong.
Commentaires