4
\$\begingroup\$

I'm doing this small Breakout clone for school, and I've looked at heaps of people's takes on Breakout and tried to combine bits and pieces that I liked. Only problem that I'm having is getting the game to end when the ball hits the bottom window edge and sounds.

You should be able to copy/paste the code into a project, only issues being with the sound and text.

#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>
#include <iostream>
#include <sstream>
using namespace std;
using namespace sf;
int x = 5;
constexpr int windowWidth ( 800 ), windowHeight( 600 );
constexpr float ballRadius( 10.f ), ballVelocity( 6.f );
constexpr float paddleWidth( 100.f ), paddleHeight( 20.f ), paddleVelocity( 8.f );
constexpr float blockWidth( 60.f ), blockHeight( 20.f );
constexpr int countBlocksX( 11 ), countBlocksY( 6 );
constexpr int countBlocks2X(11), countBlocks2Y(3);
bool isPlaying = true;
struct Ball
{
 CircleShape shape;
 Vector2f velocity{ -ballVelocity, -ballVelocity };
 Ball(float mX, float mY)
 {
 shape.setPosition(mX, mY);
 shape.setRadius(ballRadius);
 shape.setFillColor(Color::Yellow);
 shape.setOrigin(ballRadius, ballRadius);
 }
 void update()
 {
 //Need to make the ball bounce of the window edges
 shape.move(velocity);
 //If it's leaving on the left edge, we set a positive horizontal value.
 if (left() < 0)
 velocity.x = ballVelocity;
 //Same for the right
 else if (right() > windowWidth)
 velocity.x = -ballVelocity;
 //Top
 if (top() < 0)
 velocity.y = ballVelocity;
 //And bottom
 else if (bottom() > windowHeight)
 velocity.y = -ballVelocity;
 }
 float x() { return shape.getPosition().x; }
 float y() { return shape.getPosition().y; }
 float left() { return x() - shape.getRadius(); }
 float right() { return x() + shape.getRadius(); }
 float top() { return y() - shape.getRadius(); }
 float bottom() { return y() + shape.getRadius(); }
};
//Create the Rectangle shape class for the brick
struct Rectangle
{
 RectangleShape shape;
 float x() { return shape.getPosition().x; }
 float y() { return shape.getPosition().y; }
 float left() { return x() - shape.getSize().x / 2.f; }
 float right() { return x() + shape.getSize().x / 2.f; }
 float top() { return y() - shape.getSize().y / 2.f; }
 float bottom() { return y() + shape.getSize().y / 2.f; }
};
//Class for the paddle
struct Paddle : public Rectangle
{
 //Create a variable for speed.
 Vector2f velocity;
 //Set the variables for the paddle rectangle shape.
 Paddle(float mX, float mY)
 {
 shape.setPosition(mX, mY);
 shape.setSize({ paddleWidth, paddleHeight });
 shape.setFillColor(Color::Red);
 shape.setOrigin(paddleWidth / 2.f, paddleHeight / 2.f);
 }
 // Within the update function we check if the player is moving the paddle
 void update()
 {
 shape.move(velocity);
 //To ensure that the paddle stays inside the window we only change the Velocity when it's inside the boundaries
 //Making it impossible to move outside when the initial velocity is set to zero
 if (Keyboard::isKeyPressed(Keyboard::Key::Left) && left() > 0)
 velocity.x = -paddleVelocity;
 else if (Keyboard::isKeyPressed(Keyboard::Key::Right) && right() < windowWidth)
 velocity.x = paddleVelocity;
 //If the player isn't pressing a buttom (legt/right) the velocity is set to zero.
 else
 velocity.x = 0;
 }
};
//Another class for the bricks
struct Brick : public Rectangle
{
 bool destroyed{ false };
 Brick(float mX, float mY)
 {
 shape.setPosition(mX, mY);
 shape.setSize({ blockWidth, blockHeight });
 shape.setFillColor(Color::Black);
 shape.setOrigin(blockWidth / 2.f, blockHeight / 2.f);
 }
};
//C++ Feature template allows us to create a generic funtion to check if two shapes are intersecting or colliding.
template <class T1, class T2>
bool isIntersecting(T1& mA, T2& mB)
{
 return mA.right() >= mB.left() && mA.left() <= mB.right() &&
 mA.bottom() >= mB.top() && mA.top() <= mB.bottom();
}
void collisionTest(Paddle& mPaddle, Ball& mBall)
{
 if (!isIntersecting(mPaddle, mBall)) return;
 mBall.velocity.y = -ballVelocity;
 if (mBall.x() < mPaddle.x())
 mBall.velocity.x = -ballVelocity;
 else
 mBall.velocity.x = ballVelocity;
}
void collisionTest(Brick& mBrick, Ball& mBall)
{
 if (!isIntersecting(mBrick, mBall)) return;
 mBrick.destroyed = true;
 float overlapLeft{ mBall.right() - mBrick.left() };
 float overlapRight{ mBrick.right() - mBall.left() };
 float overlapTop{ mBall.bottom() - mBrick.top() };
 float overlapBottom{ mBrick.bottom() - mBall.top() };
 bool ballFromLeft(abs(overlapLeft) < abs(overlapRight));
 bool ballFromTop(abs(overlapTop) < abs(overlapBottom));
 float minOverlapX{ ballFromLeft ? overlapLeft : overlapRight };
 float minOverlapY{ ballFromTop ? overlapTop : overlapBottom };
 if (abs(minOverlapX) < abs(minOverlapY))
 mBall.velocity.x = ballFromLeft ? -ballVelocity : ballVelocity;
 else
 mBall.velocity.y = ballFromTop ? -ballVelocity : ballVelocity;
}
int main()
{
 //We render/create the window
 RenderWindow window(VideoMode(windowWidth, windowHeight ), "Breakout Game" );
 window.setFramerateLimit(60);
 Paddle paddle{ windowWidth / 2, windowHeight - 50 };
 int x = 5;
//Here we use an unconditiional goto statement to allow the user to restart the game. 
restart:
 //We reference the Ball, Paddle and Bricks
 Ball ball{ windowWidth / 2, windowHeight / 2 };
 vector<Brick> bricks;
 //vector<Brick2> bricks2;
 for (int iX{ 0 }; iX < countBlocksX; ++iX)
 for (int iY{ 0 }; iY < countBlocksY; ++iY)
 bricks.emplace_back(
 (iX + 1) * (blockWidth + 3) + 22, (iY + 2) * (blockHeight + 3));
 // Load the text font
 sf::Font font;
 if (!font.loadFromFile("arial.ttf"))
 return EXIT_FAILURE;
 // Initialize the pause message
 sf::Text loseGame;
 loseGame.setFont(font);
 loseGame.setCharacterSize(40);
 loseGame.setPosition(80.f, 150.f);
 loseGame.setColor(sf::Color::White);
 loseGame.setString("You lost, press 'Space' to play again.");
 // Load the sounds used in the game
 sf::SoundBuffer ballSoundBuffer;
 if (!ballSoundBuffer.loadFromFile("loseSound.wav"))
 return EXIT_FAILURE;
 sf::Sound loseGameSound(ballSoundBuffer);
 while (true)
 {
 window.clear(Color::Color(49, 79, 79));
 if (Keyboard::isKeyPressed(Keyboard::Key::Space))
 goto restart;
 if (Keyboard::isKeyPressed(Keyboard::Key::Escape)) 
 break;
 ball.update();
 paddle.update();
 collisionTest(paddle, ball);
 for (auto& brick : bricks) collisionTest(brick, ball);
 bricks.erase(remove_if(begin(bricks), end(bricks),
 [](const Brick& mBrick)
 { 
 return mBrick.destroyed;
 }),
 end(bricks));
 if (isPlaying)
 {
 window.draw(ball.shape);
 window.draw(paddle.shape);
 for (auto& brick : bricks) window.draw(brick.shape);
 }
 else
 {
 window.clear(Color::Black);
 // Draw the pause message
 loseGameSound.play();
 window.draw(loseGame);
 if (Keyboard::isKeyPressed(Keyboard::Key::Space))
 goto restart;
 }
 window.display();
 }
 return 0;
}
Happy Time
3902 gold badges5 silver badges16 bronze badges
asked Nov 15, 2015 at 11:00
\$\endgroup\$
0

