From e6c3c1b28873d7d875e14b6e0048effd366e2e4b Mon Sep 17 00:00:00 2001 From: Natalie Martin Date: Mon, 17 Jun 2024 11:41:54 -0500 Subject: [PATCH] Add Velocity Component Create a new component called "Velocity" to generalize things that move in a specific velocity every frame, and create a system that applies that velocity to an entity's position in an isolated, testable, and framerate-independant way. Also remove the speed/velocity calculations from the UserProjectile component so we can use the new Velocity component instead. --- src/game/components/UserProjectile.h | 4 +- src/game/components/Velocity.h | 9 +++ src/game/states/GracilisGame.cpp | 2 + src/game/systems/ApplyVelocity.cpp | 15 +++++ src/game/systems/ApplyVelocity.h | 11 ++++ src/game/systems/Wallop.cpp | 3 +- src/game/systems/ship.cpp | 4 +- test/game/systems/ApplyVelocity.test.cpp | 83 ++++++++++++++++++++++++ 8 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 src/game/components/Velocity.h create mode 100644 src/game/systems/ApplyVelocity.cpp create mode 100644 src/game/systems/ApplyVelocity.h create mode 100644 test/game/systems/ApplyVelocity.test.cpp diff --git a/src/game/components/UserProjectile.h b/src/game/components/UserProjectile.h index 03a7e24..fda3ff3 100644 --- a/src/game/components/UserProjectile.h +++ b/src/game/components/UserProjectile.h @@ -1,8 +1,6 @@ #ifndef GL_ADAGIO_USERPROJECTILE_H #define GL_ADAGIO_USERPROJECTILE_H -struct UserProjectile { - float speed; -}; +struct UserProjectile {}; #endif // GL_ADAGIO_USERPROJECTILE_H diff --git a/src/game/components/Velocity.h b/src/game/components/Velocity.h new file mode 100644 index 0000000..d3120bc --- /dev/null +++ b/src/game/components/Velocity.h @@ -0,0 +1,9 @@ +#ifndef GL_ADAGIO_VELOCITY_H +#define GL_ADAGIO_VELOCITY_H + +struct Velocity { + double direction; + double speed; +}; + +#endif // GL_ADAGIO_VELOCITY_H diff --git a/src/game/states/GracilisGame.cpp b/src/game/states/GracilisGame.cpp index b5ce388..864930e 100644 --- a/src/game/states/GracilisGame.cpp +++ b/src/game/states/GracilisGame.cpp @@ -5,6 +5,7 @@ #include "../components/SpriteAnimation.h" #include "../components/SpriteClip.h" #include "../systems/AnimateSprite.h" +#include "../systems/ApplyVelocity.h" #include "../systems/RemoveDead.h" #include "../systems/RenderSprite.h" #include "../systems/Wallop.h" @@ -15,6 +16,7 @@ void GracilisGame::init() { std::cout << "GracilisGame init" << std::endl; registerSystem(AnimateSprite); registerRenderer(RenderSprite); + registerSystem(ApplyVelocity); registerSystem(ShipSystem); registerSystem(WallopSystem); registerSystem(RemoveDead); diff --git a/src/game/systems/ApplyVelocity.cpp b/src/game/systems/ApplyVelocity.cpp new file mode 100644 index 0000000..b041fed --- /dev/null +++ b/src/game/systems/ApplyVelocity.cpp @@ -0,0 +1,15 @@ +#include "ApplyVelocity.h" +#include "../components/Position.h" +#include "../components/Velocity.h" +#include + +void ApplyVelocity(entt::registry ®istry, Adagio::GameStats &stats, + Adagio::StateMachine *state) { + auto view = registry.view(); + for (auto [entity, pos, velocity] : view.each()) { + const double delta = + stats.getFrameDelta() * 60; // Speed is in "1 pixel per 1/60 of a frame" + pos.position.x += delta * velocity.speed * std::cos(velocity.direction); + pos.position.y += delta * velocity.speed * std::sin(velocity.direction); + } +} \ No newline at end of file diff --git a/src/game/systems/ApplyVelocity.h b/src/game/systems/ApplyVelocity.h new file mode 100644 index 0000000..8962805 --- /dev/null +++ b/src/game/systems/ApplyVelocity.h @@ -0,0 +1,11 @@ +#ifndef GL_ADAGIO_APPLYVELOCITY_H +#define GL_ADAGIO_APPLYVELOCITY_H + +#include "../../state/GameStats.h" +#include "../../state/StateMachine.h" +#include "entt/entt.hpp" + +void ApplyVelocity(entt::registry ®istry, Adagio::GameStats &stats, + Adagio::StateMachine *state); + +#endif // GL_ADAGIO_APPLYVELOCITY_H diff --git a/src/game/systems/Wallop.cpp b/src/game/systems/Wallop.cpp index e0201fe..793f232 100644 --- a/src/game/systems/Wallop.cpp +++ b/src/game/systems/Wallop.cpp @@ -6,8 +6,7 @@ void WallopSystem(entt::registry ®istry, Adagio::GameStats &stats, Adagio::StateMachine *state) { auto view = registry.view(); - for (auto [entity, projectile, pos] : view.each()) { - pos.position.y -= projectile.speed; + for (auto [entity, pos] : view.each()) { if (pos.position.x < -64 || pos.position.x > 640) { registry.emplace(entity); } diff --git a/src/game/systems/ship.cpp b/src/game/systems/ship.cpp index 51ce7df..572876a 100644 --- a/src/game/systems/ship.cpp +++ b/src/game/systems/ship.cpp @@ -7,6 +7,7 @@ #include "../components/SpriteClip.h" #include "../components/SpriteScale.h" #include "../components/UserProjectile.h" +#include "../components/Velocity.h" #include float lowerVelocity(float v); @@ -36,7 +37,8 @@ void ShipSystem(entt::registry ®istry, Adagio::GameStats &stats, } if (IsKeyPressed(KEY_SPACE)) { const auto wallop = registry.create(); - registry.emplace(wallop, 6); + registry.emplace(wallop); + registry.emplace(wallop, -M_PI_2, 6); registry.emplace( wallop, Adagio::Vector2{pos.position.x + 27 - 16, pos.position.y}); registry.emplace(wallop, ship.wallopTexture, diff --git a/test/game/systems/ApplyVelocity.test.cpp b/test/game/systems/ApplyVelocity.test.cpp new file mode 100644 index 0000000..4f7c8e7 --- /dev/null +++ b/test/game/systems/ApplyVelocity.test.cpp @@ -0,0 +1,83 @@ +#include "../../../src/game/systems/ApplyVelocity.h" +#include "../../../src/game/components/Position.h" +#include "../../../src/game/components/Velocity.h" +#include "../EcsTestingHarness.h" +#include +#include + +static EcsTestingHarness harness; +static Position &setUpComponents(double direction, double speed); +static bool doubleEquals(double a, double b); + +TEST_CASE("ApplyVelocity: No components defined", "[system][ApplyVelocity]") { + harness.reset(); + REQUIRE_NOTHROW( + ApplyVelocity(harness.registry, harness.stats, harness.stateMachine)); +} + +TEST_CASE("ApplyVelocity: Does nothing with 0 speed", + "[system][ApplyVelocity]") { + Position &pos = setUpComponents(0, 0); + + harness.stats.advanceTime(1); + harness.testSystemFrame(ApplyVelocity); + + REQUIRE(pos.position.x == 0); + REQUIRE(pos.position.y == 0); +} + +TEST_CASE("ApplyVelocity moves in direction 0", "[system][ApplyVelocity]") { + Position &pos = setUpComponents(0, 1); + + harness.stats.advanceTime(1.0 / 60); + harness.testSystemFrame(ApplyVelocity); + + REQUIRE(pos.position.x == 1); + REQUIRE(pos.position.y == 0); +} + +TEST_CASE("ApplyVelocity moves in direction PI/2", "[system][ApplyVelocity]") { + Position &pos = setUpComponents(M_PI_2, 1); + + harness.stats.advanceTime(1.0 / 60); + harness.testSystemFrame(ApplyVelocity); + + REQUIRE(doubleEquals(pos.position.x, 0)); + REQUIRE(doubleEquals(pos.position.y, 1)); +} + +TEST_CASE("ApplyVelocity moves in direction PI/4", "[system][ApplyVelocity]") { + Position &pos = setUpComponents(M_PI_4, 1); + + harness.stats.advanceTime(1.0 / 60); + harness.testSystemFrame(ApplyVelocity); + + REQUIRE(doubleEquals(pos.position.x, 1.0 / std::sqrt(2))); + REQUIRE(doubleEquals(pos.position.y, 1.0 / std::sqrt(2))); +} + +TEST_CASE("ApplyVelocity applies speed every 1/60th of a second", + "[system][ApplyVelocity]") { + Position &pos = setUpComponents(0, 1); + + harness.stats.advanceTime(1); + harness.testSystemFrame(ApplyVelocity); + + REQUIRE(doubleEquals(pos.position.x, 60)); + REQUIRE(doubleEquals(pos.position.y, 0)); +} + +static Position &setUpComponents(double direction, double speed) { + harness.reset(); + auto entity = harness.registry.create(); + Position &pos = + harness.registry.emplace(entity, Adagio::Vector2d{0, 0}); + harness.registry.emplace(entity, direction, speed); + + return pos; +} + +static bool doubleEquals(double a, double b) { + const double delta = 0.0005; + return std::abs(a - b) < delta; +}