diff --git a/include/Game.h b/include/Game.h index a56de26..2835a15 100644 --- a/include/Game.h +++ b/include/Game.h @@ -3,40 +3,38 @@ #include #include +#include #include #include #include -#include struct Ball { - Ball(sf::Vector2f, sf::Vector2f); + Ball(sf::Vector2f position_in, sf::Vector2f vel_in); sf::Vector2f pos; sf::Vector2f previousPos{}; sf::Vector2f velocity; - Color color{sf::Color::Red}; bool alive{true}; }; struct Brick { - Brick(sf::Vector2f); + Brick(sf::Vector2f position_in); sf::Vector2f pos; - Color color{sf::Color::White}; bool alive{true}; }; struct Player { - Player(); + Player() = default; + Player(sf::Vector2f position_in); - sf::Vector2f pos; + sf::Vector2f pos{}; sf::Vector2f previousPos{}; - Color color{sf::Color::White}; float score{}; int lives{3}; bool left{false}; @@ -65,16 +63,39 @@ private: void checkEndGame(); + void checkBallCollision(); + private: sf::Clock clock; sf::RenderWindow window; Player player; + std::vector balls; std::vector bricks; std::vector specialBricks; - std::vector balls; std::vector ballsToAdd; sf::CircleShape tempCircle; sf::RectangleShape tempRect; + + // mostly used for imgui + sf::Vector2f brickSize{50, 10}; + sf::Vector2f playerSize{500, 10}; + sf::Vector2f playerStartPos{700,700}; + sf::Vector2f brickHalfSize{brickSize / 2.f}; + sf::Vector2f playerHalfSize{playerSize / 2.f}; + int rowSize{8}; + int columnSize{5}; + int totalSpecialBricks{3}; + int totalBricks{rowSize * columnSize}; + unsigned int frameRate{60}; + float playerSpeed{10}; + float ballRadius{5.0f}; + float ballMaxSpeed{10}; + Color ballColor{sf::Color::Red}; + Color brickColor{sf::Color::White}; + Color playerColor{sf::Color::White}; + Color specialBrickColor{sf::Color::Blue}; + bool windowCollasped{false}; + static constexpr unsigned int numPhysicsUpdates{4}; }; \ No newline at end of file diff --git a/include/Util.h b/include/Util.h index 7aa1f92..2d6a381 100644 --- a/include/Util.h +++ b/include/Util.h @@ -5,7 +5,7 @@ struct Color { Color() = default; - Color(float r_in, float g_in, float b_in, float a_in = 255) + Color(float r_in, float g_in, float b_in, float a_in = 1.0f) : r(r_in) , g(g_in) , b(b_in) @@ -13,12 +13,11 @@ struct Color { } Color(sf::Color color_in) - { - r = (float)color_in.r / 255.f; - g = (float)color_in.g / 255.f; - b = (float)color_in.b / 255.f; - a = (float)color_in.a / 255.f; - } + : r((float)color_in.r / 255.f) + , g((float)color_in.g / 255.f) + , b((float)color_in.b / 255.f) + , a((float)color_in.a / 255.f) + { } float r{}; float g{}; @@ -36,19 +35,6 @@ struct Color } }; -namespace global -{ - inline sf::Vector2f playerSize{500, 10}; - inline sf::Vector2f playerHalfSize{playerSize / 2.f}; - inline sf::Vector2f brickSize{50, 10}; - inline sf::Vector2f brickHalfSize{brickSize / 2.f}; - inline int totalSpecialBricks{3}; - inline int rowSize{8}; - inline int columnSize{5}; - inline int totalBricks{rowSize * columnSize}; - inline float ballRadius{5.0f}; - inline float ballMaxSpeed{10}; - inline float playerSpeed{10}; - inline sf::Vector2f playerStartPos{700,700}; - inline bool windowCollasped{false}; -} \ No newline at end of file +sf::Vector2f getOverlap(sf::FloatRect first, sf::FloatRect second); +sf::Vector2f velocityInRandomDir(float speed_in); +sf::Vector2f velocityInDirection(float speed_in, sf::Angle angle_in); \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 7285f78..f7bc6af 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,29 +1,16 @@ #include +#include + +#include +#include + +#include +#include +#include + #include #include -sf::Vector2f getOverlap(sf::FloatRect first, sf::FloatRect second) -{ - sf::Vector2f result; - sf::Vector2f delta; - delta.x = std::abs(second.position.x - first.position.x); - delta.y = std::abs(second.position.y - first.position.y); - - result.x = (first.size.x + second.size.x) - delta.x; - result.y = (first.size.y + second.size.y) - delta.y; - return result; -} - -sf::Vector2f velocityInRandomDir(float speed_in) -{ - bool upOrDown{(bool)Random::get(0,1)}; - - sf::Angle angle = sf::degrees((float)upOrDown ? Random::get(225, 315) : Random::get(45, 135)); - - return {speed_in * std::cos(angle.asRadians()), speed_in * std::sin(angle.asRadians())}; -} - - Ball::Ball(sf::Vector2f position_in, sf::Vector2f vel_in) : velocity(vel_in) , pos(position_in) @@ -33,29 +20,29 @@ Brick::Brick(sf::Vector2f position_in) : pos(position_in) { } -Player::Player() - : pos(global::playerStartPos) +Player::Player(sf::Vector2f position_in) + : pos(position_in) { } Game::Game() - : player() - , window({sf::VideoMode({ 1920u, 1080u }), "breakout"}) + : window({sf::VideoMode({ 1920u, 1080u }), "breakout"}) { - window.setFramerateLimit(60); + window.setFramerateLimit(frameRate); - if (!ImGui::SFML::Init(window)) - return; + if (!ImGui::SFML::Init(window)) return; - tempCircle.setOrigin({global::ballRadius, global::ballRadius}); + player.pos = playerStartPos; - bricks.reserve(global::totalBricks); + tempCircle.setOrigin({ballRadius, ballRadius}); + + bricks.reserve(totalBricks); bricks.emplace_back(sf::Vector2f{200, 100}); - specialBricks.reserve(global::totalSpecialBricks); + specialBricks.reserve(totalSpecialBricks); balls.reserve(10); ballsToAdd.reserve(10); - ballsToAdd.emplace_back(global::playerStartPos.x, global::playerStartPos.y - 300); + ballsToAdd.emplace_back(playerStartPos.x, playerStartPos.y - 300); } void Game::input() @@ -87,6 +74,10 @@ void Game::input() player.right = true; break; + case sf::Keyboard::Scan::F1: + windowCollasped = !windowCollasped; + break; + default: break; } @@ -116,7 +107,7 @@ void Game::input() switch (MouseButtonPressed->button) { case sf::Mouse::Button::Left: - bricks.emplace_back((sf::Vector2f)MouseButtonPressed->position); + specialBricks.emplace_back((sf::Vector2f)MouseButtonPressed->position); break; default: break; @@ -128,90 +119,103 @@ void Game::input() void Game::collision() { - // declaring at top so it can be used in and out of the ball collision loop - const sf:: FloatRect playerBounds = {player.pos, global::playerHalfSize}; - const sf:: FloatRect previousPlayerBounds = {player.previousPos, global::playerHalfSize}; + + checkBallCollision(); + + const sf:: FloatRect playerBounds = {player.pos, playerHalfSize}; + //player wall collide + if (playerBounds.position.x + playerBounds.size.x > window.getSize().x) + { + player.pos.x = window.getSize().x - playerHalfSize.x; + } + + if (playerBounds.position.x < playerHalfSize.x) + { + player.pos.x = playerHalfSize.x; + } +} - //ball collisions +void Game::checkBallCollision() +{ for (auto& ball : balls) { - const sf::FloatRect ballBounds = {ball.pos, {global::ballRadius, global::ballRadius}}; - const sf::FloatRect previousBallBounds = {ball.previousPos, {global::ballRadius, global::ballRadius}}; + const sf::FloatRect ballBounds = {ball.pos, {ballRadius, ballRadius}}; + const sf::FloatRect previousBallBounds = {ball.previousPos, {ballRadius, ballRadius}}; for (auto& brick : bricks) { - const sf::FloatRect brickBounds = {brick.pos, global::brickHalfSize}; + const sf::FloatRect brickBounds = {brick.pos, brickHalfSize}; auto intersect = getOverlap(ballBounds, brickBounds); auto previousIntersect = getOverlap(previousBallBounds, brickBounds); - if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0) - { - brick.alive = false; - ball.velocity.x *= -1; - if (ball.pos.x < brick.pos.x) + if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0) { - ball.pos.x -= intersect.x; + brick.alive = false; + ball.velocity.x *= -1; + if (ball.pos.x < brick.pos.x) + { + ball.pos.x -= intersect.x; + } + else + { + ball.pos.x += intersect.x; + } } - else - { - ball.pos.x += intersect.x; - } - } - // coming from y direction - if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0) - { - brick.alive = false; - ball.velocity.y *= -1; - if (ball.pos.y < brick.pos.y) + // coming from y direction + if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0) { - ball.pos.y -= intersect.y; + brick.alive = false; + ball.velocity.y *= -1; + if (ball.pos.y < brick.pos.y) + { + ball.pos.y -= intersect.y; + } + else + { + ball.pos.y += intersect.y; + } } - else - { - ball.pos.y += intersect.y; - } - } } for (auto& brick : specialBricks) { - const sf::FloatRect brickBounds = {brick.pos, global::brickHalfSize}; + const sf::FloatRect brickBounds = {brick.pos, brickHalfSize}; const auto intersect = getOverlap(ballBounds, brickBounds); const auto previousIntersect = getOverlap(previousBallBounds, brickBounds); bool spawnExtraBall{false}; - // coming from x direction - if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0) - { - brick.alive = false; - spawnExtraBall = true; - ball.velocity.x *= -1; - if (ball.pos.x < brick.pos.x) + // coming from x direction + if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0) { - ball.pos.x -= intersect.x; + brick.alive = false; + spawnExtraBall = true; + ball.velocity.x *= -1; + if (ball.pos.x < brick.pos.x) + { + ball.pos.x -= intersect.x; + } + else + { + ball.pos.x += intersect.x; + } } - else - { - ball.pos.x += intersect.x; - } - } - // coming from y direction - if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0) - { - brick.alive = false; - spawnExtraBall = true; - ball.velocity.y *= -1; - if (ball.pos.y < brick.pos.y) + // coming from y direction + if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0) { - ball.pos.y -= intersect.y; + brick.alive = false; + spawnExtraBall = true; + ball.velocity.y *= -1; + if (ball.pos.y < brick.pos.y) + { + ball.pos.y -= intersect.y; + } + else + { + ball.pos.y += intersect.y; + } } - else - { - ball.pos.y += intersect.y; - } - } if (spawnExtraBall) { @@ -226,25 +230,28 @@ void Game::collision() ball.alive = false; } - if (ball.pos.x - global::ballRadius <= 0) + if (ball.pos.x - ballRadius <= 0) { - ball.pos.x = global::ballRadius; + ball.pos.x = ballRadius; ball.velocity.x *= -1; } - if (ball.pos.x + global::ballRadius >= window.getSize().x) + if (ball.pos.x + ballRadius >= window.getSize().x) { - ball.pos.x = window.getSize().x - global::ballRadius; + ball.pos.x = window.getSize().x - ballRadius; ball.velocity.x *= -1; } - if (ball.pos.y - global::ballRadius < 0) + if (ball.pos.y - ballRadius < 0) { - ball.pos.y = global::ballRadius; + ball.pos.y = ballRadius; ball.velocity.y *= -1; } // player collide + const sf:: FloatRect playerBounds = {player.pos, playerHalfSize}; + const sf:: FloatRect previousPlayerBounds = {player.previousPos, playerHalfSize}; + auto intersect = getOverlap(ballBounds, playerBounds); auto previousIntersect = getOverlap(previousBallBounds, previousPlayerBounds); @@ -277,29 +284,19 @@ void Game::collision() } } - - //player wall collide - if (playerBounds.position.x + playerBounds.size.x > window.getSize().x) - { - player.pos.x = window.getSize().x - global::playerHalfSize.x; - } - - if (playerBounds.position.x < global::playerHalfSize.x) - { - player.pos.x = global::playerHalfSize.x; - } } void Game::updateEntities() { balls.erase(std::remove_if(balls.begin(), balls.end(), [](const Ball& ball){ return !ball.alive; }), balls.end()); bricks.erase(std::remove_if(bricks.begin(), bricks.end(), [](const Brick& brick){ return !brick.alive; }), bricks.end()); + specialBricks.erase(std::remove_if(specialBricks.begin(), specialBricks.end(), [](const Brick& brick){ return !brick.alive; }), specialBricks.end()); const auto limit = ballsToAdd.size(); for (int i = 0; i < limit; i++) { - balls.emplace_back(ballsToAdd[i], velocityInRandomDir(global::ballMaxSpeed)); + balls.emplace_back(ballsToAdd[i], velocityInRandomDir(ballMaxSpeed / numPhysicsUpdates)); ballsToAdd[i] = {0,0}; } @@ -332,7 +329,7 @@ void Game::movement() int dir = player.right - player.left; player.previousPos = player.pos; - player.pos.x += global::playerSpeed * dir; + player.pos.x += (playerSpeed / numPhysicsUpdates) * dir; } void Game::imgui() @@ -340,7 +337,7 @@ void Game::imgui() ImGui::SFML::Update(window, clock.restart()); ImGui::Begin("menu"); - ImGui::SetWindowCollapsed(global::windowCollasped); + ImGui::SetWindowCollapsed(windowCollasped); ImGui::Text("Controls:"); ImGui::Indent(); ImGui::Text("A/D or arrow keys: move left and right"); @@ -349,40 +346,52 @@ void Game::imgui() ImGui::Text("left click: spawn brick"); ImGui::Unindent(); + ImGui::SliderFloat("Player Speed", &playerSpeed, 1.f, 50.f); + ImGui::SliderFloat("Ball Speed", &ballMaxSpeed, 1.f, 50.f); + if (ImGui::SliderFloat("Player Length", &playerSize.x, 1.f, 600.f)) + { + playerHalfSize.x = playerSize.x / 2; + } + + ImGui::ColorEdit3("Player Color", playerColor.imgui()); + ImGui::ColorEdit3("Ball Color", ballColor.imgui()); + ImGui::ColorEdit3("Brick Color", brickColor.imgui()); + ImGui::ColorEdit3("Special Brick Color", specialBrickColor.imgui()); + ImGui::End(); } void Game::render() { window.clear(); - - tempRect.setOrigin(global::brickHalfSize); - tempRect.setSize(global::brickSize); + tempRect.setOrigin(brickHalfSize); + tempRect.setSize(brickSize); for (auto& brick : bricks) { tempRect.setPosition(brick.pos); - tempRect.setFillColor(brick.color.sfml()); + tempRect.setFillColor(brickColor.sfml()); window.draw(tempRect); } for (auto& brick : specialBricks) { tempRect.setPosition(brick.pos); - tempRect.setFillColor(brick.color.sfml()); + tempRect.setFillColor(specialBrickColor.sfml()); window.draw(tempRect); } - tempRect.setOrigin(global::playerHalfSize); - tempRect.setSize(global::playerSize); + tempRect.setOrigin(playerHalfSize); + tempRect.setSize(playerSize); tempRect.setPosition(player.pos); - tempRect.setFillColor(player.color.sfml()); + tempRect.setFillColor(playerColor.sfml()); window.draw(tempRect); - tempCircle.setRadius(global::ballRadius); + + tempCircle.setRadius(ballRadius); for (auto& ball : balls) { tempCircle.setPosition(ball.pos); - tempCircle.setFillColor(ball.color.sfml()); + tempCircle.setFillColor(ballColor.sfml()); window.draw(tempCircle); } @@ -396,13 +405,16 @@ void Game::run() while(window.isOpen()) { input(); - - updateEntities(); - movement(); - - collision(); - + for (int i = 0; i <= numPhysicsUpdates; i++) + { + updateEntities(); + + movement(); + + collision(); + } + checkEndGame(); imgui(); diff --git a/src/Util.cpp b/src/Util.cpp new file mode 100644 index 0000000..4b41147 --- /dev/null +++ b/src/Util.cpp @@ -0,0 +1,35 @@ +#include +#include + +sf::Vector2f getOverlap(sf::FloatRect first, sf::FloatRect second) +{ + sf::Vector2f result; + sf::Vector2f delta; + delta.x = std::abs(second.position.x - first.position.x); + delta.y = std::abs(second.position.y - first.position.y); + + result.x = (first.size.x + second.size.x) - delta.x; + result.y = (first.size.y + second.size.y) - delta.y; + return result; +} + +sf::Vector2f velocityInRandomDir(float speed_in) +{ + const bool upOrDown{(bool)Random::get(0,1)}; + + // get angle within certain ranges so that the ball does not + // start moving straight side to side or up and down + sf::Angle angle = sf::degrees(upOrDown ? Random::get(225, 315) : Random::get(45, 135)); + if (angle.asDegrees() == 90.f || angle.asDegrees() == 270) + { + angle += sf::degrees(1); + } + + return sf::Vector2f{speed_in * std::cos(angle.asRadians()), speed_in * std::sin(angle.asRadians())}; +} + +// used for debugging +sf::Vector2f velocityInDirection(float speed_in, sf::Angle angle_in) +{ + return sf::Vector2f{speed_in * std::cos(angle_in.asRadians()), speed_in * std::sin(angle_in.asRadians())}; +}