From bb9d1081027c88a61ce40b62acbb01884537efa1 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Sun, 30 Nov 2025 00:37:44 +0100 Subject: [PATCH] Implement Sprite class --- assets/sprites/neocat_64.png | Bin 0 -> 3587 bytes assets/sprites/neofox_64.png | Bin 0 -> 3517 bytes src/core/drawing/sprite.cppm | 84 +++++++++++++++++++++++++++++++++++ src/core/render_server.cppm | 16 +++++-- src/game/game.cppm | 38 +++++++++------- src/game/sprite.cppm | 35 --------------- src/wrappers/sdl/render.cppm | 15 +++++++ 7 files changed, 132 insertions(+), 56 deletions(-) create mode 100644 assets/sprites/neocat_64.png create mode 100644 assets/sprites/neofox_64.png create mode 100644 src/core/drawing/sprite.cppm delete mode 100644 src/game/sprite.cppm diff --git a/assets/sprites/neocat_64.png b/assets/sprites/neocat_64.png new file mode 100644 index 0000000000000000000000000000000000000000..b8c0957d83ff1d9a7179dce3b8dc3fe5c4126ced GIT binary patch literal 3587 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 JPDHLkV1oIb(s=*? literal 0 HcmV?d00001 diff --git a/assets/sprites/neofox_64.png b/assets/sprites/neofox_64.png new file mode 100644 index 0000000000000000000000000000000000000000..b00da058ebe4413e0b09d8f1f8a6f35d78999c76 GIT binary patch literal 3517 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 literal 0 HcmV?d00001 diff --git a/src/core/drawing/sprite.cppm b/src/core/drawing/sprite.cppm new file mode 100644 index 0000000..a927b02 --- /dev/null +++ b/src/core/drawing/sprite.cppm @@ -0,0 +1,84 @@ +module; + +#include +#include +#include +#include + +export module core.drawing.sprite; + +import core.render_server; +import wrappers.sdl; + +export namespace core +{ + class Sprite + { + // Texture ID (managed by TextureManager in RenderServer) + TextureID texture_id_; + + // Texture source boundaries: which part of the texture to render + sdl::FRect texture_boundaries_; + + // Texture offset: static offset that is added to the render position (BEFORE scale is applied) + sdl::FPoint texture_offset_{0, 0}; + + // Texture scale: factors applied to width/height (AFTER offset is applied) + // TODO: Proper vector class? + sdl::FPoint texture_scale_{1, 1}; + + // Sprite position: where to render the sprite (world coordinates) + sdl::FPoint position_{0, 0}; + + public: + explicit Sprite( + const TextureID texture_id, + const sdl::FRect texture_boundaries, + const sdl::FPoint position, + const sdl::FPoint texture_offset = {0, 0}, + const sdl::FPoint texture_scale = {1, 1} + ) + : texture_id_{texture_id}, + texture_boundaries_{texture_boundaries}, + texture_offset_{texture_offset}, + texture_scale_{texture_scale}, + position_{position} + { + assert(texture_boundaries_.w > 0 && texture_boundaries_.h > 0); + } + + static Sprite create_with_texture( + RenderServer& render_server, + const std::string& filename, + std::optional texture_boundaries, + auto... args + ) + { + const TextureID texture_id = render_server.load_texture(filename); + + if (!texture_boundaries.has_value()) { + const sdl::Texture& texture = render_server.get_texture(texture_id); + texture_boundaries = texture.get_boundaries(); + } + + return Sprite(texture_id, texture_boundaries.value(), args...); + } + + void set_position(const sdl::FPoint new_position) + { + position_ = new_position; + } + + void draw(const RenderServer& render_server) const + { + const sdl::FRect dest_rect{ + position_.x + (texture_scale_.x * texture_offset_.x), + position_.y + (texture_scale_.y * texture_offset_.y), + texture_scale_.x * texture_boundaries_.w, + texture_scale_.y * texture_boundaries_.h, + }; + + render_server.render_texture(texture_id_, &texture_boundaries_, &dest_rect); + } + }; +} diff --git a/src/core/render_server.cppm b/src/core/render_server.cppm index c159769..bcb4c9f 100644 --- a/src/core/render_server.cppm +++ b/src/core/render_server.cppm @@ -43,13 +43,21 @@ export namespace core } /** - * Loads a texture from a file (if not loaded yet) and returns its texture ID. + * Load a texture from a file (if not loaded yet) and return its texture ID. */ constexpr TextureID load_texture(const std::string& filename) { return texture_manager_.load_resource_by_name(filename); } + /** + * Get a reference to a texture by its texture ID. + */ + constexpr const sdl::Texture& get_texture(const TextureID texture_id) const + { + return texture_manager_.get_resource(texture_id); + } + void start_frame() const { renderer_.clear(); @@ -60,16 +68,16 @@ export namespace core renderer_.present(); } - constexpr void render_texture( + void render_texture( const TextureID texture_id, const sdl::FRect* src_rect, const sdl::FRect* dest_rect ) const { - render_texture(texture_manager_.get_resource(texture_id), src_rect, dest_rect); + render_texture(get_texture(texture_id), src_rect, dest_rect); } - void render_texture( + constexpr void render_texture( const sdl::Texture& texture, const sdl::FRect* src_rect, const sdl::FRect* dest_rect diff --git a/src/game/game.cppm b/src/game/game.cppm index 7f7eab0..213cb50 100644 --- a/src/game/game.cppm +++ b/src/game/game.cppm @@ -2,14 +2,15 @@ module; #include #include +#include #include #include export module game.game; +import core.drawing.sprite; import core.engine; import core.render_server; -import game.sprite; import wrappers.sdl; import wrappers.sdl_image; @@ -24,15 +25,20 @@ export namespace game core::Engine& engine_; // Sprites for testing - Sprite player_sprite_; - std::vector sprites_; + core::Sprite player_sprite_; + std::vector sprites_; // Private constructor explicit Game(core::Engine& engine) : engine_(engine), player_sprite_{ - engine_.get_render_server().load_texture("assets/neocat.png"), - sdl::FRect{0, 0, 100, 100} + core::Sprite::create_with_texture( + engine_.get_render_server(), + "assets/sprites/neocat_64.png", + std::nullopt, + sdl::FPoint{0, 0}, + sdl::FPoint{-32, -32} + ) } {} @@ -63,21 +69,19 @@ export namespace game bool handle_event(const sdl::Event* event) { if (event->type == sdl::EventType::MouseMotion) { - player_sprite_.move( - event->motion.x - 50, - event->motion.y - 50 - ); + player_sprite_.set_position({event->motion.x, event->motion.y}); return true; } if (event->type == sdl::EventType::MouseButtonUp) { - sprites_.emplace_back( - engine_.get_render_server().load_texture("assets/neofox.png"), - sdl::FRect{ - event->motion.x - 50, - event->motion.y - 50, - 100, 100 - } + sprites_.push_back( + core::Sprite::create_with_texture( + engine_.get_render_server(), + "assets/sprites/neofox_64.png", + std::nullopt, + sdl::FPoint{event->motion.x, event->motion.y}, + sdl::FPoint{-32, -32} + ) ); return true; } @@ -94,7 +98,7 @@ export namespace game const auto& render_server = engine_.get_render_server(); render_server.start_frame(); - for (const Sprite& sprite : sprites_) { + for (const core::Sprite& sprite : sprites_) { sprite.draw(render_server); } player_sprite_.draw(render_server); diff --git a/src/game/sprite.cppm b/src/game/sprite.cppm deleted file mode 100644 index cd297c3..0000000 --- a/src/game/sprite.cppm +++ /dev/null @@ -1,35 +0,0 @@ -module; - -#include - -export module game.sprite; - -import core.render_server; -import wrappers.sdl; - -// TODO: Move this to a different namespace (core, drawing, ...?) -export namespace game -{ - class Sprite - { - core::TextureID texture_id_; - sdl::FRect dest_rect_{0, 0, 0, 0}; - - public: - explicit Sprite(const core::TextureID texture_id, const sdl::FRect dest_rect) - : texture_id_{texture_id}, - dest_rect_{dest_rect} - {} - - void move(const float x, const float y) - { - dest_rect_.x = x; - dest_rect_.y = y; - } - - void draw(const core::RenderServer& render_server) const - { - render_server.render_texture(texture_id_, nullptr, &dest_rect_); - } - }; -} diff --git a/src/wrappers/sdl/render.cppm b/src/wrappers/sdl/render.cppm index e0b28c0..0cf34e1 100644 --- a/src/wrappers/sdl/render.cppm +++ b/src/wrappers/sdl/render.cppm @@ -35,6 +35,21 @@ export namespace sdl assert(raw_texture_); return raw_texture_.get(); } + + constexpr int get_width() const + { + return raw_texture_->w; + } + + constexpr int get_height() const + { + return raw_texture_->h; + } + + constexpr FRect get_boundaries() const + { + return {0, 0, static_cast(raw_texture_->w), static_cast(raw_texture_->h)}; + } }; /**