702 lines
15 KiB
C++
702 lines
15 KiB
C++
#include <Game.h>
|
|
#include <vector>
|
|
|
|
#include <Util.h>
|
|
|
|
#include <SFML/Graphics.hpp>
|
|
#include <imgui-SFML.h>
|
|
#include <imgui.h>
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
Ball::Ball(sf::Vector2f position_in, sf::Vector2f vel_in)
|
|
: pos(position_in)
|
|
, velocity(vel_in)
|
|
{ }
|
|
|
|
Brick::Brick(sf::Vector2f position_in)
|
|
: pos(position_in)
|
|
{ }
|
|
|
|
Player::Player(sf::Vector2f position_in)
|
|
: pos(position_in)
|
|
{ }
|
|
|
|
|
|
Game::Game()
|
|
: window({sf::VideoMode({ 1920u, 1080u }), "project-breakout"})
|
|
, font("assets/fonts/ChakraPetch-Regular.ttf")
|
|
, lives(font)
|
|
, score(font)
|
|
, currentMult(font)
|
|
, collideSoundBuffer("assets/sounds/hit2.wav")
|
|
, brickBreakSoundBuffer("assets/sounds/hit1.wav")
|
|
, failSoundBuffer("assets/sounds/fail.wav")
|
|
, collideSound(collideSoundBuffer)
|
|
, brickBreakSound(brickBreakSoundBuffer)
|
|
, failSound(failSoundBuffer)
|
|
{
|
|
|
|
if (!ImGui::SFML::Init(window)) return;
|
|
|
|
if (!parseConfigFile()) return;
|
|
|
|
lives.setPosition({10, static_cast<float>(window.getSize().y - 40)});
|
|
score.setPosition({10, static_cast<float>(window.getSize().y - 80)});
|
|
currentMult.setPosition({10, static_cast<float>(window.getSize().y - 120)});
|
|
|
|
window.setFramerateLimit(framerate);
|
|
|
|
collideSound.setVolume(volume);
|
|
brickBreakSound.setVolume(volume);
|
|
failSound.setVolume(volume);
|
|
|
|
player.pos = playerStartPos;
|
|
playerHalfSize = playerSize / 2.f;
|
|
brickHalfSize = brickSize / 2.f;
|
|
|
|
tempCircle.setOrigin({ballRadius, ballRadius});
|
|
ballMaxSpeed /= numPhysicsUpdates;
|
|
|
|
bricks.reserve(totalBricks);
|
|
specialBricks.reserve(totalSpecialBricks);
|
|
balls.reserve(10);
|
|
ballsToAdd.reserve(10);
|
|
|
|
setupLevel();
|
|
|
|
ballsToAdd.emplace_back(playerStartPos.x, playerStartPos.y - 50);
|
|
}
|
|
|
|
void Game::setupLevel()
|
|
{
|
|
const int specialBrickInterval = totalBricks / totalSpecialBricks;
|
|
|
|
constexpr int xSpacing = 20;
|
|
constexpr int ySpacing = 20;
|
|
const int xOffset = (int)brickSize.x + xSpacing;
|
|
const int yOffset = (int)brickSize.y + ySpacing;
|
|
const int numRows = window.getSize().x / xOffset;
|
|
const int numColumns = (window.getSize().y - 300)/ yOffset;
|
|
int row{};
|
|
int column{};
|
|
|
|
for (int i = 0; i < totalBricks; i++)
|
|
{
|
|
if (row >= numRows)
|
|
{
|
|
row = 0;
|
|
column++;
|
|
}
|
|
|
|
if (column >= numColumns)
|
|
break;
|
|
|
|
if (i % specialBrickInterval == 0)
|
|
{
|
|
specialBricks.emplace_back(sf::Vector2f{(row * xOffset) + xSpacing + brickHalfSize.x, (column * yOffset) + ySpacing + brickHalfSize.y});
|
|
}
|
|
else
|
|
{
|
|
bricks.emplace_back(sf::Vector2f{(row * xOffset) + xSpacing + brickHalfSize.x, (column * yOffset) + ySpacing + brickHalfSize.y});
|
|
}
|
|
row++;
|
|
}
|
|
}
|
|
|
|
bool Game::parseConfigFile()
|
|
{
|
|
std::ifstream configFile{"Config/game-config.txt"};
|
|
|
|
if(!configFile)
|
|
{
|
|
std::cerr << "ERROR: CANNOT OPEN 'game-config.txt'\n";
|
|
return false;
|
|
}
|
|
|
|
std::string inputBuff;
|
|
while(configFile >> inputBuff)
|
|
{
|
|
if (inputBuff == "framerate")
|
|
configFile >> framerate;
|
|
|
|
if (inputBuff == "ballRadius")
|
|
configFile >> ballRadius;
|
|
|
|
if (inputBuff == "playerSpeed")
|
|
configFile >> playerSpeed;
|
|
|
|
if (inputBuff == "brickCount")
|
|
configFile >> totalBricks;
|
|
|
|
if (inputBuff == "specialBrickCount")
|
|
configFile >> totalSpecialBricks;
|
|
|
|
if (inputBuff == "brickSize")
|
|
configFile >> brickSize.x >> brickSize.y;
|
|
|
|
if (inputBuff == "playerSize")
|
|
configFile >> playerSize.x >> playerSize.y;
|
|
|
|
if (inputBuff == "ballColor")
|
|
configFile >> ballColor.r >> ballColor.g >> ballColor.b;
|
|
|
|
if (inputBuff == "playerStartPos")
|
|
configFile >> playerStartPos.x >> playerStartPos.y;
|
|
|
|
if (inputBuff == "brickColor")
|
|
configFile >> brickColor.r >> brickColor.g >> brickColor.b;
|
|
|
|
if (inputBuff == "playerColor")
|
|
configFile >> playerColor.r >> playerColor.g >> playerColor.b;
|
|
|
|
if (inputBuff == "specialBrickColor")
|
|
configFile >> specialBrickColor.r >> specialBrickColor.g >> specialBrickColor.b;
|
|
|
|
if (inputBuff == "ballSpeed")
|
|
configFile >> ballMaxSpeed;
|
|
}
|
|
|
|
if (totalSpecialBricks > totalBricks)
|
|
totalSpecialBricks = totalBricks;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Game::input()
|
|
{
|
|
while (const std::optional event = window.pollEvent())
|
|
{
|
|
ImGui::SFML::ProcessEvent(window, *event);
|
|
|
|
if (event->is<sf::Event::Closed>())
|
|
{
|
|
window.close();
|
|
}
|
|
|
|
if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>())
|
|
{
|
|
switch (keyPressed->scancode)
|
|
{
|
|
case sf::Keyboard::Scan::Escape:
|
|
window.close();
|
|
break;
|
|
|
|
case sf::Keyboard::Scan::Left:
|
|
case sf::Keyboard::Scan::A:
|
|
player.left = true;
|
|
break;
|
|
|
|
case sf::Keyboard::Scan::Right:
|
|
case sf::Keyboard::Scan::D:
|
|
player.right = true;
|
|
break;
|
|
|
|
case sf::Keyboard::Scan::R:
|
|
resetGame();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (const auto* keyReleased = event->getIf<sf::Event::KeyReleased>())
|
|
{
|
|
switch (keyReleased->scancode)
|
|
{
|
|
case sf::Keyboard::Scan::Left:
|
|
case sf::Keyboard::Scan::A:
|
|
player.left = false;
|
|
break;
|
|
|
|
case sf::Keyboard::Scan::Right:
|
|
case sf::Keyboard::Scan::D:
|
|
player.right = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if (const auto* MouseButtonPressed = event->getIf<sf::Event::MouseButtonPressed>())
|
|
// {
|
|
// switch (MouseButtonPressed->button)
|
|
// {
|
|
// case sf::Mouse::Button::Left:
|
|
// //specialBricks.emplace_back((sf::Vector2f)MouseButtonPressed->position);
|
|
// break;
|
|
|
|
// default:
|
|
// break;
|
|
// }
|
|
|
|
// }
|
|
}
|
|
}
|
|
|
|
void Game::collision()
|
|
{
|
|
|
|
checkBallCollision();
|
|
|
|
//player wall collide
|
|
const sf:: FloatRect playerBounds = {player.pos, playerHalfSize};
|
|
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)
|
|
{
|
|
ball.collided = false;
|
|
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, brickHalfSize};
|
|
const auto intersect = getOverlap(ballBounds, brickBounds);
|
|
const auto previousIntersect = getOverlap(previousBallBounds, brickBounds);
|
|
|
|
// coming from x directon
|
|
if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0)
|
|
{
|
|
ball.collided = true;
|
|
brick.alive = false;
|
|
ball.velocity.x *= -1;
|
|
if (ball.pos.x < brick.pos.x)
|
|
{// from the left
|
|
ball.pos.x -= intersect.x;
|
|
}
|
|
else
|
|
{// from the right
|
|
ball.pos.x += intersect.x;
|
|
}
|
|
}
|
|
|
|
// coming from y direction
|
|
if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
|
|
{
|
|
ball.collided = true;
|
|
brick.alive = false;
|
|
ball.velocity.y *= -1;
|
|
if (ball.pos.y < brick.pos.y)
|
|
{// from the top
|
|
ball.pos.y -= intersect.y;
|
|
}
|
|
else
|
|
{// from the bottom
|
|
ball.pos.y += intersect.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& brick : specialBricks)
|
|
{
|
|
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)
|
|
{
|
|
ball.collided = true;
|
|
brick.alive = false;
|
|
spawnExtraBall = true;
|
|
ball.velocity.x *= -1;
|
|
if (ball.pos.x < brick.pos.x)
|
|
{// from the left
|
|
ball.pos.x -= intersect.x;
|
|
}
|
|
else
|
|
{// from the right
|
|
ball.pos.x += intersect.x;
|
|
}
|
|
}
|
|
|
|
// coming from y direction
|
|
if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
|
|
{
|
|
ball.collided = true;
|
|
brick.alive = false;
|
|
spawnExtraBall = true;
|
|
ball.velocity.y *= -1;
|
|
if (ball.pos.y < brick.pos.y)
|
|
{// from the top
|
|
ball.pos.y -= intersect.y;
|
|
}
|
|
else
|
|
{// from the bottom
|
|
ball.pos.y += intersect.y;
|
|
}
|
|
}
|
|
|
|
if (spawnExtraBall)
|
|
{
|
|
ballsToAdd.emplace_back(ball.pos);
|
|
}
|
|
}
|
|
|
|
//wall collide
|
|
if (ballBounds.position.y + ballBounds.size.y > window.getSize().y)
|
|
{
|
|
ball.alive = false;
|
|
}
|
|
|
|
if (ball.pos.x - ballRadius <= 0)
|
|
{
|
|
ball.collided = true;
|
|
ball.pos.x = ballRadius;
|
|
ball.velocity.x *= -1;
|
|
}
|
|
|
|
if (ball.pos.x + ballRadius >= window.getSize().x)
|
|
{
|
|
ball.collided = true;
|
|
ball.pos.x = window.getSize().x - ballRadius;
|
|
ball.velocity.x *= -1;
|
|
}
|
|
|
|
if (ball.pos.y - ballRadius < 0)
|
|
{
|
|
ball.collided = true;
|
|
ball.pos.y = ballRadius;
|
|
ball.velocity.y *= -1;
|
|
}
|
|
|
|
// player collide
|
|
const sf:: FloatRect playerBounds = {player.pos, playerHalfSize};
|
|
const sf:: FloatRect previousPlayerBounds = {player.previousPos, playerHalfSize};
|
|
|
|
const auto intersect = getOverlap(ballBounds, playerBounds);
|
|
const auto previousIntersect = getOverlap(previousBallBounds, previousPlayerBounds);
|
|
|
|
// coming from x direction
|
|
if (intersect.x > 0 && previousIntersect.x <= 0 && intersect.y > 0)
|
|
{
|
|
ball.collided = true;
|
|
ball.velocity.x *= -1;
|
|
if (ball.pos.x < player.pos.x)
|
|
{// from the left
|
|
ball.pos.x -= intersect.x;
|
|
}
|
|
else
|
|
{// from the right
|
|
ball.pos.x += intersect.x;
|
|
}
|
|
}
|
|
|
|
// coming from y direction
|
|
if (intersect.y > 0 && previousIntersect.y <= 0 && intersect.x > 0)
|
|
{
|
|
ball.collided = true;
|
|
ball.velocity.y *= -1;
|
|
if (ball.pos.y < player.pos.y)
|
|
{// from the top
|
|
ball.pos.y -= intersect.y;
|
|
}
|
|
else if (ball.pos.y > player.pos.y)
|
|
{// from the bottom
|
|
ball.pos.y += intersect.y;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
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 (size_t i = 0; i < limit; i++)
|
|
{
|
|
balls.emplace_back(ballsToAdd[i], velocityInRandomDir(ballMaxSpeed));
|
|
ballsToAdd[i] = {0,0};
|
|
}
|
|
|
|
ballsToAdd.clear();
|
|
}
|
|
|
|
void Game::checkEndGame()
|
|
{
|
|
if (balls.empty())
|
|
{
|
|
player.lives--;
|
|
ballsToAdd.emplace_back(sf::Vector2f{player.pos.x, player.pos.y - 50});
|
|
}
|
|
|
|
|
|
if (player.lives <= 0)
|
|
{
|
|
resetGame();
|
|
}
|
|
}
|
|
|
|
void Game::movement()
|
|
{
|
|
for (auto& ball : balls)
|
|
{
|
|
ball.previousPos = ball.pos;
|
|
ball.pos += ball.velocity;
|
|
}
|
|
|
|
const int dir = player.right - player.left;
|
|
|
|
player.previousPos = player.pos;
|
|
player.pos.x += (playerSpeed / numPhysicsUpdates) * dir;
|
|
}
|
|
|
|
void Game::imgui()
|
|
{
|
|
ImGui::SetNextWindowCollapsed(false, ImGuiCond_Once);
|
|
ImGui::SetNextWindowSize({490,375}, ImGuiCond_Once);
|
|
ImGui::SetNextWindowPos({26,29}, ImGuiCond_Once);
|
|
if(!ImGui::Begin("menu"))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
ImGui::Text("Controls:");
|
|
ImGui::Indent();
|
|
ImGui::Text("A/D or arrow keys: move left and right");
|
|
ImGui::Text("R: reset game");
|
|
ImGui::Unindent();
|
|
|
|
if (ImGui::SliderFloat("Game Volume", &volume, 0, 100, "%.1f"))
|
|
{
|
|
collideSound.setVolume(volume);
|
|
brickBreakSound.setVolume(volume);
|
|
failSound.setVolume(volume);
|
|
}
|
|
|
|
ImGui::Text("Level config:");
|
|
if(ImGui::InputInt("Total bricks", &totalBricks))
|
|
{
|
|
if (totalBricks < 0)
|
|
totalBricks = 0;
|
|
|
|
if (totalSpecialBricks > totalBricks)
|
|
totalSpecialBricks = totalBricks;
|
|
}
|
|
|
|
if(ImGui::InputInt("Special bricks", &totalSpecialBricks))
|
|
{
|
|
if (totalSpecialBricks < 0)
|
|
totalSpecialBricks = 0;
|
|
|
|
if (totalSpecialBricks > totalBricks)
|
|
totalSpecialBricks = totalBricks;
|
|
}
|
|
|
|
if(ImGui::Button("Reload Level"))
|
|
{
|
|
resetGame();
|
|
}
|
|
|
|
ImGui::Text("Game config:");
|
|
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();
|
|
|
|
lives.setString("Lives: " + std::to_string(player.lives));
|
|
score.setString("Score: " + std::to_string(player.score));
|
|
currentMult.setString("Mult: x" + std::to_string(player.mult));
|
|
|
|
window.draw(lives);
|
|
window.draw(score);
|
|
window.draw(currentMult);
|
|
|
|
tempRect.setOrigin(brickHalfSize);
|
|
tempRect.setSize(brickSize);
|
|
for (auto& brick : bricks)
|
|
{
|
|
tempRect.setPosition(brick.pos);
|
|
tempRect.setFillColor(brickColor.sfml());
|
|
window.draw(tempRect);
|
|
}
|
|
|
|
for (auto& brick : specialBricks)
|
|
{
|
|
tempRect.setPosition(brick.pos);
|
|
tempRect.setFillColor(specialBrickColor.sfml());
|
|
window.draw(tempRect);
|
|
}
|
|
|
|
tempRect.setOrigin(playerHalfSize);
|
|
tempRect.setSize(playerSize);
|
|
tempRect.setPosition(player.pos);
|
|
tempRect.setFillColor(playerColor.sfml());
|
|
window.draw(tempRect);
|
|
|
|
tempCircle.setRadius(ballRadius);
|
|
for (auto& ball : balls)
|
|
{
|
|
tempCircle.setPosition(ball.pos);
|
|
tempCircle.setFillColor(ballColor.sfml());
|
|
window.draw(tempCircle);
|
|
}
|
|
|
|
ImGui::SFML::Render(window);
|
|
|
|
window.display();
|
|
}
|
|
|
|
void Game::soundSystem()
|
|
{
|
|
for (auto& ball : balls)
|
|
{
|
|
if (ball.collided)
|
|
{
|
|
collideSound.play();
|
|
}
|
|
if (!ball.alive)
|
|
{
|
|
failSound.play();
|
|
}
|
|
}
|
|
|
|
for (auto& brick : bricks)
|
|
{
|
|
if (!brick.alive)
|
|
{
|
|
brickBreakSound.play();
|
|
}
|
|
}
|
|
|
|
for (auto& brick : specialBricks)
|
|
{
|
|
if (!brick.alive)
|
|
{
|
|
brickBreakSound.play();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Game::scoreSystem()
|
|
{
|
|
player.mult = Math::max<int>(balls.size(), 1);
|
|
for (auto& brick : bricks)
|
|
{
|
|
if (!brick.alive)
|
|
{
|
|
player.score += 100 * player.mult;
|
|
}
|
|
}
|
|
|
|
for (auto& brick : specialBricks)
|
|
{
|
|
if (!brick.alive)
|
|
{
|
|
player.score += 100 * player.mult;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::resetGame()
|
|
{
|
|
bricks.clear();
|
|
specialBricks.clear();
|
|
balls.clear();
|
|
ballsToAdd.clear();
|
|
|
|
setupLevel();
|
|
|
|
player.lives = 3;
|
|
player.score = 0;
|
|
|
|
ballsToAdd.emplace_back(sf::Vector2f{player.pos.x, player.pos.y - 50});
|
|
}
|
|
|
|
void Game::run()
|
|
{
|
|
while(window.isOpen())
|
|
{
|
|
ImGui::SFML::Update(window, clock.restart());
|
|
|
|
input();
|
|
|
|
for (size_t i = 0; i <= numPhysicsUpdates; i++)
|
|
{
|
|
updateEntities();
|
|
|
|
movement();
|
|
|
|
collision();
|
|
|
|
soundSystem();
|
|
|
|
scoreSystem();
|
|
}
|
|
|
|
checkEndGame();
|
|
|
|
imgui();
|
|
|
|
render();
|
|
|
|
}
|
|
|
|
ImGui::SFML::Shutdown();
|
|
}
|
|
|
|
void Game::runNoImgui()
|
|
{
|
|
while(window.isOpen())
|
|
{
|
|
ImGui::SFML::Update(window, clock.restart());
|
|
|
|
input();
|
|
|
|
for (size_t i = 0; i <= numPhysicsUpdates; i++)
|
|
{
|
|
updateEntities();
|
|
|
|
movement();
|
|
|
|
collision();
|
|
|
|
soundSystem();
|
|
|
|
scoreSystem();
|
|
}
|
|
|
|
checkEndGame();
|
|
|
|
render();
|
|
}
|
|
|
|
ImGui::SFML::Shutdown();
|
|
} |