Skip to content

Commit

Permalink
Basic Sprite component + RenderSprite renderer
Browse files Browse the repository at this point in the history
This Component + System deals with basic sprite rendering functionality.
The plan is to add additional optional components that can coexist with
the `Sprite` component to add additional rendering options. Then a
sprite animation component/system can manipulate these various values to
create reusable and extendable sprite animation.
  • Loading branch information
meisekimiu committed Jun 5, 2024
1 parent e66e17b commit f54001f
Show file tree
Hide file tree
Showing 16 changed files with 325 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ project(gl-adagio)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin")
file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp" "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/src/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/src/state/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/states/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/*.cpp")
file(GLOB_RECURSE TESTS "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/test/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/test/audio/**/*.cpp"
file(GLOB_RECURSE TESTS "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/test/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/test/audio/**/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/factories/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" "${CMAKE_SOURCE_DIR}/test/game/**/*.cpp"
"${CMAKE_SOURCE_DIR}/src/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/test/graphics/*.cpp"
"${CMAKE_SOURCE_DIR}/src/state/*.cpp" "${CMAKE_SOURCE_DIR}/test/state/**/*.cpp"
"${CMAKE_SOURCE_DIR}/test/*.cpp")
Expand Down
15 changes: 15 additions & 0 deletions src/game/components/Sprite.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef GL_ADAGIO_SPRITE_H
#define GL_ADAGIO_SPRITE_H

#include "../../graphics/Texture2D.h"
#include "../../graphics/AbstractTextureManager.h"
#include "../../math/Vector2.h"
#include "../../math/Rect.h"

struct Sprite {
Adagio::Texture2D texture;
Adagio::Vector2d position;
int zIndex{0};
};

#endif //GL_ADAGIO_SPRITE_H
9 changes: 9 additions & 0 deletions src/game/factories/LoadSprite.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "LoadSprite.h"

Sprite loadSprite(const std::string &resource, Adagio::AbstractTextureManager &textureManager) {
Sprite s;
s.texture = textureManager.load(resource);
s.zIndex = 0;
s.position = {0, 0};
return s;
}
8 changes: 8 additions & 0 deletions src/game/factories/LoadSprite.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef GL_ADAGIO_LOADSPRITE_H
#define GL_ADAGIO_LOADSPRITE_H

#include "../components/Sprite.h"

Sprite loadSprite(const std::string &resource, Adagio::AbstractTextureManager &textureManager);

#endif //GL_ADAGIO_LOADSPRITE_H
16 changes: 16 additions & 0 deletions src/game/systems/RenderSprite.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "RenderSprite.h"
#include "../components/Sprite.h"
#include "../components/Position.h"

void
RenderSprite(entt::registry &registry, Adagio::SpriteBatch &spriteBatch, Adagio::RenderingServices &services) {
auto view = registry.view<Sprite>();
for (auto [entity, sprite]: view.each()) {
Adagio::Vector2d position = sprite.position;
Position *posComponent = registry.try_get<Position>(entity);
if (posComponent) {
position += posComponent->position;
}
spriteBatch.draw(sprite.texture, position, static_cast<short>(sprite.zIndex));
}
}
12 changes: 12 additions & 0 deletions src/game/systems/RenderSprite.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef GL_ADAGIO_RENDERSPRITE_H
#define GL_ADAGIO_RENDERSPRITE_H

#include "entt/entt.hpp"
#include "../../graphics/SpriteBatch.h"
#include "../../state/GameState.h"

void
RenderSprite(entt::registry &registry, Adagio::SpriteBatch &spriteBatch, Adagio::RenderingServices &services);


#endif //GL_ADAGIO_RENDERSPRITE_H
18 changes: 18 additions & 0 deletions test/game/EcsTestingHarness.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "EcsTestingHarness.h"

EcsTestingHarness::EcsTestingHarness() {
renderingServices = {&spriteBatch, graphicsDevice.getTextureManager(), &stats};
}

void EcsTestingHarness::reset() {
spriteBatch.begin();
spriteBatch.end();
registry.clear();
stats.reset();
}

void EcsTestingHarness::testRendererFrame(Adagio::RendererFn renderer) {
spriteBatch.begin();
renderer(registry, spriteBatch, renderingServices);
spriteBatch.end();
}
27 changes: 27 additions & 0 deletions test/game/EcsTestingHarness.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef GL_ADAGIO_ECSTESTINGHARNESS_H
#define GL_ADAGIO_ECSTESTINGHARNESS_H

#include "../../src/graphics/SpriteBatch.h"
#include "../../src/state/RenderingServices.h"
#include "harness/EcsMockGraphicsDevice.h"
#include "harness/MockGameStats.h"
#include "entt/entt.hpp"
#include "../../src/state/EntityGameState.h"

class EcsTestingHarness {
public:
EcsMockGraphicsDevice graphicsDevice;
Adagio::SpriteBatch spriteBatch{&graphicsDevice};
MockGameStats stats;
entt::registry registry;
Adagio::RenderingServices renderingServices{};

EcsTestingHarness();

void reset();

void testRendererFrame(Adagio::RendererFn renderer);
};


#endif //GL_ADAGIO_ECSTESTINGHARNESS_H
15 changes: 15 additions & 0 deletions test/game/factories/LoadSprite.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <catch2/catch.hpp>
#include "../../../src/game/factories/LoadSprite.h"
#include "../harness/MockTextureLibrary.h"


TEST_CASE("It can make a basic Sprite object", "[factory][LoadSprite]") {
auto textureMan = MockTextureLibrary();
auto sprite = loadSprite("test.jpg", textureMan);

REQUIRE(sprite.texture.handle == 0xcafe);
REQUIRE(sprite.texture.getSecretId() == 0xcafe);
REQUIRE(sprite.position.x == 0);
REQUIRE(sprite.position.y == 0);
REQUIRE(sprite.zIndex == 0);
}
43 changes: 43 additions & 0 deletions test/game/harness/EcsMockGraphicsDevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "EcsMockGraphicsDevice.h"

void EcsMockGraphicsDevice::begin() {
spritesRenderedOnFrame.clear();
}

void EcsMockGraphicsDevice::end() {
// do nothing
}

void EcsMockGraphicsDevice::setClearColor(const Adagio::Color &color) {
clearColor = color;
}

void
EcsMockGraphicsDevice::drawTexture(Adagio::Texture2D &texture, const Adagio::RectF &source, const Adagio::RectF &dest,
const Adagio::Vector2d &origin, float rotation, const Adagio::Color &tint) {
Adagio::SpriteState sprite;
sprite.texture = &texture;
sprite.rotation = rotation;
sprite.tint = tint;
sprite.origin = origin;
sprite.destination = dest;
sprite.source = source;
spritesRenderedOnFrame.push_back(sprite);
}

Adagio::AbstractTextureManager *EcsMockGraphicsDevice::getTextureManager() {
return &textureLibrary;
}

Adagio::Color EcsMockGraphicsDevice::getClearColor() const {
return clearColor;
}

const std::vector<Adagio::SpriteState> *EcsMockGraphicsDevice::getSprites() const {
return &spritesRenderedOnFrame;
}

void EcsMockGraphicsDevice::drawText(Font &font, const char *text, const Adagio::Vector2d &position, float fontSize,
float spacing, const Adagio::Color &tint) {
// do nothing for now
}
35 changes: 35 additions & 0 deletions test/game/harness/EcsMockGraphicsDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef GL_ADAGIO_ECSMOCKGRAPHICSDEVICE_H
#define GL_ADAGIO_ECSMOCKGRAPHICSDEVICE_H

#include "../../../src/graphics/GraphicsDevice.h"
#include "../../../src/graphics/SpriteState.h"
#include "MockTextureLibrary.h"

class EcsMockGraphicsDevice : public Adagio::GraphicsDevice {
public:
void begin() override;

void end() override;

void setClearColor(const Adagio::Color &color) override;

void drawTexture(Adagio::Texture2D &texture, const Adagio::RectF &source, const Adagio::RectF &dest,
const Adagio::Vector2d &origin, float rotation, const Adagio::Color &tint) override;

void drawText(Font &font, const char *text, const Adagio::Vector2d &position, float fontSize, float spacing,
const Adagio::Color &tint) override;

Adagio::AbstractTextureManager *getTextureManager() override;

[[nodiscard]] Adagio::Color getClearColor() const;

[[nodiscard]] const std::vector<Adagio::SpriteState> *getSprites() const;

protected:
Adagio::Color clearColor{0, 0, 0, 0};
std::vector<Adagio::SpriteState> spritesRenderedOnFrame;
MockTextureLibrary textureLibrary;
};


#endif //GL_ADAGIO_ECSMOCKGRAPHICSDEVICE_H
28 changes: 28 additions & 0 deletions test/game/harness/MockGameStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "MockGameStats.h"

bool MockGameStats::isRunning() const {
return running;
}

void MockGameStats::closeGame() {
running = false;
}

float MockGameStats::getFrameDelta() const {
return static_cast<float>(gameTime - lastTickTime);
}

double MockGameStats::getGameTime() const {
return gameTime;
}

void MockGameStats::advanceTime(double delta) {
lastTickTime = gameTime;
gameTime += delta;
}

void MockGameStats::reset() {
lastTickTime = 0;
gameTime = 0;
running = true;
}
27 changes: 27 additions & 0 deletions test/game/harness/MockGameStats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef GL_ADAGIO_MOCKGAMESTATS_H
#define GL_ADAGIO_MOCKGAMESTATS_H

#include "../../../src/state/GameStats.h"

class MockGameStats : public Adagio::GameStats {
public:
[[nodiscard]] bool isRunning() const override;

void closeGame() override;

[[nodiscard]] float getFrameDelta() const override;

[[nodiscard]] double getGameTime() const override;

void advanceTime(double delta);

void reset();

protected:
bool running{true};
double gameTime{0.0};
double lastTickTime{0.0};
};


#endif //GL_ADAGIO_MOCKGAMESTATS_H
9 changes: 9 additions & 0 deletions test/game/harness/MockTextureLibrary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "MockTextureLibrary.h"

Adagio::Texture2D MockTextureLibrary::load(std::string resource) {
return Adagio::Texture2D(0xcafe, 0xcafe, 256, 128);
}

void MockTextureLibrary::unload(Adagio::Texture2D texture) {
// do nothing
}
13 changes: 13 additions & 0 deletions test/game/harness/MockTextureLibrary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef GL_ADAGIO_MOCKTEXTURELIBRARY_H
#define GL_ADAGIO_MOCKTEXTURELIBRARY_H

#include "../../../src/graphics/AbstractTextureManager.h"

class MockTextureLibrary : public Adagio::AbstractTextureManager {
public:
Adagio::Texture2D load(std::string resource) override;

void unload(Adagio::Texture2D texture) override;
};

#endif //GL_ADAGIO_MOCKTEXTURELIBRARY_H
49 changes: 49 additions & 0 deletions test/game/systems/RenderSprite.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <catch2/catch.hpp>
#include "../../../src/game/components/Sprite.h"
#include "../../../src/game/components/Position.h"
#include "../../../src/game/systems/RenderSprite.h"
#include "../EcsTestingHarness.h"

static EcsTestingHarness harness;

TEST_CASE("No components defined", "[renderer][RenderSprite]") {
harness.reset();
REQUIRE_NOTHROW(RenderSprite(harness.registry, harness.spriteBatch, harness.renderingServices));
}

TEST_CASE("It renders a basic sprite", "[renderer][RenderSprite]") {
harness.reset();
auto sprite = harness.registry.create();
auto &spriteComponent = harness.registry.emplace<Sprite>(sprite);
spriteComponent.texture = Adagio::Texture2D{1, 1, 128, 128};
spriteComponent.position = Adagio::Vector2d{1, 2};

harness.testRendererFrame(RenderSprite);

auto renderedSprites = *(harness.graphicsDevice.getSprites());
REQUIRE(renderedSprites.size() == 1);
REQUIRE(renderedSprites[0].texture->handle == 1);
REQUIRE(renderedSprites[0].texture->getSecretId() == 1);
REQUIRE(renderedSprites[0].texture->getWidth() == 128);
REQUIRE(renderedSprites[0].texture->getHeight() == 128);
REQUIRE(renderedSprites[0].destination.position.x == 1);
REQUIRE(renderedSprites[0].destination.position.y == 2);
REQUIRE(renderedSprites[0].destination.size.x == 128);
REQUIRE(renderedSprites[0].destination.size.y == 128);
}

TEST_CASE("A sprite's position property is offset off of the `Position` component if it exists") {
harness.reset();
auto sprite = harness.registry.create();
auto &spriteComponent = harness.registry.emplace<Sprite>(sprite);
auto &position = harness.registry.emplace<Position>(sprite);
spriteComponent.texture = Adagio::Texture2D{1, 1, 128, 128};
spriteComponent.position = Adagio::Vector2d{1, 2};
position.position = Adagio::Vector2d{300, 300};

harness.testRendererFrame(RenderSprite);

auto renderedSprite = (*(harness.graphicsDevice.getSprites()))[0];
REQUIRE(renderedSprite.destination.position.x == 301);
REQUIRE(renderedSprite.destination.position.y == 302);
}

0 comments on commit f54001f

Please sign in to comment.