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 <Util.h>
#include <Random.h>
#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include <imgui.h>
#include <Random.h>
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<Ball> balls;
std::vector<Brick> bricks;
std::vector<Brick> specialBricks;
std::vector<Ball> balls;
std::vector<sf::Vector2f> 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};
};

View File

@ -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};
}
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);

View File

@ -1,29 +1,16 @@
#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 <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)
: 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();

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())};
}