Brick by Brick!
Breakout in C++
Breakout rebuilt the C++ way: a Brick class, std::vector, constructors, and collision that knows which side it hit. Your third Breakout, but your first with real object-oriented C++.
Breakout rebuilt the C++ way: a Brick class, std::vector, constructors, and collision that knows which side it hit. Your third Breakout, but your first with real object-oriented C++.
sf::VideoMode({800, 500})sf::VideoMode({800, 500}) (curly braces), the new event loop while (const std::optional e = window.pollEvent())while (const std::optional e = window.pollEvent()) with e->is<sf::Event::Closed>()e->is<sf::Event::Closed>(), sf::Keyboard::Key::Leftsf::Keyboard::Key::Left (add ::Key::Key), and setPosition({x, y})setPosition({x, y}) / move({dx, dy})move({dx, dy}) (curly braces). The โ
complete finished code at the bottom of this page is full SFML 3 and compiles on a fresh install, use it as your reference.sf::RenderWindowsf::RenderWindow, the game loop and event polling. If you're starting here or need that boilerplate, grab it in one click: Episode 1 setup โ ยท C++ Cheatsheet โSet up 700ร520 Breakout with a paddle that follows the mouse.
sf::RectangleShape paddle(sf::Vector2f(110, 14));
paddle.setFillColor(sf::Color(255, 119, 0));
// in the loop:
float mx = (float)sf::Mouse::getPosition(window).x;
float px = mx - 55; // centre on cursor
px = std::max(0.f, std::min(590.f, px)); // clamp
paddle.setPosition({px, 480});sf::RectangleShape paddle(sf::Vector2f(110, 14));
paddle.setFillColor(sf::Color(255, 119, 0));
// in the loop:
float mx = (float)sf::Mouse::getPosition(window).x;
float px = mx - 55; // centre on cursor
px = std::max(0.f, std::min(590.f, px)); // clamp
paddle.setPosition({px, 480});
Write a Brick class and build the wall in a std::vector.
class Brick {
public:
sf::RectangleShape shape;
int points;
bool alive = true;
Brick(int row, int col) {
shape.setSize(sf::Vector2f(64, 22));
shape.setPosition({col * 68.f + 12, row * 26.f + 50});
points = (5 - row) * 10;
static const sf::Color colours[5] = {
{248,113,113}, {251,146,60}, {250,204,21},
{74,222,128}, {96,165,250} };
shape.setFillColor(colours[row]);
}
};
std::vector<Brick> bricks;
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.emplace_back(r, c);class Brick {
public:
sf::RectangleShape shape;
int points;
bool alive = true;
Brick(int row, int col) {
shape.setSize(sf::Vector2f(64, 22));
shape.setPosition({col * 68.f + 12, row * 26.f + 50});
points = (5 - row) * 10;
static const sf::Color colours[5] = {
{248,113,113}, {251,146,60}, {250,204,21},
{74,222,128}, {96,165,250} };
shape.setFillColor(colours[row]);
}
};
std::vector<Brick> bricks;
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.emplace_back(r, c);
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.(r, (r, ););
A dt-driven ball that bounces off walls and takes spin from the paddle.
sf::CircleShape ball(7);
ball.setPosition({343, 300});
float bvx = 260.f, bvy = -260.f;
ball.move({bvx * dt, bvy * dt});
auto p = ball.getPosition();
if (p.x <= 0 || p.x + 14 >= 700) bvx = -bvx;
if (p.y <= 0) bvy = -bvy;
if (ball.getGlobalBounds().findIntersection(paddle.getGlobalBounds()) && bvy > 0) {
bvy = -bvy;
float offset = (p.x + 7) - (px + 55);
bvx = std::max(-500.f, std::min(500.f, offset * 4.f));
}sf::CircleShape ball(7);
ball.setPosition({343, 300});
float bvx = 260.f, bvy = -260.f;
ball.move({bvx * dt, bvy * dt});
auto p = ball.getPosition();
if (p.x <= 0 || p.x + 14 >= 700) bvx = -bvx;
if (p.y <= 0) bvy = -bvy;
if (ball.getGlobalBounds().findIntersection(paddle.getGlobalBounds()) && bvy > 0) {
bvy = -bvy;
float offset = (p.x + 7) - (px + 55);
bvx = std::max(-500.f, std::min(500.f, offset * 4.f));
}
Bounce correctly off brick sides, not just up/down.
for (auto& brick : bricks) {
if (!brick.alive) continue;
sf::FloatRect bb = ball.getGlobalBounds();
sf::FloatRect kb = brick.shape.getGlobalBounds();
if (bb.findIntersection(kb)) {
float overlapX = std::min(bb.position.x + bb.size.x, kb.position.x + kb.size.x)
- std::max(bb.position.x, kb.position.x);
float overlapY = std::min(bb.position.y + bb.size.y, kb.position.y + kb.size.y)
- std::max(bb.position.y, kb.position.y);
if (overlapX < overlapY) bvx = -bvx; // side hit
else bvy = -bvy; // top/bottom hit
brick.alive = false;
score += brick.points;
break;
}
}for (auto& brick : bricks) {
if (!brick.alive) continue;
sf::FloatRect bb = ball.getGlobalBounds();
sf::FloatRect kb = brick.shape.getGlobalBounds();
if (bb.findIntersection(kb)) {
float overlapX = std::min(bb.position.x + bb.size.x, kb.position.x + kb.size.x)
- std::max(bb.position.x, kb.position.x);
float overlapY = std::min(bb.position.y + bb.size.y, kb.position.y + kb.size.y)
- std::max(bb.position.y, kb.position.y);
if (overlapX < overlapY) bvx = -bvx; // side hit
else bvy = -bvy; // top/bottom hit
brick.alive = false;
score += brick.points;
break;
}
}
if (overlapX < overlapY) if (overlapX < overlapY) = -bvx; // came in from the side else = -bvx; // came in from the side else = -bvy; brick.alive = = -bvy; brick.alive = ;;
Skip dead bricks efficiently and learn the famous C++ idiom.
#include <algorithm>
// erase-remove (the famous idiom, for reference):
bricks.erase(
std::remove_if(bricks.begin(), bricks.end(),
[](const Brick& b){ return !b.alive; }),
bricks.end());
// win check with flags:
bool won = std::none_of(bricks.begin(), bricks.end(),
[](const Brick& b){ return b.alive; });#include <algorithm>
// erase-remove (the famous idiom, for reference):
bricks.erase(
std::remove_if(bricks.begin(), bricks.end(),
[](const Brick& b){ return !b.alive; }),
bricks.end());
// win check with flags:
bool won = std::none_of(bricks.begin(), bricks.end(),
[](const Brick& b){ return b.alive; });
Finish with lives, a losing screen, a winning screen, and restart.
int lives = 3;
enum State { PLAY, WIN, LOSE };
State state = PLAY;
if (p.y > 520) {
lives--;
ball.setPosition({343, 300});
bvx = 260; bvy = -260;
if (lives == 0) state = LOSE;
}
if (won) state = WIN;
// restart on R:
bricks.clear();
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.emplace_back(r, c);
lives = 3; score = 0; state = PLAY;int lives = 3;
enum State { PLAY, WIN, LOSE };
State state = PLAY;
if (p.y > 520) {
lives--;
ball.setPosition({343, 300});
bvx = 260; bvy = -260;
if (lives == 0) state = LOSE;
}
if (won) state = WIN;
// restart on R:
bricks.clear();
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.emplace_back(r, c);
lives = 3; score = 0; state = PLAY;
enum State { PLAY, enum State { PLAY, , LOSE };
State state = , LOSE };
State state = ;
if (lives == 0) state = ;
if (lives == 0) state = ;;
The whole game in one file, written in current SFML 3 (what the install step gives you today) and compiled with g++ + SFML 3.0 to confirm it builds. The step snippets above use classic SFML 2 names; this is your working reference. Build it with g++ breakout.cpp -o breakout -lsfml-graphics -lsfml-window -lsfml-systemg++ breakout.cpp -o breakout -lsfml-graphics -lsfml-window -lsfml-system then run ./breakout./breakout (on Windows, from the MSYS2 MinGW terminal so it finds the SFML DLLs).
// Complete C++ Breakout for SFML 3. Build:
// g++ breakout.cpp -o breakout -lsfml-graphics -lsfml-window -lsfml-system
#include <SFML/Graphics.hpp>
#include <optional>
#include <vector>
struct Brick { sf::FloatRect rect; int row; bool alive; };
int main() {
sf::RenderWindow window(sf::VideoMode({700, 520}), "C++ Breakout");
window.setFramerateLimit(60);
sf::Color rowCol[5] = {
{248,113,113},{251,146,60},{250,204,21},{74,222,128},{96,165,250}
};
float padX = 295, bx = 343, by = 300, bvx = 4, bvy = -4;
int score = 0, lives = 3;
std::vector<Brick> bricks;
auto build = [&]() {
bricks.clear();
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.push_back({ sf::FloatRect({c*68.f+12, r*26.f+50}, {64.f, 22.f}), r, true });
};
build();
while (window.isOpen()) {
while (const std::optional event = window.pollEvent())
if (event->is<sf::Event::Closed>()) window.close();
float mx = (float)sf::Mouse::getPosition(window).x;
padX = mx - 55.f; // centre the 110px-wide paddle on the mouse
if (padX < 0) padX = 0; if (padX > 590) padX = 590;
bx += bvx; by += bvy;
if (bx <= 0 || bx + 14 >= 700) bvx = -bvx;
if (by <= 0) bvy = -bvy;
sf::FloatRect ball({bx, by}, {14.f, 14.f});
sf::FloatRect pad({padX, 480.f}, {110.f, 14.f});
if (ball.findIntersection(pad) && bvy > 0) {
bvy = -bvy;
float hit = (bx + 7) - (padX + 55);
bvx = hit / 12.f;
if (bvx == 0) bvx = 1;
}
for (auto& b : bricks) {
if (b.alive && ball.findIntersection(b.rect)) {
b.alive = false; score += (5 - b.row) * 10; bvy = -bvy; break;
}
}
if (by - 14 > 520) {
if (--lives <= 0) { build(); lives = 3; score = 0; }
bx = 343; by = 300; bvx = 4; bvy = -4;
}
bool anyAlive = false; for (auto& b : bricks) if (b.alive) anyAlive = true;
if (!anyAlive) { build(); bx = 343; by = 300; }
window.clear(sf::Color(15, 18, 32));
for (auto& b : bricks) if (b.alive) {
sf::RectangleShape r(b.rect.size);
r.setPosition(b.rect.position);
r.setFillColor(rowCol[b.row]);
window.draw(r);
}
sf::RectangleShape pd({110.f, 14.f}); pd.setPosition({padX, 480.f});
pd.setFillColor(sf::Color(255,119,0)); window.draw(pd);
sf::CircleShape ballShape(7.f); ballShape.setPosition({bx, by});
window.draw(ballShape);
window.display();
}
}// Complete C++ Breakout for SFML 3. Build:
// g++ breakout.cpp -o breakout -lsfml-graphics -lsfml-window -lsfml-system
#include <SFML/Graphics.hpp>
#include <optional>
#include <vector>
struct Brick { sf::FloatRect rect; int row; bool alive; };
int main() {
sf::RenderWindow window(sf::VideoMode({700, 520}), "C++ Breakout");
window.setFramerateLimit(60);
sf::Color rowCol[5] = {
{248,113,113},{251,146,60},{250,204,21},{74,222,128},{96,165,250}
};
float padX = 295, bx = 343, by = 300, bvx = 4, bvy = -4;
int score = 0, lives = 3;
std::vector<Brick> bricks;
auto build = [&]() {
bricks.clear();
for (int r = 0; r < 5; r++)
for (int c = 0; c < 10; c++)
bricks.push_back({ sf::FloatRect({c*68.f+12, r*26.f+50}, {64.f, 22.f}), r, true });
};
build();
while (window.isOpen()) {
while (const std::optional event = window.pollEvent())
if (event->is<sf::Event::Closed>()) window.close();
float mx = (float)sf::Mouse::getPosition(window).x;
padX = mx - 55.f; // centre the 110px-wide paddle on the mouse
if (padX < 0) padX = 0; if (padX > 590) padX = 590;
bx += bvx; by += bvy;
if (bx <= 0 || bx + 14 >= 700) bvx = -bvx;
if (by <= 0) bvy = -bvy;
sf::FloatRect ball({bx, by}, {14.f, 14.f});
sf::FloatRect pad({padX, 480.f}, {110.f, 14.f});
if (ball.findIntersection(pad) && bvy > 0) {
bvy = -bvy;
float hit = (bx + 7) - (padX + 55);
bvx = hit / 12.f;
if (bvx == 0) bvx = 1;
}
for (auto& b : bricks) {
if (b.alive && ball.findIntersection(b.rect)) {
b.alive = false; score += (5 - b.row) * 10; bvy = -bvy; break;
}
}
if (by - 14 > 520) {
if (--lives <= 0) { build(); lives = 3; score = 0; }
bx = 343; by = 300; bvx = 4; bvy = -4;
}
bool anyAlive = false; for (auto& b : bricks) if (b.alive) anyAlive = true;
if (!anyAlive) { build(); bx = 343; by = 300; }
window.clear(sf::Color(15, 18, 32));
for (auto& b : bricks) if (b.alive) {
sf::RectangleShape r(b.rect.size);
r.setPosition(b.rect.position);
r.setFillColor(rowCol[b.row]);
window.draw(r);
}
sf::RectangleShape pd({110.f, 14.f}); pd.setPosition({padX, 480.f});
pd.setFillColor(sf::Color(255,119,0)); window.draw(pd);
sf::CircleShape ballShape(7.f); ballShape.setPosition({bx, by});
window.draw(ballShape);
window.display();
}
}