Skip to content

Instantly share code, notes, and snippets.

@m1cr0lab
Last active November 26, 2018 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m1cr0lab/a8517f5adfeb44f50cbe47991b4d499a to your computer and use it in GitHub Desktop.
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
/*
* 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