1 Answer 1

4
\$\begingroup\$

I don't have SFML installed right now, so I didn't test you code, but here are some suggestions on improvements you can look into:

Avoid using namespace at the global level

This is a common issue with single-file programs. When projects are small, this is not very harmful, but can break builds in larger code bases due to name collisions. A namespace is a way of allowing identical names to coexist peacefully, if you use namespace at the global scope, that advantage is lost. Namespace names are usually sort precisely to make it easy qualifying the references with the namespace prefix. sf:: and std:: are not an annoyance to type.

One declaration per line

Longer lines with multiple declarations are less clear. In a long line like the following, it takes a few extra milliseconds to realize how many constants are being declared:

constexpr float paddleWidth( 100.f ), paddleHeight( 20.f ), paddleVelocity( 8.f );

Put each declaration in its own line. paddleVelocity is not even related to the sizes, so one less reason for that not be coupled in the same line.

Also, you're generally consistent by initializing all variables with the { } syntax, but those constants at the top are using parentheses.

Don't use mutable global data

It might be tempting to declare a few global variables when the code is small, but at scale, they are a sure recipe for bugs. Use function parameters to pass data around as much as you can. It will make dependencies explicit and easy to track down. isPlaying is actually only used inside main, so it doesn't even make sense for it to be a global.

