refactoring

-moved globals to game class
-moved helper functions to util class
-added more imgui functionality
This commit is contained in:
Joseph Aquino 2025-09-03 03:51:50 -04:00
parent 6559a9dc95
commit 60a923d4c9
4 changed files with 213 additions and 159 deletions

View File

@ -3,40 +3,38 @@
#include <vector> #include <vector>
#include <Util.h> #include <Util.h>
#include <Random.h>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <imgui-SFML.h> #include <imgui-SFML.h>
#include <imgui.h> #include <imgui.h>
#include <Random.h>
struct Ball struct Ball
{ {
Ball(sf::Vector2f, sf::Vector2f); Ball(sf::Vector2f position_in, sf::Vector2f vel_in);
sf::Vector2f pos; sf::Vector2f pos;
sf::Vector2f previousPos{}; sf::Vector2f previousPos{};
sf::Vector2f velocity; sf::Vector2f velocity;
Color color{sf::Color::Red};
bool alive{true}; bool alive{true};
}; };
struct Brick struct Brick
{ {
Brick(sf::Vector2f); Brick(sf::Vector2f position_in);
sf::Vector2f pos; sf::Vector2f pos;
Color color{sf::Color::White};
bool alive{true}; bool alive{true};
}; };
struct Player struct Player
{ {
Player(); Player() = default;
Player(sf::Vector2f position_in);
sf::Vector2f pos; sf::Vector2f pos{};
sf::Vector2f previousPos{}; sf::Vector2f previousPos{};
Color color{sf::Color::White};
float score{}; float score{};
int lives{3}; int lives{3};
bool left{false}; bool left{false};
@ -65,16 +63,39 @@ private:
void checkEndGame(); void checkEndGame();
void checkBallCollision();
private: private:
sf::Clock clock; sf::Clock clock;
sf::RenderWindow window; sf::RenderWindow window;
Player player; Player player;
std::vector<Ball> balls;
std::vector<Brick> bricks; std::vector<Brick> bricks;
std::vector<Brick> specialBricks; std::vector<Brick> specialBricks;
std::vector<Ball> balls;
std::vector<sf::Vector2f> ballsToAdd; std::vector<sf::Vector2f> ballsToAdd;
sf::CircleShape tempCircle; sf::CircleShape tempCircle;
sf::RectangleShape tempRect; 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};
}; };

View File

@ -5,7 +5,7 @@
struct Color struct Color
{ {
Color() = default; 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) : r(r_in)
, g(g_in) , g(g_in)
, b(b_in) , b(b_in)
@ -13,12 +13,11 @@ struct Color
{ } { }
Color(sf::Color color_in) Color(sf::Color color_in)
{ : r((float)color_in.r / 255.f)
r = (float)color_in.r / 255.f; , g((float)color_in.g / 255.f)
g = (float)color_in.g / 255.f; , b((float)color_in.b / 255.f)
b = (float)color_in.b / 255.f; , a((float)color_in.a / 255.f)
a = (float)color_in.a / 255.f; { }
}
float r{}; float r{};
float g{}; float g{};
@ -36,19 +35,6 @@ struct Color
} }
}; };
namespace global sf::Vector2f getOverlap(sf::FloatRect first, sf::FloatRect second);
{ sf::Vector2f velocityInRandomDir(float speed_in);
inline sf::Vector2f playerSize{500, 10}; sf::Vector2f velocityInDirection(float speed_in, sf::Angle angle_in);
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};
}

View File

