Build a Brick Breaker Game in C
Write a Brick Breaker game in C using raylib and compile it to a native .exe.
Introduction
30 Second Summary
Remember the satisfying crunch of smashing through rows of colorful bricks in an arcade game? Brick Breaker is one of those timeless games that feels simple to play but requires real engineering to build from scratch.
In this project, you will write a fully playable Brick Breaker game in the C programming language. You will set up a C development environment on Windows, learn how C organizes data with structs and arrays, and compile your source code into a native executable you can run directly.
What You'll Build
A classic Brick Breaker arcade game written in C using the raylib game library, compiled to a native Windows executable.
By the end of this project, you'll have:
- A complete C development environment on Windows with a compiler and game library ready to go.
- A single-file C game using structs, arrays, and a game loop to manage game state and rendering.
- A playable game window with a paddle, bouncing ball, and rows of destructible bricks that responds to keyboard input.
- Secret Mission: Add a lives system and a score counter that displays on screen.
Are there any prerequisites?
You should have basic programming experience in any language. If you understand variables, loops, conditionals, and functions in Python, JavaScript, Dart, or any other language, you are ready for this project.
Set Up Your C Development Environment
C programs need to be compiled into native executables before they can run. Unlike interpreted languages like Python or JavaScript, you need a dedicated compiler tool installed on your machine.
In this step, you'll install GCC (a C compiler) through a portable toolkit called w64devkit, download the raylib game library, and verify everything compiles correctly.
In this step, get ready to:
- Install a C compiler (GCC via w64devkit).
- Download and set up the raylib game library.
- Verify the full toolchain by compiling a test window.
Download and extract w64devkit
w64devkit is a portable C and C++ development kit for Windows. It bundles GCC (the compiler), Make, GDB (the debugger), and other tools into a single download with no installer required.
- Open the w64devkit releases page in your browser:
- https://github.com/skeeto/w64devkit/releases
- Under the 2.8.0 release (the latest), find the Assets section.
- Download the file named w64devkit-x64-2.8.0.7z.exe.
- Double-click the downloaded file to run the self-extracting archive.
- When prompted for a destination, extract it to C:\ so the result is C:\w64devkit.
What is w64devkit?
w64devkit is a self-contained portable development kit recommended by the raylib wiki for compiling C programs on Windows. It includes GCC 16.1.0 and requires no installation wizard or system changes beyond adding it to your PATH.
Now you need to add w64devkit to your system PATH so you can use gcc from any terminal window.
- Press the Windows key to open your search bar.
- Type Environment Variables and click Edit the system environment variables.
- In the System Properties window, click the Environment Variables button at the bottom.
- Under User variables, find and select the Path variable, then click Edit.
- Click New and type C:\w64devkit\bin.
- Click OK on all three open dialog windows to save your changes.
Now verify that gcc is accessible from a fresh terminal.
- Press the Windows key, type cmd, and press Enter to open a new Command Prompt.
- Check that GCC is available by running this command:
gcc --version
✔️ I see GCC version info
You should see output starting with gcc.exe (GCC) followed by a version number. GCC is ready to use from any terminal.
ⓧ gcc is not recognized
This means the PATH change has not taken effect.
- Make sure you opened a new Command Prompt after saving the PATH change. Terminals that were already open will not pick up the new PATH.
- Double-check that you typed C:\w64devkit\bin exactly (no trailing spaces, no extra backslash).
- Confirm the folder C:\w64devkit\bin exists and contains gcc.exe.
help me fix my PATH so gcc is recognized in Command Prompt
Download raylib pre-compiled binaries
raylib is a simple, beginner-friendly C library designed for learning game programming. You will download its pre-compiled binaries so you can link against them when compiling your game.
- Create your project folder by opening Command Prompt and running these commands:
mkdir C:\projects\brickbreaker
cd C:\projects\brickbreaker
- Open the raylib 6.0 release page in your browser:
- https://github.com/raysan5/raylib/releases/tag/6.0
- Under Assets, download the file named raylib-6.0_win64_mingw-w64.zip.
- Open the downloaded zip file.
- Inside the zip, you will see a folder named raylib-6.0_win64_mingw-w64. Open it.
- Copy the include folder into C:\projects\brickbreaker.
- Copy the lib folder into C:\projects\brickbreaker.
Confirm the files are in place by running this command from your project folder:
dir include\raylib.h
dir lib\libraylib.a
You should see both files listed without any errors.
Seeing a file not found error?
- Make sure you copied the include and lib folders directly into C:\projects\brickbreaker, not into a subfolder like C:\projects\brickbreaker\raylib-6.0_win64_mingw-w64\include.
- The correct paths should be C:\projects\brickbreaker\include\raylib.h and C:\projects\brickbreaker\lib\libraylib.a.
help me figure out why my raylib files are not in the right location
Install the C/C++ extension and test your setup
VS Code needs the C/C++ extension to provide syntax highlighting and IntelliSense for your C code. After installing it, you will create a small test file to confirm your full toolchain works.
- In VS Code, click the Extensions icon in the left sidebar (or press Ctrl+Shift+X).
- Type C/C++ in the search bar.
- Find the extension published by Microsoft (identifier: ms-vscode.cpptools) and click Install.
- Open your project folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to C:\projects\brickbreaker and click Select Folder.
Now create the test file that will verify raylib and GCC work together.
- In the Explorer sidebar, click the New File icon at the top of the file tree.
- Name the file main.c and press Enter.
- Paste the following code into main.c:
#include "raylib.h"
int main(void)
{
// Create an 800x600 window titled "Brick Breaker"
InitWindow(800, 600, "Brick Breaker");
SetTargetFPS(60);
// Game loop: runs until the user closes the window
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(DARKBLUE);
EndDrawing();
}
// Clean up the window resources
CloseWindow();
return 0;
}
What does this code do?
- InitWindow(800, 600, "Brick Breaker") creates an 800 by 600 pixel window with the title "Brick Breaker".
- SetTargetFPS(60) caps the game at 60 frames per second so it runs smoothly.
- The while (!WindowShouldClose()) loop is the game loop. It keeps running until you close the window.
- BeginDrawing() and EndDrawing() wrap all the rendering code. Everything drawn goes between these two calls.
- ClearBackground(DARKBLUE) fills the window with a dark blue color each frame.
- Save main.c by pressing Ctrl+S.
Now compile your test file into an executable.
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, usually above Tab).
- Make sure the terminal is in your project folder (C:\projects\brickbreaker).
- Compile your game by running this command:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
What does this compile command do?
- gcc invokes the C compiler.
- -o game.exe names the output file game.exe.
- -I include tells the compiler to look in the include folder for header files (like raylib.h).
- -L lib tells the linker to look in the lib folder for library files.
- -lraylib links the raylib static library.
- -lopengl32 -lgdi32 -lwinmm links the Windows system libraries that raylib depends on (OpenGL for rendering, GDI32 for window management, WinMM for timers).
- Run the compiled game by running:
.\game.exe
A window titled "Brick Breaker" with a dark blue background should appear on your screen. Close the window by clicking the X button or pressing Alt+F4.
Window did not appear?
- If you see an error about raylib.h not being found, check that include\raylib.h exists in your project folder. The -I include flag points to this relative path.
- If you see an error about libraylib.a not being found, check that lib\libraylib.a exists. The -L lib flag points to this relative path.
- If you see a message about gcc not being recognized, go back to the PATH setup and make sure C:\w64devkit\bin is added.
help me fix my raylib compilation error
✔️ Awesome, I've got everything!
Great. Double check you have saved main.c and that game.exe runs without errors.
ⓧ I'd like to double check the full code
Here is the complete main.c file for comparison:
#include "raylib.h"
int main(void)
{
InitWindow(800, 600, "Brick Breaker");
SetTargetFPS(60);
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(DARKBLUE);
EndDrawing();
}
CloseWindow();
return 0;
}
Your C development environment is fully set up. Next up, you'll define the game objects using C structs and draw them on screen.
Create the Game Objects with Structs
Your development environment is ready and you have a working game window. Now you need to define the actual game objects that will live inside that window.
In C, there are no classes. Instead, you group related data into structs. Each game object (the paddle, ball, and bricks) becomes a struct that holds its position, size, and state.
In this step, get ready to:
- Define C structs for the paddle, ball, and brick game objects.
- Initialize and draw all game objects on screen.
Define preprocessor constants for the brick grid
Before defining the structs, you need constants that control the brick grid layout. In C, #define creates compile-time constants that the compiler substitutes directly into your code.
- In VS Code, open main.c from the Explorer sidebar.
- Add the following constants between the #include "raylib.h" line and the int main(void) line:
#define BRICK_ROWS 5
#define BRICK_COLS 8
#define BRICK_WIDTH 80
#define BRICK_HEIGHT 25
#define BRICK_PADDING 5
#define BRICK_OFFSET_X 45
#define BRICK_OFFSET_Y 60
What do these constants control?
- BRICK_ROWS and BRICK_COLS set the grid dimensions (5 rows, 8 columns = 40 bricks total).
- BRICK_WIDTH, BRICK_HEIGHT, and BRICK_PADDING control each brick's size and the gap between them.
- BRICK_OFFSET_X and BRICK_OFFSET_Y position the grid from the top-left corner of the window.
- Save main.c.
You won't see a visual change yet. These constants are used by the struct initialization code you'll add next.
Define the Paddle, Ball, and Brick structs
Each game object needs a data structure to hold its properties. In C, a typedef struct lets you define a custom type that bundles multiple values together.
- Add the following three struct definitions below your #define constants, still above int main(void):
typedef struct {
float x;
float y;
float width;
float height;
float speed;
} Paddle;
typedef struct {
float x;
float y;
float speedX;
float speedY;
float radius;
int active;
} Ball;
typedef struct {
float x;
float y;
float width;
float height;
int active;
Color color;
} Brick;
What are these structs doing?
Each struct is a blueprint for a game object. Paddle holds position, size, and movement speed. Ball has position, velocity in both axes, a radius, and an active flag that tracks whether it has been launched.
Brick stores position, size, whether it is still visible (active), and its display color. Unlike Java or Python classes, C structs hold only data. There are no methods attached to them.
- Save main.c.
Still no visual change. The structs define shapes of data but don't create any game objects yet.
Seeing a compile error?
Make sure the Color type in the Brick struct is capitalized exactly. It comes from raylib's header file. Also check that all three struct blocks are placed above int main(void) and below the #define lines.
help me fix a struct definition error in my C code
Initialize the game objects in main()
Now that the struct types exist, you can create actual instances of them inside main(). C99 designated initializers let you name each field when setting values, which makes the code easier to read.
- Replace everything inside your int main(void) function (from the opening { to the closing }) with the following initialization code:
const int screenWidth = 800;
const int screenHeight = 600;
InitWindow(screenWidth, screenHeight, "Brick Breaker");
SetTargetFPS(60);
// Create the paddle, centered at the bottom of the screen
Paddle paddle = {
.x = screenWidth / 2.0f - 50,
.y = screenHeight - 40,
.width = 100,
.height = 15,
.speed = 7.0f
};
// Create the ball, sitting on top of the paddle
Ball ball = {
.x = screenWidth / 2.0f,
.y = paddle.y - 15,
.speedX = 0,
.speedY = 0,
.radius = 8,
.active = 0
};
// Declare the brick array and assign colors per row
Brick bricks[BRICK_ROWS * BRICK_COLS];
Color brickColors[BRICK_ROWS] = { RED, ORANGE, GOLD, GREEN, SKYBLUE };
// Fill the brick grid using nested loops
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
int index = row * BRICK_COLS + col;
bricks[index].x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING);
bricks[index].y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING);
bricks[index].width = BRICK_WIDTH;
bricks[index].height = BRICK_HEIGHT;
bricks[index].active = 1;
bricks[index].color = brickColors[row];
}
}
// Track game state
int gameOver = 0;
int gameWon = 0;
// Game loop with drawing
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(DARKBLUE);
// Draw the paddle as a white rectangle
DrawRectangleRec((Rectangle){ paddle.x, paddle.y, paddle.width, paddle.height }, WHITE);
// Draw the ball as a yellow circle
DrawCircleV((Vector2){ ball.x, ball.y }, ball.radius, YELLOW);
// Draw each active brick
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
DrawRectangleRec((Rectangle){ bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height }, bricks[i].color);
}
}
EndDrawing();
}
CloseWindow();
return 0;
What does this code do?
The .x = ... syntax is a C99 designated initializer. It lets you name each struct field directly, instead of relying on the order of values. The paddle starts centered horizontally and near the bottom. The ball starts sitting on top of the paddle with zero speed (not moving until launched).
The nested for loop creates 40 bricks in a 5x8 grid. Each brick's position is calculated from its row and column index, multiplied by the brick size plus padding. The brickColors array assigns a different color to each row.
The drawing section uses DrawRectangleRec() for rectangles and DrawCircleV() for the ball. These raylib functions take struct values directly, which is why you see inline (Rectangle){...} and (Vector2){...} casts.
- Save main.c.
- Open a terminal in VS Code by pressing Ctrl+` and compile by running:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
- Run the game by executing:
game.exe
You should see a dark blue window with a white paddle at the bottom, a yellow ball on top of the paddle, and five rows of colored bricks across the top. Nothing moves yet.
Window appears empty or compile fails?
- If you see only the dark blue background with no shapes, check that your drawing calls are inside the while loop between BeginDrawing() and EndDrawing().
- If compilation fails with "undeclared identifier" errors, make sure your three typedef struct blocks are above int main(void) in the file.
- If you see "expected expression" errors near the .x = lines, make sure your gcc is from w64devkit. It supports C99 designated initializers by default.
help me fix compilation errors in my raylib brick breaker game
✔️ Awesome, I've got everything!
Great. Double check you have saved main.c and that your game window shows all three types of objects (paddle, ball, bricks).
ⓧ I'd like to double check the full code
Here is the complete main.c at this point:
#include "raylib.h"
#define BRICK_ROWS 5
#define BRICK_COLS 8
#define BRICK_WIDTH 80
#define BRICK_HEIGHT 25
#define BRICK_PADDING 5
#define BRICK_OFFSET_X 45
#define BRICK_OFFSET_Y 60
typedef struct {
float x;
float y;
float width;
float height;
float speed;
} Paddle;
typedef struct {
float x;
float y;
float speedX;
float speedY;
float radius;
int active;
} Ball;
typedef struct {
float x;
float y;
float width;
float height;
int active;
Color color;
} Brick;
int main(void)
{
const int screenWidth = 800;
const int screenHeight = 600;
InitWindow(screenWidth, screenHeight, "Brick Breaker");
SetTargetFPS(60);
Paddle paddle = {
.x = screenWidth / 2.0f - 50,
.y = screenHeight - 40,
.width = 100,
.height = 15,
.speed = 7.0f
};
Ball ball = {
.x = screenWidth / 2.0f,
.y = paddle.y - 15,
.speedX = 0,
.speedY = 0,
.radius = 8,
.active = 0
};
Brick bricks[BRICK_ROWS * BRICK_COLS];
Color brickColors[BRICK_ROWS] = { RED, ORANGE, GOLD, GREEN, SKYBLUE };
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
int index = row * BRICK_COLS + col;
bricks[index].x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING);
bricks[index].y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING);
bricks[index].width = BRICK_WIDTH;
bricks[index].height = BRICK_HEIGHT;
bricks[index].active = 1;
bricks[index].color = brickColors[row];
}
}
int gameOver = 0;
int gameWon = 0;
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(DARKBLUE);
DrawRectangleRec((Rectangle){ paddle.x, paddle.y, paddle.width, paddle.height }, WHITE);
DrawCircleV((Vector2){ ball.x, ball.y }, ball.radius, YELLOW);
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
DrawRectangleRec((Rectangle){ bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height }, bricks[i].color);
}
}
EndDrawing();
}
CloseWindow();
return 0;
}
Your game objects are defined and drawn. Next up, you'll bring them to life by adding paddle movement and ball physics.
Add Movement and Ball Physics
In the previous step, you built all the visual pieces of your game. The paddle, ball, and bricks all appear on screen, but nothing moves.
A game without movement is just a picture. In this step, you'll bring the game to life by reading keyboard input, moving the paddle, and giving the ball realistic bouncing physics.
In this step, get ready to:
- Make the paddle respond to left and right arrow keys.
- Launch the ball with the spacebar and add wall-bouncing physics.
- Display a game over screen when the ball falls below the paddle.
Implement paddle movement and ball launch
Every frame of a game loop follows a pattern: read input, update state, then draw. Right now your loop only draws. You need to add the input-reading phase first.
- In main.c, find the BeginDrawing(); line inside your while loop.
- Add this input handling code above BeginDrawing();:
// Input
if (!gameOver && !gameWon) {
if (IsKeyDown(KEY_LEFT) && paddle.x > 0) {
paddle.x -= paddle.speed;
}
if (IsKeyDown(KEY_RIGHT) && paddle.x + paddle.width < screenWidth) {
paddle.x += paddle.speed;
}
if (!ball.active && IsKeyPressed(KEY_SPACE)) {
ball.active = 1;
ball.speedX = 4.0f;
ball.speedY = -4.0f;
}
}
What does this code do?
The outer if (!gameOver && !gameWon) guard prevents any input from registering once the game ends.
- IsKeyDown() returns true every frame the key is held. This gives smooth continuous paddle movement.
- The boundary checks (paddle.x > 0 and paddle.x + paddle.width < screenWidth) stop the paddle from sliding off the edges of the window.
- IsKeyPressed() fires only once per key press. This prevents the ball from being re-launched while it's already moving.
- Setting speedY = -4.0f sends the ball upward (negative Y is up in screen coordinates).
Why IsKeyDown vs IsKeyPressed?
raylib gives you two ways to check keys. IsKeyDown() is true every frame the key is held down. IsKeyPressed() is true only on the single frame the key first goes down.
Paddle movement needs IsKeyDown() so holding the arrow key keeps the paddle sliding. Ball launch needs IsKeyPressed() so one tap fires the ball exactly once.
- Save main.c.
- Compile and run to test paddle movement by running:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
game.exe
- Press the left and right arrow keys. The paddle should slide smoothly along the bottom of the screen without going past the edges.
- Pressing space won't visibly launch the ball yet. The ball's speed values are set, but you haven't written the physics code that actually moves it each frame.
Paddle not moving?
- Make sure the Input section is inside the while (!WindowShouldClose()) loop, not above it.
- Check that the code is above BeginDrawing(), not below EndDrawing(). Input must be processed before drawing the frame.
- Verify you're using KEY_LEFT and KEY_RIGHT (all caps). These are raylib constants.
Why isn't my paddle moving when I press arrow keys?
Add ball physics and wall bouncing
The ball's speed values are set when you press space, but nothing uses those values yet. You need an update phase that moves the ball by its speed every frame and handles what happens when it hits walls.
- In main.c, add this update section directly below the Input block you just wrote (still above BeginDrawing();):
// Update
if (ball.active && !gameOver && !gameWon) {
ball.x += ball.speedX;
ball.y += ball.speedY;
// Wall collisions
if (ball.x - ball.radius <= 0 || ball.x + ball.radius >= screenWidth) {
ball.speedX *= -1;
}
if (ball.y - ball.radius <= 0) {
ball.speedY *= -1;
}
// Ball falls below screen
if (ball.y + ball.radius >= screenHeight) {
gameOver = 1;
}
} else if (!ball.active && !gameOver && !gameWon) {
ball.x = paddle.x + paddle.width / 2.0f;
ball.y = paddle.y - ball.radius;
}
What does this code do?
- ball.x += ball.speedX and ball.y += ball.speedY move the ball's position by its speed every frame. At 60 FPS, this creates smooth motion.
- Multiplying a speed by -1 reverses direction. If the ball hits a side wall, flip horizontal speed. If it hits the top, flip vertical speed.
- The ball.radius offset in boundary checks means the ball bounces when its edge touches the wall, not when its center passes through.
- The else if branch handles when the ball hasn't launched yet. It keeps the ball centered on the paddle so it follows the paddle's movement.
- Now add a // Draw comment directly above your existing BeginDrawing(); line to label the drawing phase of your game loop.
Next, you need to show the player helpful text. Before launch, they need to know to press space. On game over, they need to see the result.
- Find the EndDrawing(); line at the bottom of your drawing section.
- Add these text-drawing blocks just above EndDrawing();:
if (!ball.active && !gameOver && !gameWon) {
DrawText("Press SPACE to launch", screenWidth / 2 - 100, screenHeight / 2, 20, LIGHTGRAY);
}
if (gameOver) {
DrawText("GAME OVER", screenWidth / 2 - 80, screenHeight / 2, 30, RED);
}
What does this code do?
- DrawText() renders a string at a given (x, y) position with a font size and color.
- The first block only shows "Press SPACE to launch" when the ball is inactive and the game is still running. Once you press space, this text disappears.
- The second block shows "GAME OVER" in red when gameOver becomes 1 (i.e. when the ball falls below the screen).
- Save main.c.
- Compile and run your game by running:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
game.exe
- You should see "Press SPACE to launch" in the center of the screen.
- Move the paddle left and right. The ball should follow the paddle before launch.
- Press space. The ball launches diagonally upward and bounces off the top and side walls.
- Let the ball fall past the paddle. You should see "GAME OVER" in red.
Ball not moving after pressing space?
- Check that your Update section is between the Input section and BeginDrawing(). If it's inside the drawing block, the ball position updates but may not behave correctly.
- Make sure the condition reads if (ball.active && !gameOver && !gameWon). If you wrote !ball.active instead, the physics only run when the ball is NOT launched.
- Confirm ball.speedY = -4.0f has a negative sign. Positive Y goes downward in screen coordinates, so a negative value sends the ball up.
Help me debug why my ball doesn't move after pressing space.
✔️ Awesome, I've got everything!
Great. Double check you've saved main.c and confirmed the ball bounces off walls before moving on.
ⓧ I'd like to double check the full code
Here is the complete main.c at this point. Compare it with your file to find any differences.
#include "raylib.h"
#define BRICK_ROWS 5
#define BRICK_COLS 8
#define BRICK_WIDTH 80
#define BRICK_HEIGHT 25
#define BRICK_PADDING 5
#define BRICK_OFFSET_X 45
#define BRICK_OFFSET_Y 60
typedef struct {
float x;
float y;
float width;
float height;
float speed;
} Paddle;
typedef struct {
float x;
float y;
float speedX;
float speedY;
float radius;
int active;
} Ball;
typedef struct {
float x;
float y;
float width;
float height;
int active;
Color color;
} Brick;
int main(void)
{
const int screenWidth = 800;
const int screenHeight = 600;
InitWindow(screenWidth, screenHeight, "Brick Breaker");
SetTargetFPS(60);
Paddle paddle = {
.x = screenWidth / 2.0f - 50,
.y = screenHeight - 40,
.width = 100,
.height = 15,
.speed = 7.0f
};
Ball ball = {
.x = screenWidth / 2.0f,
.y = paddle.y - 15,
.speedX = 0,
.speedY = 0,
.radius = 8,
.active = 0
};
Brick bricks[BRICK_ROWS * BRICK_COLS];
Color brickColors[BRICK_ROWS] = { RED, ORANGE, GOLD, GREEN, SKYBLUE };
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
int index = row * BRICK_COLS + col;
bricks[index].x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING);
bricks[index].y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING);
bricks[index].width = BRICK_WIDTH;
bricks[index].height = BRICK_HEIGHT;
bricks[index].active = 1;
bricks[index].color = brickColors[row];
}
}
int gameOver = 0;
int gameWon = 0;
while (!WindowShouldClose())
{
// Input
if (!gameOver && !gameWon) {
if (IsKeyDown(KEY_LEFT) && paddle.x > 0) {
paddle.x -= paddle.speed;
}
if (IsKeyDown(KEY_RIGHT) && paddle.x + paddle.width < screenWidth) {
paddle.x += paddle.speed;
}
if (!ball.active && IsKeyPressed(KEY_SPACE)) {
ball.active = 1;
ball.speedX = 4.0f;
ball.speedY = -4.0f;
}
}
// Update
if (ball.active && !gameOver && !gameWon) {
ball.x += ball.speedX;
ball.y += ball.speedY;
if (ball.x - ball.radius <= 0 || ball.x + ball.radius >= screenWidth) {
ball.speedX *= -1;
}
if (ball.y - ball.radius <= 0) {
ball.speedY *= -1;
}
if (ball.y + ball.radius >= screenHeight) {
gameOver = 1;
}
} else if (!ball.active && !gameOver && !gameWon) {
ball.x = paddle.x + paddle.width / 2.0f;
ball.y = paddle.y - ball.radius;
}
// Draw
BeginDrawing();
ClearBackground(DARKBLUE);
DrawRectangleRec((Rectangle){ paddle.x, paddle.y, paddle.width, paddle.height }, WHITE);
DrawCircleV((Vector2){ ball.x, ball.y }, ball.radius, YELLOW);
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
DrawRectangleRec((Rectangle){ bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height }, bricks[i].color);
}
}
if (!ball.active && !gameOver && !gameWon) {
DrawText("Press SPACE to launch", screenWidth / 2 - 100, screenHeight / 2, 20, LIGHTGRAY);
}
if (gameOver) {
DrawText("GAME OVER", screenWidth / 2 - 80, screenHeight / 2, 30, RED);
}
EndDrawing();
}
CloseWindow();
return 0;
}
Your ball bounces and your game can end. Next up, you'll add collision detection so the ball actually interacts with the paddle and destroys bricks.
Implement Collision Detection
Your ball bounces off the walls, but it flies straight through the paddle and the bricks. That is because your game has no collision detection yet.
Collision detection checks whether two game objects overlap each frame. In this step, you will use raylib's built-in CheckCollisionCircleRec() function to detect when the ball hits the paddle or a brick, then react with the right physics.
In this step, get ready to:
- Add ball-to-paddle collision that bounces the ball at different angles.
- Add ball-to-brick collision that destroys bricks on contact.
- Display a win message when all bricks are destroyed.
Add ball-to-paddle collision
The paddle is a rectangle and the ball is a circle. CheckCollisionCircleRec() takes a circle center, a radius, and a rectangle, then returns true if they overlap.
- In main.c, scroll to the // Update section inside the game loop.
- Find the game over check that looks like this:
if (ball.y + ball.radius >= screenHeight) {
gameOver = 1;
}
- Add the following paddle collision code directly below that closing }:
// Paddle collision
Rectangle paddleRec = { paddle.x, paddle.y, paddle.width, paddle.height };
if (CheckCollisionCircleRec((Vector2){ ball.x, ball.y }, ball.radius, paddleRec)) {
ball.speedY *= -1;
ball.y = paddle.y - ball.radius;
float hitPos = (ball.x - paddle.x) / paddle.width;
ball.speedX = (hitPos - 0.5f) * 8.0f;
}
What does this code do?
This code creates a Rectangle from the paddle's position and size, then checks if the ball circle overlaps it.
- ball.speedY *= -1 reverses the ball's vertical direction so it bounces upward.
- ball.y = paddle.y - ball.radius snaps the ball to just above the paddle so it does not get stuck inside.
- hitPos calculates where on the paddle the ball landed (0.0 = left edge, 1.0 = right edge). The formula (hitPos - 0.5f) * 8.0f converts that into a horizontal speed. Hitting the center sends the ball straight up. Hitting the edges sends it at a sharp angle.
- Save main.c.
- Compile and run the game by running this command in your terminal:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
- Run game.exe, press Space to launch the ball, then move the paddle to catch it.
- Try hitting the ball with the left edge, center, and right edge of the paddle to see the angle change.
Ball passes through the paddle?
- Make sure the paddle collision code is inside the if (ball.active && !gameOver && !gameWon) block, not outside it.
- Check that paddleRec uses paddle.x, paddle.y, paddle.width, paddle.height in that exact order.
- Verify the code is placed after the game over check, not before it.
Still stuck? Help me debug why my ball passes through the paddle in my raylib Brick Breaker game.
Add ball-to-brick collision
The same circle-to-rectangle check works for bricks. The difference is you need to loop through all 40 bricks and skip any that are already destroyed.
- In main.c, find the paddle collision code you just added.
- Add the following brick collision code directly below the paddle collision's closing }:
// Brick collisions
int activeBricks = 0;
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
activeBricks++;
Rectangle brickRec = { bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height };
if (CheckCollisionCircleRec((Vector2){ ball.x, ball.y }, ball.radius, brickRec)) {
bricks[i].active = 0;
ball.speedY *= -1;
activeBricks--;
}
}
}
if (activeBricks == 0) {
gameWon = 1;
}
What does this code do?
- activeBricks counts how many bricks are still alive during this frame.
- The loop skips any brick with active == 0 (already destroyed).
- When the ball overlaps an active brick, bricks[i].active = 0 marks it as destroyed and the ball bounces off.
- After the loop, if zero bricks remain, gameWon = 1 triggers the win state.
You also need to display the win message when all bricks are destroyed.
- Scroll down to the // Draw section and find the gameOver draw block:
if (gameOver) {
DrawText("GAME OVER", screenWidth / 2 - 80, screenHeight / 2, 30, RED);
}
- Add this code directly below it to display the win message:
if (gameWon) {
DrawText("YOU WIN!", screenWidth / 2 - 70, screenHeight / 2, 30, GREEN);
}
- Save main.c.
- Compile and run the game by running:
gcc -o game.exe main.c -I include -L lib -lraylib -lopengl32 -lgdi32 -lwinmm
- Run game.exe and play the game. Bricks should disappear when the ball hits them.
- Destroy all the bricks to see "YOU WIN!" appear in green text.
Bricks not disappearing?
- Make sure the brick collision loop is inside the if (ball.active && !gameOver && !gameWon) block, after the paddle collision code.
- Check that brickRec uses bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height (not bricks[0] or a fixed value).
- Verify the if (activeBricks == 0) check is outside the for-loop but still inside the if (ball.active ... block.
Need help? Help me debug why bricks are not disappearing when the ball hits them in my raylib game.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.c and that your game compiles without errors.
ⓧ I'd like to double check the full code
#include "raylib.h"
#define BRICK_ROWS 5
#define BRICK_COLS 8
#define BRICK_WIDTH 80
#define BRICK_HEIGHT 25
#define BRICK_PADDING 5
#define BRICK_OFFSET_X 45
#define BRICK_OFFSET_Y 60
typedef struct {
float x;
float y;
float width;
float height;
float speed;
} Paddle;
typedef struct {
float x;
float y;
float speedX;
float speedY;
float radius;
int active;
} Ball;
typedef struct {
float x;
float y;
float width;
float height;
int active;
Color color;
} Brick;
int main(void)
{
const int screenWidth = 800;
const int screenHeight = 600;
InitWindow(screenWidth, screenHeight, "Brick Breaker");
SetTargetFPS(60);
Paddle paddle = {
.x = screenWidth / 2.0f - 50,
.y = screenHeight - 40,
.width = 100,
.height = 15,
.speed = 7.0f
};
Ball ball = {
.x = screenWidth / 2.0f,
.y = paddle.y - 15,
.speedX = 0,
.speedY = 0,
.radius = 8,
.active = 0
};
Brick bricks[BRICK_ROWS * BRICK_COLS];
Color brickColors[BRICK_ROWS] = { RED, ORANGE, GOLD, GREEN, SKYBLUE };
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
int index = row * BRICK_COLS + col;
bricks[index].x = BRICK_OFFSET_X + col * (BRICK_WIDTH + BRICK_PADDING);
bricks[index].y = BRICK_OFFSET_Y + row * (BRICK_HEIGHT + BRICK_PADDING);
bricks[index].width = BRICK_WIDTH;
bricks[index].height = BRICK_HEIGHT;
bricks[index].active = 1;
bricks[index].color = brickColors[row];
}
}
int gameOver = 0;
int gameWon = 0;
while (!WindowShouldClose())
{
// Input
if (!gameOver && !gameWon) {
if (IsKeyDown(KEY_LEFT) && paddle.x > 0) {
paddle.x -= paddle.speed;
}
if (IsKeyDown(KEY_RIGHT) && paddle.x + paddle.width < screenWidth) {
paddle.x += paddle.speed;
}
if (!ball.active && IsKeyPressed(KEY_SPACE)) {
ball.active = 1;
ball.speedX = 4.0f;
ball.speedY = -4.0f;
}
}
// Update
if (ball.active && !gameOver && !gameWon) {
ball.x += ball.speedX;
ball.y += ball.speedY;
// Wall collisions
if (ball.x - ball.radius <= 0 || ball.x + ball.radius >= screenWidth) {
ball.speedX *= -1;
}
if (ball.y - ball.radius <= 0) {
ball.speedY *= -1;
}
// Ball falls below screen
if (ball.y + ball.radius >= screenHeight) {
gameOver = 1;
}
// Paddle collision
Rectangle paddleRec = { paddle.x, paddle.y, paddle.width, paddle.height };
if (CheckCollisionCircleRec((Vector2){ ball.x, ball.y }, ball.radius, paddleRec)) {
ball.speedY *= -1;
ball.y = paddle.y - ball.radius;
float hitPos = (ball.x - paddle.x) / paddle.width;
ball.speedX = (hitPos - 0.5f) * 8.0f;
}
// Brick collisions
int activeBricks = 0;
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
activeBricks++;
Rectangle brickRec = { bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height };
if (CheckCollisionCircleRec((Vector2){ ball.x, ball.y }, ball.radius, brickRec)) {
bricks[i].active = 0;
ball.speedY *= -1;
activeBricks--;
}
}
}
if (activeBricks == 0) {
gameWon = 1;
}
} else if (!ball.active && !gameOver && !gameWon) {
ball.x = paddle.x + paddle.width / 2.0f;
ball.y = paddle.y - ball.radius;
}
// Draw
BeginDrawing();
ClearBackground(DARKBLUE);
DrawRectangleRec((Rectangle){ paddle.x, paddle.y, paddle.width, paddle.height }, WHITE);
DrawCircleV((Vector2){ ball.x, ball.y }, ball.radius, YELLOW);
for (int i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
if (bricks[i].active) {
DrawRectangleRec((Rectangle){ bricks[i].x, bricks[i].y, bricks[i].width, bricks[i].height }, bricks[i].color);
}
}
if (!ball.active && !gameOver && !gameWon) {
DrawText("Press SPACE to launch", screenWidth / 2 - 100, screenHeight / 2, 20, LIGHTGRAY);
}
if (gameOver) {
DrawText("GAME OVER", screenWidth / 2 - 80, screenHeight / 2, 30, RED);
}
if (gameWon) {
DrawText("YOU WIN!", screenWidth / 2 - 70, screenHeight / 2, 30, GREEN);
}
EndDrawing();
}
CloseWindow();
return 0;
}
Secret mission
Add Lives and Score
Your game ends after a single missed ball. Real arcade games give you multiple chances and reward your accuracy with points. Can you add a lives system, a score counter, and a restart button to make your Brick Breaker feel like a true arcade experience?
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Project folder containing main.c, game.exe, include/, and lib/.
- w64devkit portable compiler (extracted to C:\w64devkit, added to Windows PATH).
- C/C++ VS Code extension (ms-vscode.cpptools).
Keep everything running
No action needed. Choose this if you're still actively building or want to keep experimenting with your Brick Breaker game.
- Your project folder stays where it is. You can recompile anytime with the same gcc command.
- w64devkit remains on your system at C:\w64devkit and stays available in your PATH for future C projects.
- The C/C++ extension stays installed in VS Code for any future C or C++ work.
Pause - I'll come back to this later
There are no running processes to pause. The game only runs when you launch game.exe directly.
- Close the game window if it is open.
- Close the project in VS Code if you want to free up editor tabs.
- Everything else stays in place for when you return.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow these steps to fully clean up.
- Delete your project folder. Open Command Prompt and run:
rmdir /s /q C:\projects\brickbreaker
- Delete the w64devkit folder:
rmdir /s /q C:\w64devkit
- Remove C:\w64devkit\bin from your Windows PATH. Press the Windows key, search for Environment Variables, and open Edit the system environment variables.
- Click Environment Variables, select Path under User variables, and click Edit.
- Find and remove the entry for C:\w64devkit\bin, then click OK on all dialogs.
- Uninstall the C/C++ extension in VS Code. Open VS Code, click the Extensions icon in the left sidebar, find C/C++, and click Uninstall.
Nice Work!
Nice Work!
Nice work! You just built a fully playable Brick Breaker game from scratch in C, compiled it into a native Windows executable, and learned how real game developers structure their code.
You've learned how to:
- Set up a complete C development environment on Windows with GCC (via w64devkit) and raylib, compiling native executables with a single gcc command.
- Write a game in pure C using structs to define game objects, arrays to manage a grid of bricks, and raylib functions for collision detection.
- Implement a complete game loop with input handling, ball physics, wall and paddle bouncing, and win/lose conditions.
- Secret Mission: Added a lives system, score tracking, and a restart mechanism using TextFormat() for on-screen HUD rendering.
Ready to quiz yourself?