The global integer x also seems unused. But worse, main apparently redeclares it, so that's probably an artifact from a previous version of the code. Always compile your code with the highest warning levels available. They will remind you of this kind of mistakes, like unused variables and names shadowed by legal but conflicting declarations.

Implement proper inheritance

The class Rectangle serves as base for two other classes. You could actually implement both Paddle and Brick by using composition instead, e.g. just by making them have a Rectangle member instead of being rectangles, this tends to make things simpler in the long run.

Nevertheless, when you do use inheritance, you have to decide how the base class destructor is going to be declared. There are two options:

  1. If child classes can be deleted from a base class pointer (e.g. delete a Rectangle instance that is in fact a Brick), then the base class must have a public virtual destructor.

  2. If you'd like to disallow deleting an instance from a base class pointer, or you don't need this feature, them you can make the base destructor protected and non-virtual.

Side note: You're actually using struct for your object types. As far as the language is concerned, there's no distinction between structs and classes, besides the default access level. However, the usual convention is to use class for polymorphic types or types with some behavior/logic and leave struct for Plain-Old-Data (POD) types.

Use const methods

Your helpers and accessors in Rectangle and Ball are not modifying member variables, so they should be const methods, to both allow them being called on a const instance of the classes and also to clearly convey that info to the readers of your code. More about it here.

Related are functions that take read-only parameters by reference, like isIntersecting. This method should not change the objects passed in, so it should take them by const reference:

bool isIntersecting(const T1& mA, const T2& mB) {
 ^^^^^ ^^^^^

Please avoid goto

The fact that you are resorting to goto is a clear sign that main is overly complicated and needs immediate refactoring. Rethink it by breaking the distinct logic sections into separate functions and get rid of those gotos by using structured loops.

Default { } can help readability

The lack of { } in these nested loops added to that line broken at the start of the function gives the wrong idea, from a quick glance, about the structure of this piece of code. My suggestion is to add default curly braces when you have more complex statements like nested loops, even if the inner statement is a one-liner

 for (int iX{ 0 }; iX < countBlocksX; ++iX)
 for (int iY{ 0 }; iY < countBlocksY; ++iY)
 bricks.emplace_back(
 (iX + 1) * (blockWidth + 3) + 22, (iY + 2) * (blockHeight + 3));

Suggested:

for (int iX{ 0 }; iX < countBlocksX; ++iX)
{
 for (int iY{ 0 }; iY < countBlocksY; ++iY)
 {
 bricks.emplace_back(
 (iX + 1) * (blockWidth + 3) + 22, 
 (iY + 2) * (blockHeight + 3));
 }
}
answered Nov 15, 2015 at 18:39
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.