diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a38747..fd4fe72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.28) -project(CMakeSFMLProject LANGUAGES CXX) +project(SFMLplayground LANGUAGES CXX) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) @@ -12,6 +12,9 @@ FetchContent_Declare(SFML SYSTEM) FetchContent_MakeAvailable(SFML) -add_executable(main src/main.cpp) -target_compile_features(main PRIVATE cxx_std_17) -target_link_libraries(main PRIVATE SFML::Graphics) +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*.cpp) +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS src/*.hpp) + +add_executable(SFMLplayground ${SOURCES} ${HEADERS}) +target_compile_features(SFMLplayground PRIVATE cxx_std_17) +target_link_libraries(SFMLplayground PRIVATE SFML::Graphics) \ No newline at end of file diff --git a/src/Ball.cpp b/src/Ball.cpp new file mode 100644 index 0000000..fee78c2 --- /dev/null +++ b/src/Ball.cpp @@ -0,0 +1,57 @@ +#include "Ball.hpp" + +#include + +#include "Constants.hpp" + +Ball::Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel) + : m_radius(radius), m_velocity(vel) { + m_shape.setRadius(radius); + m_shape.setOrigin(sf::Vector2f(radius, radius)); + m_shape.setPosition(pos); + m_shape.setFillColor(sf::Color::Red); +} + +void Ball::update(float dt) { + m_velocity.y += Constants::GRAVITY * dt; + m_shape.move(m_velocity * dt); + handleWallCollision(); +} + +void Ball::draw(sf::RenderWindow& window) { window.draw(m_shape); } + +void Ball::applyImpulse(const sf::Vector2f& impulse) { m_velocity += impulse; } + +sf::Vector2f Ball::getPosition() const { return m_shape.getPosition(); } + +void Ball::handleWallCollision() { + sf::Vector2f pos = m_shape.getPosition(); + + auto handleAxis = [&](int axis, float min, float max, float& velocity, + float radius, float restitution) { + float value = (axis == 0) ? pos.x : pos.y; + + if (value - radius < min) { + value = min + radius; + velocity = -velocity * restitution; + } else if (value + radius > max) { + value = max - radius; + velocity = -velocity * restitution; + if (axis == 1 && std::abs(velocity) < 10.f) velocity = 0.f; + } + + (axis == 0 ? pos.x : pos.y) = value; + }; + + handleAxis(0, 0.f, Constants::WIDTH, m_velocity.x, m_radius, + Constants::RESTITUTION); + handleAxis(1, 0.f, Constants::HEIGHT, m_velocity.y, m_radius, + Constants::RESTITUTION); + + if (pos.y + m_radius >= Constants::HEIGHT - 1.0f) { + m_velocity.x *= Constants::FRICTION; + if (std::abs(m_velocity.x) < 5.f) m_velocity.x = 0.f; + } + + m_shape.setPosition(pos); +} diff --git a/src/Ball.hpp b/src/Ball.hpp new file mode 100644 index 0000000..cc1705d --- /dev/null +++ b/src/Ball.hpp @@ -0,0 +1,22 @@ +#pragma once +#include + +#include "PhysicalObject.hpp" + +class Ball : public PhysicalObject { + private: + sf::CircleShape m_shape; + sf::Vector2f m_velocity; + float m_radius; + + void handleWallCollision(); + + public: + Ball(float radius, const sf::Vector2f& pos, const sf::Vector2f& vel); + + void update(float dt) override; + void draw(sf::RenderWindow& window) override; + void applyImpulse(const sf::Vector2f& impulse) override; + + sf::Vector2f getPosition() const; +}; diff --git a/src/Constants.hpp b/src/Constants.hpp new file mode 100644 index 0000000..ead0e47 --- /dev/null +++ b/src/Constants.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Constants { +constexpr unsigned WIDTH = 1280; +constexpr unsigned HEIGHT = 720; +constexpr float GRAVITY = 8000.f; +constexpr float RESTITUTION = 0.8f; +constexpr float FRICTION = 0.9f; +constexpr float BALL_RADIUS = 20.f; +} // namespace Constants diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..4c3f39e --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,87 @@ +#include "Game.hpp" + +#include + +#include "Ball.hpp" +#include "Constants.hpp" +#include "VectorMath.hpp" + +Game::Game() { + m_window.create(sf::VideoMode({Constants::WIDTH, Constants::HEIGHT}), + "SFML Playground"); + m_window.setVerticalSyncEnabled(true); + m_timeScale = 1.0f; + m_objects.clear(); + m_objects.push_back(std::make_unique( + Constants::BALL_RADIUS, + sf::Vector2f(Constants::WIDTH / 2.f, Constants::HEIGHT / 4.f), + sf::Vector2f(400.f, 0.f))); +} + +void Game::processKeyPressed(const sf::Event::KeyPressed& keyPressed) { + if (keyPressed.code == sf::Keyboard::Key::Add || + keyPressed.code == sf::Keyboard::Key::Equal) { + m_timeScale *= 1.1f; + } else if (keyPressed.code == sf::Keyboard::Key::Subtract || + keyPressed.code == sf::Keyboard::Key::Hyphen) { + m_timeScale /= 1.1f; + if (m_timeScale < 0.1f) m_timeScale = 0.1f; + } +} + +void Game::processMousePressed( + const sf::Event::MouseButtonPressed& mousePressed) { + if (mousePressed.button == sf::Mouse::Button::Left) { + handleMouseClick( + sf::Vector2i(mousePressed.position.x, mousePressed.position.y)); + } +} + +void Game::processEvents() { + while (const std::optional event = m_window.pollEvent()) { + if (event->is()) { + m_window.close(); + return; + } + if (auto kP = event->getIf()) + processKeyPressed(*kP); + if (auto mP = event->getIf()) + processMousePressed(*mP); + } +} + +void Game::handleMouseClick(const sf::Vector2i& mousePos) { + auto* ball = dynamic_cast(m_objects[0].get()); + if (!ball) return; + sf::Vector2f ballPos = ball->getPosition(); + sf::Vector2f mouseWorld(static_cast(mousePos.x), + static_cast(mousePos.y)); + sf::Vector2f dir = mouseWorld - ballPos; + + dir = VectorMath::normalize(dir); + + ball->applyImpulse(2000.f * dir); +} + +void Game::update() { + float dt = m_clock.restart().asSeconds() * m_timeScale; + for (auto& object : m_objects) { + object->update(dt); + } +} + +void Game::render() { + m_window.clear(sf::Color::Black); + for (auto& object : m_objects) { + object->draw(m_window); + } + m_window.display(); +} + +void Game::run() { + while (m_window.isOpen()) { + processEvents(); + update(); + render(); + } +} diff --git a/src/Game.hpp b/src/Game.hpp new file mode 100644 index 0000000..063c3f1 --- /dev/null +++ b/src/Game.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "PhysicalObject.hpp" + +class PhysicalObject; + +class Game { + private: + float m_timeScale; + sf::RenderWindow m_window; + std::vector> m_objects; + sf::Clock m_clock; + + void processEvents(); + void processKeyPressed(const sf::Event::KeyPressed& keyPressed); + void processMousePressed(const sf::Event::MouseButtonPressed& mousePressed); + void handleMouseClick(const sf::Vector2i& mousePos); + void update(); + void render(); + + public: + Game(); + void run(); +}; diff --git a/src/PhysicalObject.hpp b/src/PhysicalObject.hpp new file mode 100644 index 0000000..2f6f4fd --- /dev/null +++ b/src/PhysicalObject.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include + +class PhysicalObject { + public: + virtual ~PhysicalObject() = default; + virtual void update(float dt) = 0; + virtual void draw(sf::RenderWindow& window) = 0; + virtual void applyImpulse(const sf::Vector2f& impulse) = 0; +}; diff --git a/src/VectorMath.cpp b/src/VectorMath.cpp new file mode 100644 index 0000000..748c713 --- /dev/null +++ b/src/VectorMath.cpp @@ -0,0 +1,17 @@ +#include "VectorMath.hpp" + +#include + +namespace VectorMath { +float length(const sf::Vector2f& vector) { + return std::sqrt(vector.x * vector.x + vector.y * vector.y); +} + +sf::Vector2f normalize(const sf::Vector2f& vector) { + float len = length(vector); + if (len > 0.0001f) { + return vector / len; + } + return vector; +} +} // namespace VectorMath diff --git a/src/VectorMath.hpp b/src/VectorMath.hpp new file mode 100644 index 0000000..5be16fb --- /dev/null +++ b/src/VectorMath.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace VectorMath { +float length(const sf::Vector2f& vector); +sf::Vector2f normalize(const sf::Vector2f& vector); +} // namespace VectorMath diff --git a/src/main.cpp b/src/main.cpp index e1aec12..015c122 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,21 +1,7 @@ -#include +#include "Game.hpp" -int main() -{ - auto window = sf::RenderWindow(sf::VideoMode({1920u, 1080u}), "CMake SFML Project"); - window.setFramerateLimit(144); - - while (window.isOpen()) - { - while (const std::optional event = window.pollEvent()) - { - if (event->is()) - { - window.close(); - } - } - - window.clear(); - window.display(); - } +int main() { + Game game; + game.run(); + return 0; }