Brick Breaker is a classic arcade game, and excellent beginner project. This break breaker tutorial is part of my Re-Creating popular Games for the Gameboy tutorial series. In this tutorial, you’ll use the 2020 Gameboy Development Kit (GBDK 2020) and the C Programming language to recreate the classic Bricker Breaker Game: Alleyway.
If you are not familiar with GBDK 2020 yet, i have a tutorial series on that: How to make Games for Gameboy. It should help you get started.
The code for this Brick Breaker Tutorial will be available for free on GitHub. This tutorial is not 1-for-1 follow along style. Meaning some code snippets might not be included. The best way to utilize this tutorial is by downloading the free source from GitHub.
The graphics for this game are from The Spriters Resource. This tutorial is for educational purposes, they cannot be re-sold for commercial reasons.
Setting things up
Before we can start drawing the ball, the background, or the paddle we must get the Gameboy’s Video RAM (VRAM for short) in the proper state. In VRAM we’ll store the tiles used for each object, and our color palettes.
void main(void){
DISPLAY_ON;
SHOW_SPRITES;
SHOW_BKG;
SPRITES_8x8;
// Turn on sound
NR52_REG = 0x80;
NR51_REG = 0xFF;
NR50_REG = 0x77;
set_sprite_palette(0,1,BlackAndWhitePalette);
set_bkg_palette(0,1,BlackAndWhitePalette);
set_bkg_data(0,Bricks_TILE_COUNT,Bricks_tiles);
set_bkg_data(USERINTERFACE_TILES_START,UserInterface_TILE_COUNT,UserInterface_tiles);
set_bkg_data(FONT_TILES_START,Font_TILE_COUNT,Font_tiles);
set_sprite_data(0,paddle_TILE_COUNT,paddle_tiles);
set_sprite_data(paddle_TILE_COUNT,ball_TILE_COUNT,ball_tiles);
...
}
In the above code snippet we also enabled sound, and turned on our sprites/background/display.
For setting tiles in VRAM we used the “set_bkg_data” & “set_sprite_data” GBDK functions. Into those functions we passed values generated from the GBDK 2020 utility: png2asset. Png2Asset comes with the gbdk 2020, and is used to convert modern PNG graphics to files compatible with GBDK 2020. Here is a sum of all the graphics used:
The conversion from PNG to GBDK format occurs in our “generate-graphics.bat”. Here is how that’s done:
:: Sprites
%PNG2ASSET% graphics\paddle.png -c source\gen\graphics\paddle.c -sw 32 -sh 8 -spr8x8 -keep_palette_order -noflip
%PNG2ASSET% graphics\ball.png -c source\gen\graphics\ball.c -sw 8 -sh 8 -spr8x8 -keep_palette_order -noflip
:: Backgrounds Tiles
%PNG2ASSET% graphics\UserInterface.png -c source\gen\graphics\UserInterface.c -map -use_map_attributes -keep_palette_order -noflip
%PNG2ASSET% graphics\Bricks.png -c source\gen\graphics\Bricks.c -map -use_map_attributes -keep_palette_order -noflip
%PNG2ASSET% graphics\Font.png -c source\gen\graphics\Font.c -map -use_map_attributes -keep_palette_order -noflip
Usage of PNG2Asset isn’t required, it’s just my personal preference. A reliable alternative would be GBTD and GBMB. Those two tools allow you to create tiles and tile-maps respectively.
When our VRAM is fully populated with our tile data we can check it out using any Gameboy emulator’s VRAM Viewer.
You can find more information about setting tiles into VRAM in my Drawing on the Background and Window Layers tutorial, and Drawing and Moving Sprites in Gameboy Games tutorial.
The User Interface & Background
With our VRAM ready, the first thing to do is draw our Background & User Interface. We’ve got 2 primary functions for this: “UpdateScore”, and “ResetUserInterface”.
The “ResetUserInterface” function draws the UserInterface and then calls the “UpdateScore” function. The “UpdateScore” function draws the top score, current score, and current stage.
The “DrawNumber” function was custom created, but it’s not specific to this game.
void UpdateScore(){
DrawNumber(16,2,topScore,4);
DrawNumber(16,5,score,4);
DrawNumber(17,10,stage+1,2);
}
void ResetUserInterface(){
// Redraw the entire user-interface which will clear all existing bricks
set_bkg_based_tiles(0,0,20,18,UserInterface_map,USERINTERFACE_TILES_START);
UpdateScore();
}
For drawing the user interface in “ResetUserInterface”, we use the “set_bkg_based_tiles” function. This difers from the “set_bkg_tiles” function in one manner. The “set_bkg_based_tiles” function takes one additional argument: The base tile. All tile indices specified in the tilemap array, will be offset by this base tile value.
A preprocessor macro, USERINTERFACE_TILES_START, was created. It represents where the user interface tiles start(in VRAM). The indices defined in “UserInterface_map” need to be offset by this amount, since the user interface tiles come after the bricks.
If you lookup the definition for USERINTERFACE_TILES_START in common.h, you’ll see it just equals “Bricks_TILE_COUNT”. That value could have been used instead. However, i like the readability of USERINTERFACE_TILES_START .
Our Game Loop
Every game has a game loop. Our game loop in this Brick Breaker Tutorial will handle two different states. When the ball is ready, and when the ball is active.
When the ball is ready, we will move it to the center and wait for the A button to be pressed. When A is pressed, we’ll set the ball’s velocity. The ball will move upwards and randomly to the left or right.
When the ball is active, we’ll move/update the ball and handle collision against the paddle and/or bricks. Three functions will be used “UpdateBall”, “CollideBricksAgainstBall”, and “CollidePaddleAgainstBall”. Those functions will be explained later in their corresponding sections.
When the ball is active, but there are no blocks left, we’ll increase our stage and reset the game back to the ready state.
void main(void){
...
topScore=0;
stage=0;
ResetToReady();
while(1){
joypadPrevious=joypadCurrent;
joypadCurrent=joypad();
// Update the paddle
uint8_t lastSprite = UpdatePaddle();
// If the ball is ready
if(ballState==BALLSTATE_READY){
// Reset it's position
ballX=BALLSTART_X;
ballY=BALLSTART_Y;
lastSprite = move_metasprite(ball_metasprites[0],paddle_TILE_COUNT,lastSprite,(ballX>>4)+7,(ballY>>4)+15);
// If the A button was JUST pressed
if((joypadCurrent & J_A) && !(joypadPrevious & J_A)){
// The ball is now active
ballState=BALLSTATE_ACTIVE;
// Randomly choose left or right
if(RandomNumber(0,100)<50)ballVelocityX=200;
else ballVelocityX=-200;
ballVelocityY=-RandomNumber(180,220);
}
}else{
// Update the ball
ballState=UpdateBall(lastSprite);
CollideBricksAgainstBall();
CollidePaddleAgainstBall();
if(blocksLeft==0){
stage++;
ResetToReady();
}
}
// Slow down the frame rate some
wait_vbl_done();
}
}
For more info on accepting Gameboy joypad input, check out my tutorial on: How to handle Gameboy Joypad input
The Ball
The update logic for the ball is pretty simple and straight-forward. Our “UpdateBall” function will return 1 or 0. It only returns 0 when the ball has gone below the paddle. Otherwise it returns 1. Besides that, we update the ball’s position based on it’s velocity. Finally, we draw the ball using the gbdk-2020 move_metasprite function.
NOTE: Our ball’s position and velocity variables are all scaled integers. To ensure the ball’s motion is always smooth, we utilize scaled integer to achieve Sub-Pixel Resolution. I have a tutorial on that here: Smooth Movement using Sub-Pixel Resolution
uint8_t UpdateBall(uint8_t lastSprite){
// Move according to our velocity
ballX+=ballVelocityX>>4;
ballY+=ballVelocityY>>4;
// if the ball is at the bottom of the screen
if((ballY>>4)>144){
// Reset the gameplay
return 0;
}
move_metasprite(ball_metasprites[0],paddle_TILE_COUNT,lastSprite,(ballX>>4)+7,(ballY>>4)+15);
return 1;
}
When we want to Reset the ball, we just reset it’s position and clear it’s velocity. Later, when the ball is ready (not active), the ball’s velocity will be set like at the start of the game.
void ResetBall(){
ballX=BALLSTART_X;
ballY=BALLSTART_Y;
ballVelocityX=0;
ballVelocityY=0;
}
Next we’ll cover the paddle.
The Paddle
For the paddle, we won’t need a variable for it’s vertical speed. We’ll only need a variable for it’s position (paddleX and paddleY) and horizontal speed. This variable will be called paddleXSpeed. Like the paddleX & paddleY variables (and the ball variables), paddleXSpeed will be scaled integer.
We’ll set the paddleXSpeed to positive/negative 600 when holding right/left respectively. When neither right or left is not held down, we’ll move the paddleXSpeed to zero. This makes the paddle slide to a stop, as if there were friction. It’s not required, but it’s a nice touch. We’re also going to clamp the paddle’s x position and make it bounce of the edges a little.
We finally draw the paddle’s metasprite at the true values of the paddleX and paddleY variables.
uint8_t UpdatePaddle(){
if(joypadCurrent & J_LEFT){
paddleXSpeed=-600;
}else if(joypadCurrent & J_RIGHT){
paddleXSpeed=600;
}else{
// Slow down
if(paddleXSpeed>30)paddleXSpeed-=30;
else if(paddleXSpeed<-30)paddleXSpeed+=30;
else paddleXSpeed=0;
}
paddleX=CLAMP(paddleX+(paddleXSpeed>>4),LEFT_PADDLE_LIMIT,RIGHT_PADDLE_LIMIT);
/// Check if we are at the right limit
if(paddleX>=RIGHT_PADDLE_LIMIT){
paddleX=RIGHT_PADDLE_LIMIT;
paddleXSpeed=-ABS((paddleXSpeed)/4)*2;
}
// Check if we are at the left limit
else if(paddleX<=LEFT_PADDLE_LIMIT){
paddleX=LEFT_PADDLE_LIMIT;
paddleXSpeed=ABS((paddleXSpeed)/4)*2;
}
return move_metasprite(paddle_metasprites[0],0,0,(paddleX>>4)+8,(paddleY>>4)+16);
}
For resetting the paddle, we just set it’s horizontal speed to zero and place it in the middle of the screen (horizontally).
void ResetPaddle(){
paddleX=80<<4;
paddleY=130<<4;
paddleXSpeed=0;
}
Bricks
For the bricks, a large (constant) array of unsigned integers is used. I created macros to help in building the array:
- NO____BRICK – For no bricks
- LGHT__BRICK – For Light Bricks
- MED___BRICK – For Medium Bricks
- DRK___BRICK – For Dark Bricks
There should be enough bricks to fill around 17 rows.
const uint8_t level[AREA_TOTAL]={
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,MED___BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,LGHT__BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,DRK___BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,
NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK,NO____BRICK
};
For drawing the bricks, i can pass the above array into the following function called “DrawLevelBricks“. I made this a function so other levels could easily be created and run.
This function just loops through the Area height (17) and Area width (14), setting the proper background tile for each. The tile used is already set in the previously created macros used for the level array.
This function also counts how many bricks are left. Later, when the blocksLeft variable reaches 0, the game will reset and you could tell it to load another level.
void DrawLevelBricks(uint8_t *level){
// Reset this counter to zero
// We'll recount in the following double for-loops
blocksLeft=0;
for(uint8_t i=0;i<AREA_HEIGHT;i++){
for(uint8_t j=0;j<AREA_WIDTH;j++){
uint8_t brick = level[i*AREA_WIDTH+j];
if(brick==NO____BRICK){
set_bkg_tile_xy(j+1,i+1,0);
}
else {
set_bkg_tile_xy(j+1,i+1,brick);
// Increase how many blocks are left
blocksLeft+= (brick==LGHT__BRICK||brick==MED___BRICK||brick==DRK___BRICK)? 2:1;
}
}
}
}
Checking for Top/Bottom Bricks.
Next we have to setup a function called “CheckTopOrBottomBrick“. This function is for determining if a given x/y coordinate is a brick or not. In addition this function will return which brick, top or bottom, is at the given x/y coordinate. This will be used when the ball is checking for collision against the bricks.
The difficulty is that the bricks are 4×8 instead of 8×8. Which means, a single tile can have 0, 1, or 2 bricks in it. So, this gives us the following possibilities for a single tile:
Top | Bottom |
Light | Light |
Light | Medium |
Light | Dark |
Light | None |
Medium | Light |
Medium | Medium |
Medium | Dark |
Medium | None |
Dark | Light |
Dark | Medium |
Dark | Dark |
Dark | None |
None | Light |
None | Medium |
None | Dark |
None | None |
The “CheckTopOrBottomBrick” function will return one of the following values based off of those possibilities:
- 0 – There is nothing solid at the given coordinate
- WALL – There is something solid at the given coordinate, but it’s not a brick
- TOP – The given coordinate intersects with a brick in the top half.
- BOTTOM – The given coordinate intersects with a brick in the bottom half.
uint8_t CheckTopOrBottomBrick(uint8_t x, uint8_t y){
if(x<=8) return WALL;
if(y<=8) return WALL;
if(x>=120) return WALL;
// Get the background tile at the given location
uint8_t brick =get_bkg_tile_xy(x/8,y/8);
// If this tile is zero, both the top and bottom tile have been destroyed
if(brick==0) return 0;
// Get the vertical middle of the tile
uint8_t mid = (y/8)*8+4;
uint8_t bottomMissing = brick==4||brick==8||brick==0x0C;
uint8_t topMissing = (brick-1)>=12&&(brick-1)<15;
// Return if it's the top or bottom
if((y/4)%2!=0 && !bottomMissing)return BOTTOM;
if((y/4)%2==0 && !topMissing)return TOP;
if(brick>=0x10)return WALL;
//
return 0;
}
Here’s a screenshot of the game’s VRAM for background tiles.
Updating Bricks
When a brick is hit, we need to update the background tile associated with it. A single background tile can have 0, 1, or 2 bricks drawn with it. So, in addition to the x & y coordinate, we pass in which side was hit from the “CheckTopOrBottomBrick” function.
If we pass TOP in, then the associated tile’s top brick will be cleared. Conversely, if we pass BOTTOM in, then then the associated tile’s top brick will be cleared. If these bricks are already empty, then nothing happens.
void UpdateBrick(uint8_t side, uint8_t x, uint8_t y){
uint8_t column=x/8;
uint8_t row=y/8;
// Get the background tile at the given location
uint8_t brick =get_bkg_tile_xy(column,row);
// If this tile is zero, both the top and bottom tile have been destroyed
if(brick==0) return;
if(brick>=USERINTERFACE_TILES_START)return;
uint8_t brickRow= 0;
side = (y/4)%2==0?TOP:BOTTOM;
// Clearing the brick
// If we're top only or bottom only
if((brick==4||brick==8||brick==12)&&side==TOP)set_bkg_tile_xy(column,row,0);
else if(brick>=13&&side==BOTTOM)set_bkg_tile_xy(column,row,0);
else{
uint8_t nextTile=brick;
if(side==BOTTOM){
if(brick>=1&&brick<=3)nextTile=4;
else if(brick>=5&&brick<=7)nextTile=8;
else if(brick>=9&&brick<=11)nextTile=12;
}
else if(side==TOP){
if(brick>=1&&brick<=3)nextTile=13;
else if(brick>=5&&brick<=7)nextTile=14;
else if(brick>=9&&brick<=11)nextTile=15;
}
set_bkg_tile_xy(column,row,nextTile);
}
}
Collision Detection
Now that we have our ball, the paddle, and our bricks drawn we can move onto handling collision amongst them. We split this into two functions:
- CollideBricksAgainstBall
- CollidePaddleAgainstBall
These functions are defined in collision.c, and called at the end of our game loop (when the ball is active).
Ball Collision against Bricks
For collision against the bricks, the ball will check for bricks at 2 different points.
- The horizontal edge in the direction the ball is moving
- The vertical edge in the direction the ball is moving
This means, if the ball is moving to the right it will check its right edge. If the ball is moving upward, it will check it’s top edge. The opposites are also true. If the ball is moving to the left, it will check it’s left edge. If the ball is moving downward, it will check it’s bottom edge.
This type of collision detection is really basic. An improvement would involve using actual circle/rectangle collision detection and response. This will not be done in this tutorial. Here is a link if that’s the direction you want to go in later.
We check for bricks using the previously created “CheckTopOrBottomBrick” function. If this function returns a non-zero value (WALL, TOP, or BOTTOM). It will change the ball’s velocity. If the function returns TOP or BOTTOM, it will additionally update the tile, increase our score, and decrease our blocksLeft variable.
void CollideBricksAgainstBall(){
int16_t verticalSide=SIGN(ballVelocityY);
int16_t horizontalSide=SIGN(ballVelocityX);
int16_t checkHorizontal = (ballX>>4)+horizontalSide*BALL_RADIUS;
int16_t checkVertical = (ballY>>4)+verticalSide*BALL_RADIUS;
uint8_t topBottomOrWall = CheckTopOrBottomBrick(ballX>>4,checkVertical);
// If the ball hit something
if(topBottomOrWall!=0){
ballVelocityY=-verticalSide*ABS(ballVelocityY);
// Increase Speed slightly with each bounce
ballVelocityY+=SIGN(ballVelocityY)*BALL_SPEEDUP;
// If we didn't hit a wall
if(topBottomOrWall!=WALL){
blocksLeft--;
// Play a sound
NR10_REG=0X00;
NR11_REG=0X81;
NR12_REG=0X43;
NR13_REG=0X73;
NR14_REG=0X86;
IncreaseScore(5);
UpdateBrick(topBottomOrWall,ballX>>4,checkVertical);
}else{
// Play a sound
NR10_REG=0X4A;
NR11_REG=0X81;
NR12_REG=0X43;
NR13_REG=0X56;
NR14_REG=0X86;
}
}
topBottomOrWall = CheckTopOrBottomBrick(checkHorizontal,ballY>>4);
// If the ball hit something
if(topBottomOrWall!=0){
// Reflect
ballVelocityX=-horizontalSide*ABS(ballVelocityX);
// Increase Speed slightly with each bounce
ballVelocityX+=SIGN(ballVelocityX)*BALL_SPEEDUP;
// If we didn't hit a wall
if(topBottomOrWall!=WALL){
// Decrease how many bullets we have
blocksLeft--;
// Play a sound
NR10_REG=0X00;
NR11_REG=0X81;
NR12_REG=0X43;
NR13_REG=0X73;
NR14_REG=0X86;
IncreaseScore(5);
// Update the brick at the location
UpdateBrick(topBottomOrWall,checkHorizontal,ballY>>4);
}else{
// Play a sound
NR10_REG=0X4A;
NR11_REG=0X81;
NR12_REG=0X43;
NR13_REG=0X56;
NR14_REG=0X86;
}
}
}
Ball Collision against The Paddle
The final aspect of collision is against the paddle.
For simplicity sake, we’ll consider the ball and the paddle to both be axis-aligned (non-rotated) rectangles. This makes intersection a simple task. For both the x-axis and the y-axis, we get the distance between their centers. If both distances are smaller than the sum of their half-width/half-heights, then we have collision.
In the case of collision, we’ll play a sound and reflect the ball’s y velocity. If the ball is near the edge of the paddle, we’ll also reflect the ball’s x velocity.
NOTE: Our variables for the ball and paddle’s positions are scaled integers. So, when comparing against non-scaled integers, we need to shift them to the right to utilize their true values.
void CollidePaddleAgainstBall(){
int16_t xd = (int16_t)(ballX>>4)-(int16_t)(paddleX>>4);
xd = ABS(xd);
if(xd<=PADDLE_SIZE/2+BALL_RADIUS){
int16_t yd =(int16_t)(ballY>>4)-(int16_t)(paddleY>>4);
yd = ABS(yd);
if(yd<=(PADDLE_HALF_THICKNESS+BALL_RADIUS)){
// Flip the y velocity
ballVelocityY=-ballVelocityY;
NR10_REG=0X2B;
NR11_REG=0X81;
NR12_REG=0X41;
NR13_REG=0X5C;
NR14_REG=0X86;
if(xd>PADDLE_SIZE/2-PADDLE_HALF_THICKNESS){
if(paddleX<ballX){
// Use the negative absolute value so it goes right
ballVelocityX=ABS(ballVelocityX);
ballX=MAX(ballX,paddleX+1+PADDLE_SIZE/2+((PADDLE_HALF_THICKNESS-BALL_RADIUS)<<4));
}else{
// Use the negative absolute value so it goes left
ballVelocityX=-ABS(ballVelocityX);
ballX=MIN(ballX,paddleX-1-PADDLE_SIZE/2-((PADDLE_HALF_THICKNESS-BALL_RADIUS)<<4));
}
}
ballY=MIN(ballY,paddleY-((1+PADDLE_HALF_THICKNESS+BALL_RADIUS)<<4));
}
}
}
Resetting our Games State
When all blocks are cleared, and before the game loop begins; we’ll call a function called “ResetToReady”. This function simply resets everything, including the score, and changes the ball’s state to ready.
void ResetToReady(){
ballState = BALLSTATE_READY;
score=0;
ResetUserInterface();
DrawLevelBricks(level);
ResetPaddle();
ResetBall();
}
Conclusion
That’s it! You should a basic Brick Breaker game like Alleyway. If you want to see the code for this tutorial, it can be found on GitHub. If you found this tutorial useful, please share on Social Media to help The Junkyard grow. If you have any questions or critique, feel free to leave a comment or send an email. Your critique helps The Junkyard grow.