@ -1,29 +1,16 @@
#include <Game.h> #include <Game.h>
#include <vector>
#include <Util.h>
#include <Random.h>
#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include <imgui.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
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) Ball::Ball(sf::Vector2f position_in, sf::Vector2f vel_in)
: velocity(vel_in) : velocity(vel_in)
, pos(position_in) , pos(position_in)
@ -33,29 +20,29 @@ Brick::Brick(sf::Vector2f position_in)
: pos(position_in) : pos(position_in)
{ } { }
Player::Player() Player::Player(sf::Vector2f position_in)
: pos(global::playerStartPos) : pos(position_in)
{ } { }
Game::Game() 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)) if (!ImGui::SFML::Init(window)) return;
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}); bricks.emplace_back(sf::Vector2f{200, 100});
specialBricks.reserve(global::totalSpecialBricks); specialBricks.reserve(totalSpecialBricks);
balls.reserve(10); balls.reserve(10);
ballsToAdd.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() void Game::input()
@ -87,6 +74,10 @@ void Game::input()
player.right = true; player.right = true;
break; break;
case sf::Keyboard::Scan::F1:
windowCollasped = !windowCollasped;
break;
default: default:
break; break;
} }
@ -116,7 +107,7 @@ void Game::input()
switch (MouseButtonPressed->button) switch (MouseButtonPressed->button)
{ {
case sf::Mouse::Button::Left: case sf::Mouse::Button::Left:
bricks.emplace_back((sf::Vector2f)MouseButtonPressed->position); specialBricks.emplace_back((sf::Vector2f)MouseButtonPressed->position);
break; break;
default: default:
break; break;
@ -128,19 +119,32 @@ void Game::input()
void Game::collision() 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};
//ball collisions 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;
}
}
void Game::checkBallCollision()
{
for (auto& ball : balls) for (auto& ball : balls)
{ {
const sf::FloatRect ballBounds = {ball.pos, {global::ballRadius, global::ballRadius}}; const sf::FloatRect ballBounds = {ball.pos, {ballRadius, ballRadius}};
const sf::FloatRect previousBallBounds = {ball.previousPos, {global::ballRadius, global::ballRadius}}; const sf::FloatRect previousBallBounds = {ball.previousPos, {ballRadius, ballRadius}};
for (auto& brick : bricks) 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 intersect = getOverlap(ballBounds, brickBounds);
auto previousIntersect = getOverlap(previousBallBounds, brickBounds); auto previousIntersect = getOverlap(previousBallBounds, brickBounds);
@ -176,7 +180,7 @@ void Game::collision()
for (auto& brick : specialBricks) 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 intersect = getOverlap(ballBounds, brickBounds);
const auto previousIntersect = getOverlap(previousBallBounds, brickBounds); const auto previousIntersect = getOverlap(previousBallBounds, brickBounds);
bool spawnExtraBall{false}; bool spawnExtraBall{false};
@ -226,25 +230,28 @@ void Game::collision()
ball.alive = false; 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; 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; 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; ball.velocity.y *= -1;
} }
// player collide // player collide
const sf:: FloatRect playerBounds = {player.pos, playerHalfSize};
const sf:: FloatRect previousPlayerBounds = {player.previousPos, playerHalfSize};
auto intersect = getOverlap(ballBounds, playerBounds); auto intersect = getOverlap(ballBounds, playerBounds);
auto previousIntersect = getOverlap(previousBallBounds, previousPlayerBounds); 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() void Game::updateEntities()
{ {
balls.erase(std::remove_if(balls.begin(), balls.end(), [](const Ball& ball){ return !ball.alive; }), balls.end()); 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()); 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(); const auto limit = ballsToAdd.size();
for (int i = 0; i < limit; i++) 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}; ballsToAdd[i] = {0,0};
} }
@ -332,7 +329,7 @@ void Game::movement()
int dir = player.right - player.left; int dir = player.right - player.left;
player.previousPos = player.pos; player.previousPos = player.pos;
player.pos.x += global::playerSpeed * dir; player.pos.x += (playerSpeed / numPhysicsUpdates) * dir;
} }
void Game::imgui() void Game::imgui()
@ -340,7 +337,7 @@ void Game::imgui()
ImGui::SFML::Update(window, clock.restart()); ImGui::SFML::Update(window, clock.restart());
ImGui::Begin("menu"); ImGui::Begin("menu");
ImGui::SetWindowCollapsed(global::windowCollasped); ImGui::SetWindowCollapsed(windowCollasped);
ImGui::Text("Controls:"); ImGui::Text("Controls:");
ImGui::Indent(); ImGui::Indent();
ImGui::Text("A/D or arrow keys: move left and right"); ImGui::Text("A/D or arrow keys: move left and right");
@ -349,6 +346,18 @@ void Game::imgui()
ImGui::Text("left click: spawn brick"); ImGui::Text("left click: spawn brick");
ImGui::Unindent(); 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(); ImGui::End();
} }
@ -356,33 +365,33 @@ void Game::render()
{ {
window.clear(); window.clear();
tempRect.setOrigin(brickHalfSize);
tempRect.setOrigin(global::brickHalfSize); tempRect.setSize(brickSize);
tempRect.setSize(global::brickSize);
for (auto& brick : bricks) for (auto& brick : bricks)
{ {
tempRect.setPosition(brick.pos); tempRect.setPosition(brick.pos);
tempRect.setFillColor(brick.color.sfml()); tempRect.setFillColor(brickColor.sfml());
window.draw(tempRect); window.draw(tempRect);
} }
for (auto& brick : specialBricks) for (auto& brick : specialBricks)
{ {
tempRect.setPosition(brick.pos); tempRect.setPosition(brick.pos);
tempRect.setFillColor(brick.color.sfml()); tempRect.setFillColor(specialBrickColor.sfml());
window.draw(tempRect); window.draw(tempRect);
} }
tempRect.setOrigin(global::playerHalfSize); tempRect.setOrigin(playerHalfSize);
tempRect.setSize(global::playerSize); tempRect.setSize(playerSize);
tempRect.setPosition(player.pos); tempRect.setPosition(player.pos);
tempRect.setFillColor(player.color.sfml()); tempRect.setFillColor(playerColor.sfml());
window.draw(tempRect); window.draw(tempRect);
tempCircle.setRadius(global::ballRadius);
tempCircle.setRadius(ballRadius);
for (auto& ball : balls) for (auto& ball : balls)
{ {
tempCircle.setPosition(ball.pos); tempCircle.setPosition(ball.pos);
tempCircle.setFillColor(ball.color.sfml()); tempCircle.setFillColor(ballColor.sfml());
window.draw(tempCircle); window.draw(tempCircle);
} }
@ -397,11 +406,14 @@ void Game::run()
{ {
input(); input();
for (int i = 0; i <= numPhysicsUpdates; i++)
{
updateEntities(); updateEntities();
movement(); movement();
collision(); collision();
}
checkEndGame(); checkEndGame();

35
src/Util.cpp Normal file
View File

@ -0,0 +1,35 @@
#include <Util.h>
#include <Random.h>
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())};
}