-
-
Save m1cr0lab/a8517f5adfeb44f50cbe47991b4d499a to your computer and use it in GitHub Desktop.
Implémentation d'un système de gestion des collisions pour un casse-briques
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Implémentation d'un système de gestion des collisions pour un casse-briques | |
* réalisé sur la Gamebuino META | |
* | |
* auteur : Steph (https://gamebuino.com/@steph) | |
* date : 26 novembre 2018 | |
* | |
* En réponse à la demande suivante de Jicehel : | |
* https://gamebuino.com/fr/community/topic/big-tuto-on-shading-effect-in-high-resolution#c6121 | |
* | |
* Avertissement : il ne s'agit pas du jeu de casse-briques intégral, | |
* mais simplement un simulateur de test d'un algorithme | |
* de gestion des collisions entre la balle et les briques | |
* la balle rebondira sur tous les bords de l'écran | |
* | |
* Commandes interactives : | |
* - Bouton A : afficher les surfaces d'exposition des briques | |
* - Bouton B : réinitialiser la simulation | |
* - Pad Left : diminuer la vitesse horizontale de la balle | |
* - Pad Right : augmenter la vitesse horizontale de la balle | |
*/ | |
#include <Gamebuino-Meta.h> | |
#define SCREEN_WIDTH 80 | |
#define SCREEN_HEIGHT 64 | |
#define HORIZONTAL_BRICKS 10 | |
#define VERTICAL_BRICKS 6 | |
#define BALL_RADIUS 2 | |
const uint8_t BRICK_WIDTH = SCREEN_WIDTH / HORIZONTAL_BRICKS; | |
const uint8_t BRICK_HEIGHT = 4; | |
const uint8_t BALL_RIGTH_LIMIT = SCREEN_WIDTH - 1 - BALL_RADIUS; | |
const uint8_t BALL_BOTTOM_LIMIT = SCREEN_HEIGHT - 1 - BALL_RADIUS; | |
const Color PALETTE[] = {RED, ORANGE, YELLOW, LIGHTGREEN, GREEN, LIGHTBLUE}; | |
// ce flag permettra de dessiner (true) ou non (false) | |
// les surfaces d'exposition des briques qui constituent le mur | |
bool wallBoundsVisible = false; | |
// définition d'une brique | |
typedef struct { | |
// un flag permet de déterminer si la brique : | |
// - a déjà été brisée (false) | |
// - ou non (true) | |
bool alive; | |
uint8_t x,y; | |
// color correspondant à l'index de la couleur dans la palette | |
uint8_t color; | |
// ici chaque brique se voit attribuer un facteur d'exposition | |
// composé des entiers binaires suivants : | |
// | |
// 0b1000 : la brique est exposée par le haut | |
// 0b0100 : la brique est exposée par la droite | |
// 0b0010 : la brique est exposée par le bas | |
// 0b0001 : la brique est exposée par la gauche | |
// | |
// on va donc pouvoir combiner ses multiples expositions | |
// en combinant ces entiers binaires, par exemple : | |
// si le facteur d'exposition de la brique est 0b1101, | |
// cela signifie que la brique est exposée : | |
// - par le haut | |
// - par la droite | |
// - et par la gauche | |
uint8_t exposure; | |
} Brick; | |
// définition de la balle | |
typedef struct { | |
int8_t x,y; | |
int8_t vx,vy; | |
} Ball; | |
Brick wall[HORIZONTAL_BRICKS][VERTICAL_BRICKS]; | |
Ball ball; | |
void setup() { | |
gb.begin(); | |
gb.setFrameRate(40); | |
// SerialUSB.begin(9600); | |
initGame(); | |
} | |
void loop() { | |
while (!gb.update()); | |
gb.display.clear(); | |
// if (gb.frameCount % 25 == 0) { | |
// SerialUSB.printf("CPU: %i, RAM: %i\n", gb.getCpuLoad(), gb.getFreeRam()); | |
// } | |
// un appui sur le bouton A permettra | |
// d'activer ou de désactiver le tracé | |
// des surfaces d'exposition des briques | |
// constituant le mur | |
if (gb.buttons.pressed(BUTTON_A)) { | |
wallBoundsVisible = !wallBoundsVisible; | |
// un appui sur le bouton B permettra de | |
// reconstituer le mur instantanément | |
} else if (gb.buttons.pressed(BUTTON_B)) { | |
initGame(); | |
// un appui sur le PAD à gauche permettra | |
// de diminuer la composante X de la vitesse | |
// de la balle | |
} else if (gb.buttons.pressed(BUTTON_LEFT)) { | |
ball.vx--; | |
// un appui sur le PAD à droite permettra | |
// d'augmenter la composante X de la vitesse | |
// de la balle | |
} else if (gb.buttons.pressed(BUTTON_RIGHT)) { | |
ball.vx++; | |
} | |
// on applique les propriétés cinématiques | |
// de la balle | |
moveBall(); | |
// on vérifie si la balle rebondit | |
// sur les bords de l'écran | |
handleGroundLimit(); | |
// et on gère les collisions éventuelles | |
// entre la balle et les briques du mur | |
handleBrickCollisions(); | |
// puis on dessine le mur | |
drawWall(); | |
// et la balle | |
drawBall(); | |
} | |
// intialisation du mur et de la balle | |
void initGame() { | |
initWall(); | |
initBall(); | |
} | |
// initialisation des propriétés cinématiques | |
// de la balle : elle est initialement placée | |
// au centre inférieur de l'écran, et sa vitesse | |
// est initialisée avec le vecteur (-1, -1) | |
void initBall() { | |
ball.x = SCREEN_WIDTH / 2; | |
ball.y = BALL_BOTTOM_LIMIT; | |
ball.vx = -1; | |
ball.vy = -1; | |
} | |
void initWall() { | |
Brick* b; | |
uint8_t yoffset = 2; | |
uint8_t x,y; | |
for (y = 0; y < VERTICAL_BRICKS; y++) { | |
for (x = 0; x < HORIZONTAL_BRICKS; x++) { | |
b = &wall[x][y]; | |
b->alive = true; | |
b->x = x * BRICK_WIDTH; | |
b->y = (y + yoffset) * BRICK_HEIGHT; | |
b->color = y; | |
// initialisation des surfaces d'exposition | |
// par défaut aucune brique n'est exposée | |
b->exposure = 0b0000; | |
// sauf les briques qui s'étendent | |
// sur le pourtour du mur | |
b->exposure |= x == 0 ? 0b0001 : 0b0000; | |
b->exposure |= x == HORIZONTAL_BRICKS-1 ? 0b0100 : 0b0000; | |
b->exposure |= y == 0 ? 0b1000 : 0b0000; | |
b->exposure |= y == VERTICAL_BRICKS-1 ? 0b0010 : 0b0000; | |
} | |
} | |
} | |
void moveBall() { | |
// application des lois cinématiques | |
// le vecteur position de la balle | |
// est directement dérivé de sa vitesse instantanée | |
ball.x += ball.vx; | |
ball.y += ball.vy; | |
} | |
void handleGroundLimit() { | |
// les rebonds ne sont détectés qu'après pénétration de la balle | |
// dans l'un des bords de l'écran | |
// | |
// y a-t-il eu rebond sur le bord gauche | |
if ((ball.vx < 0) && (ball.x < BALL_RADIUS)) { | |
// dans ce cas, on replace la balle dans l'écran | |
// une légère approximation est faite ici | |
// pour simplifier les calculs | |
ball.x = BALL_RADIUS; | |
// et on inverse la composante X de la vitesse de la balle | |
ball.vx *= -1; | |
// même chose avec le bord droit de l'écran | |
} else if (ball.x > BALL_RIGTH_LIMIT) { | |
ball.x = BALL_RIGTH_LIMIT; | |
ball.vx *= -1; | |
} | |
// puis le bord supérieur de l'écran | |
if ((ball.vy < 0) && (ball.y < BALL_RADIUS)) { | |
ball.y = BALL_RADIUS; | |
ball.vy *= -1; | |
// et enfin le bord inférieur | |
} else if (ball.y > BALL_BOTTOM_LIMIT) { | |
ball.y = BALL_BOTTOM_LIMIT; | |
ball.vy *= -1; | |
} | |
} | |
void handleBrickCollisions() { | |
Brick* b; | |
bool xbin,ybin,impact; | |
uint8_t brick_side_impact = 0b0000; | |
uint8_t x,y; | |
for (y = 0; y < VERTICAL_BRICKS; y++) { | |
for (x = 0; x < HORIZONTAL_BRICKS; x++) { | |
b = &wall[x][y]; | |
// on vérifie que la brique n'a pas encore été brisée | |
// et qu'elle offre au moins une surface d'exposition ; | |
// ceci permet de limiter les calculs de collisions | |
// aux seules briques exposées pour des raisons de performances | |
if (b->alive && b->exposure) { | |
// ici on calcule les témoins de pénétration de la balle | |
// dans la brique, selon les deux axes X et Y | |
xbin = (ball.x + BALL_RADIUS >= b->x) && (ball.x - BALL_RADIUS < b->x + BRICK_WIDTH); | |
ybin = (ball.y + BALL_RADIUS >= b->y) && (ball.y - BALL_RADIUS < b->y + BRICK_HEIGHT); | |
// s'il y a eu pénétration... | |
if (xbin && ybin) { | |
// on va chercher à savoir où a eu lieu l'impact | |
// c'est-à-dire sur quelle face de la brique | |
// pour déterminer ensuite le rebond approprié | |
// | |
// ici aussi on va utiliser des entiers binaires : | |
// - 0b1000 si l'impact a eu lieu sur la face supérieure | |
// - 0b0100 si l'impact a eu lieu sur la face droite | |
// - 0b0010 si l'impact a eu lieu sur la face inférieure | |
// - 0b0001 si l'impact a eu lieu sur la face gauche | |
if (ball.x < b->x) { | |
if (ball.y < b->y) { | |
brick_side_impact = 0b1000; | |
} else if (ball.y > b->y + BRICK_HEIGHT) { | |
brick_side_impact = 0b0010; | |
} else { | |
brick_side_impact = 0b0001; | |
} | |
} else { | |
if (ball.y < b->y) { | |
brick_side_impact = 0b1000; | |
} else if (ball.y > b->y + BRICK_HEIGHT) { | |
brick_side_impact = 0b0010; | |
} else { | |
brick_side_impact = 0b0100; | |
} | |
} | |
// on va maintenant mettre en correspondance : | |
// - la surface d'exposition de la brique | |
// - la face sur laquelle a eu lieu l'impact | |
// - la cohérence avec la vitesse de la bille | |
// pour déterminer le rebond approprié | |
// | |
// la balle est replacée comme si la pénétration | |
// n'avait pas eu lieu... avec une petite approximation | |
// pour simplifier les calculs | |
// si l'impact a eu lieu sur la face droite de la brique | |
if ((b->exposure & 0b0100) && (brick_side_impact & 0b0100) && (ball.vx < 0)) { | |
handleImpact(x,y); | |
ball.x = b->x + BRICK_WIDTH + BALL_RADIUS; | |
ball.vx *= -1; | |
continue; | |
} | |
// si la collision a eu lieu sur la face gauche de la brique | |
if ((b->exposure & 0b0001) && (brick_side_impact & 0b0001) && (ball.vx > 0)) { | |
handleImpact(x,y); | |
ball.x = b->x - BALL_RADIUS - 1; | |
ball.vx *= -1; | |
continue; | |
} | |
// si la collision a eu lieu sur la face inférieure de la brique | |
if ((b->exposure & 0b0010) && (brick_side_impact & 0b0010) && (ball.vy < 0)) { | |
handleImpact(x,y); | |
ball.y = b->y + BRICK_HEIGHT + BALL_RADIUS; | |
ball.vy *= -1; | |
continue; | |
} | |
// si la collision a eu lieu sur la face supérieure de la brique | |
// (a priori, il ne reste plus que ce cas possible...) | |
if ((b->exposure & 0b1000) && (brick_side_impact & 0b1000) && (ball.vy > 0)) { | |
handleImpact(x,y); | |
ball.y = b->y - BALL_RADIUS - 1; | |
ball.vy *= -1; | |
} | |
} | |
} | |
} | |
} | |
} | |
// ici on modifie les surfaces d'exposition de toutes les briques | |
// voisines de celle qui vient d'être brisée | |
void handleImpact(uint8_t x, uint8_t y) { | |
wall[x][y].alive = false; | |
if (x>0) wall[x-1][y].exposure |= 0b0100; | |
if (x<HORIZONTAL_BRICKS-1) wall[x+1][y].exposure |= 0b0001; | |
if (y>0) wall[x][y-1].exposure |= 0b0010; | |
if (y<VERTICAL_BRICKS-1) wall[x][y+1].exposure |= 0b1000; | |
} | |
// chaque brique est dessinée, ainsi que ses surfaces d'exposition | |
// si le témoin wallBoundsVisible == true | |
void drawWall() { | |
// on ne dessine pas exactement toute la surface | |
// de la brique pour qu'on puisse distinguer | |
// les contours de chaque brique (pur esthétisme) | |
uint8_t bw = BRICK_WIDTH - 1; | |
uint8_t bh = BRICK_HEIGHT - 1; | |
Brick* b; | |
uint8_t x,y; | |
for (y = 0; y < VERTICAL_BRICKS; y++) { | |
for (x = 0; x < HORIZONTAL_BRICKS; x++) { | |
b = &wall[x][y]; | |
if (b->alive) { | |
gb.display.setColor(PALETTE[b->color]); | |
gb.display.fillRect(b->x, b->y, bw, bh); | |
// si les surface d'exposition doivent être dessineées... | |
if (wallBoundsVisible && b->exposure) { | |
// ...ben on le fait ! | |
drawBrickExposure(b); | |
} | |
} | |
} | |
} | |
} | |
// tracé des surfaces d'exposition d'une brique donnée | |
void drawBrickExposure(Brick* b) { | |
gb.display.setColor(WHITE); | |
if (b->exposure & 0b1000) { | |
gb.display.drawLine(b->x, b->y, b->x + BRICK_WIDTH, b->y); | |
} | |
if (b->exposure & 0b0100) { | |
gb.display.drawLine(b->x + BRICK_WIDTH, b->y, b->x + BRICK_WIDTH, b->y + BRICK_HEIGHT); | |
} | |
if (b->exposure & 0b0010) { | |
gb.display.drawLine(b->x, b->y + BRICK_HEIGHT, b->x + BRICK_WIDTH, b->y + BRICK_HEIGHT); | |
} | |
if (b->exposure & 0b0001) { | |
gb.display.drawLine(b->x, b->y, b->x, b->y + BRICK_HEIGHT); | |
} | |
} | |
// tracé de la balle | |
void drawBall() { | |
gb.display.setColor(WHITE); | |
gb.display.fillCircle(ball.x, ball.y, BALL_RADIUS); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment