Bricks and Collisions
A Breakout level is just a grid of rectangles. Build it once at startup, then remove bricks as the ball destroys them.
Laying out the grid
#include <vector>
std::vector<sf::RectangleShape> createBricks(int rows, int cols) {
std::vector<sf::RectangleShape> bricks;
float brickWidth = 75.f;
float brickHeight = 20.f;
float padding = 5.f;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
sf::RectangleShape brick(sf::Vector2f(brickWidth, brickHeight));
brick.setPosition(
col * (brickWidth + padding) + 35.f,
row * (brickHeight + padding) + 50.f
);
brick.setFillColor(sf::Color::Cyan);
bricks.push_back(brick);
}
}
return bricks;
}
Detecting collisions
Every drawable shape has getGlobalBounds(), an sf::FloatRect describing its bounding box in the window, and every sf::FloatRect has .intersects(other) to test overlap:
for (std::size_t i = 0; i < bricks.size(); i++) {
if (ball.shape.getGlobalBounds().intersects(bricks[i].getGlobalBounds())) {
ball.velocity.y = -ball.velocity.y; // simplified bounce
bricks.erase(bricks.begin() + i);
score += 10;
break; // only handle one collision per frame
}
}
The pieces
getGlobalBounds()returns the shape's bounding box after any position, rotation, or scale is applied, use this (not raw size) for collision checks..intersects(other)returnstrueif the two rectangles overlap at all.bricks.erase(bricks.begin() + i)removes the destroyed brick from the vector, shifting later elements down. Once a collision is handled,breakout of the loop, the indices are no longer valid after an erase.- Flipping
velocity.yon every brick hit is a simplification, real Breakout clones check which side of the brick was hit to decide whether to flipxory. That's a great stretch goal for the final project.
WARNING
Erasing from a std::vector while iterating it with a range-based for loop (for (auto& brick : bricks)) invalidates the iterator and causes undefined behavior. Use an index-based loop with break, like above, whenever the loop body might erase.