Skip to content

Commit

Permalink
Add Velocity Component
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
meisekimiu committed Jun 17, 2024
1 parent 5119c03 commit e6c3c1b
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 6 deletions.
4 changes: 1 addition & 3 deletions src/game/components/UserProjectile.h
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions src/game/components/Velocity.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/game/states/GracilisGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions src/game/systems/ApplyVelocity.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "ApplyVelocity.h"
#include "../components/Position.h"
#include "../components/Velocity.h"
#include <cmath>

void ApplyVelocity(entt::registry &registry, Adagio::GameStats &stats,
Adagio::StateMachine *state) {
auto view = registry.view<Position, Velocity>();
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);
}
}
11 changes: 11 additions & 0 deletions src/game/systems/ApplyVelocity.h
Original file line number Diff line number Diff line change
@@ -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 &registry, Adagio::GameStats &stats,
Adagio::StateMachine *state);

#endif // GL_ADAGIO_APPLYVELOCITY_H
3 changes: 1 addition & 2 deletions src/game/systems/Wallop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
void WallopSystem(entt::registry &registry, Adagio::GameStats &stats,
Adagio::StateMachine *state) {
auto view = registry.view<UserProjectile, Position>();
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<Dead>(entity);
}
Expand Down
4 changes: 3 additions & 1 deletion src/game/systems/ship.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "../components/SpriteClip.h"
#include "../components/SpriteScale.h"
#include "../components/UserProjectile.h"
#include "../components/Velocity.h"
#include <iostream>

float lowerVelocity(float v);
Expand Down Expand Up @@ -36,7 +37,8 @@ void ShipSystem(entt::registry &registry, Adagio::GameStats &stats,
}
if (IsKeyPressed(KEY_SPACE)) {
const auto wallop = registry.create();
registry.emplace<UserProjectile>(wallop, 6);
registry.emplace<UserProjectile>(wallop);
registry.emplace<Velocity>(wallop, -M_PI_2, 6);
registry.emplace<Position>(
wallop, Adagio::Vector2{pos.position.x + 27 - 16, pos.position.y});
registry.emplace<Sprite>(wallop, ship.wallopTexture,
Expand Down
83 changes: 83 additions & 0 deletions test/game/systems/ApplyVelocity.test.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch.hpp>
#include <cmath>

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<Position>(entity, Adagio::Vector2d{0, 0});
harness.registry.emplace<Velocity>(entity, direction, speed);

return pos;
}

static bool doubleEquals(double a, double b) {
const double delta = 0.0005;
return std::abs(a - b) < delta;
}

0 comments on commit e6c3c1b

Please sign in to comment.