From ac3b6121b4b293779532cd189387c3a09dcd8ef7 Mon Sep 17 00:00:00 2001 From: Ziyu Date: Wed, 21 May 2025 10:04:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=B8=B2=E6=9F=93=E5=99=A8?= =?UTF-8?q?=E7=9B=B8=E6=9C=BA=E4=B8=8E=E7=B2=BE=E7=81=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + src/engine/core/game_app.cpp | 69 +++++++++++- src/engine/core/game_app.h | 17 ++- src/engine/render/camera.cpp | 80 +++++++++++++ src/engine/render/camera.h | 45 ++++++++ src/engine/render/renderer.cpp | 199 +++++++++++++++++++++++++++++++++ src/engine/render/renderer.h | 91 +++++++++++++++ src/engine/render/sprite.h | 46 ++++++++ src/engine/utils/math.h | 12 ++ src/main.cpp | 2 +- 10 files changed, 557 insertions(+), 6 deletions(-) create mode 100644 src/engine/render/camera.cpp create mode 100644 src/engine/render/camera.h create mode 100644 src/engine/render/renderer.cpp create mode 100644 src/engine/render/renderer.h create mode 100644 src/engine/render/sprite.h create mode 100644 src/engine/utils/math.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6df8dd1..184ba34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,8 @@ add_executable(${TARGET} src/engine/resource/texture_manager.cpp src/engine/resource/audio_manager.cpp src/engine/resource/font_manager.cpp + src/engine/render/renderer.cpp + src/engine/render/camera.cpp ) # 链接库 diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index 0d665b6..9634b24 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -1,6 +1,8 @@ #include "game_app.h" #include "time.h" #include "../resource/resource_manager.h" +#include "../render/renderer.h" +#include "../render/camera.h" #include #include @@ -40,6 +42,8 @@ bool GameApp::init() { if (!initSDL()) return false; if (!initTime()) return false; if (!initResourceManager()) return false; + if (!initRenderer()) return false; + if (!initCamera()) return false; // 测试资源管理器 testResourceManager(); @@ -59,11 +63,19 @@ void GameApp::handleEvents() { } void GameApp::update(float /* delta_time */) { - // 游戏逻辑更新,暂时为空 + // 游戏逻辑更新 + testCamera(); } void GameApp::render() { - // 渲染代码,暂时为空 + // 1. 清除屏幕 + renderer_->clearScreen(); + + // 2. 具体渲染代码 + testRenderer(); + + // 3. 更新屏幕显示 + renderer_->present(); } void GameApp::close() { @@ -101,6 +113,9 @@ bool GameApp::initSDL() { spdlog::error("无法创建渲染器! SDL错误: {}", SDL_GetError()); return false; } + + // 设置逻辑分辨率 + SDL_SetRenderLogicalPresentation(sdl_renderer_, 640, 360, SDL_LOGICAL_PRESENTATION_LETTERBOX); spdlog::trace("SDL 初始化成功。"); return true; } @@ -127,6 +142,31 @@ bool GameApp::initResourceManager() { return true; } +bool GameApp::initRenderer() { + try { + renderer_ = std::make_unique(sdl_renderer_, resource_manager_.get()); + } catch (const std::exception& e) { + spdlog::error("初始化渲染器失败: {}", e.what()); + return false; + } + spdlog::trace("渲染器初始化成功。"); + return true; +} + +bool GameApp::initCamera() { + try { + camera_ = std::make_unique(glm::vec2(640, 360)); + } catch (const std::exception& e) { + spdlog::error("初始化相机失败: {}", e.what()); + return false; + } + spdlog::trace("相机初始化成功。"); + return true; +} + + +// --- 测试用函数 --- + void GameApp::testResourceManager() { resource_manager_->getTexture("assets/textures/Actors/eagle-attack.png"); @@ -138,4 +178,29 @@ void GameApp::testResourceManager() resource_manager_->unloadSound("assets/audio/button_click.wav"); } +void GameApp::testRenderer() +{ + engine::render::Sprite sprite_world("assets/textures/Actors/frog.png"); + engine::render::Sprite sprite_ui("assets/textures/UI/buttons/Start1.png"); + engine::render::Sprite sprite_parallax("assets/textures/Layers/back.png"); + + static float rotation = 0.0f; + rotation += 0.1f; + + // 注意渲染顺序 + renderer_->drawParallax(*camera_, sprite_parallax, glm::vec2(100, 100), glm::vec2(0.5f, 0.5f), glm::bvec2(true, false)); + renderer_->drawSprite(*camera_, sprite_world, glm::vec2(200, 200), glm::vec2(1.0f, 1.0f), rotation); + renderer_->drawUISprite(sprite_ui, glm::vec2(100, 100)); + +} + +void GameApp::testCamera() +{ + auto key_state = SDL_GetKeyboardState(nullptr); + if (key_state[SDL_SCANCODE_UP]) camera_->move(glm::vec2(0, -1)); + if (key_state[SDL_SCANCODE_DOWN]) camera_->move(glm::vec2(0, 1)); + if (key_state[SDL_SCANCODE_LEFT]) camera_->move(glm::vec2(-1, 0)); + if (key_state[SDL_SCANCODE_RIGHT]) camera_->move(glm::vec2(1, 0)); +} + } // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/game_app.h b/src/engine/core/game_app.h index 60ea3f4..41cd959 100644 --- a/src/engine/core/game_app.h +++ b/src/engine/core/game_app.h @@ -9,6 +9,11 @@ namespace engine::resource { class ResourceManager; } +namespace engine::render { +class Renderer; +class Camera; +} + namespace engine::core { // 命名空间的最佳实践:与文件路径一致 class Time; @@ -24,6 +29,8 @@ private: // 引擎组件 std::unique_ptr time_; std::unique_ptr resource_manager_; + std::unique_ptr renderer_; + std::unique_ptr camera_; public: GameApp(); @@ -48,12 +55,16 @@ private: void close(); // 各模块的初始化/创建函数,在init()中调用 - bool initSDL(); - bool initTime(); - bool initResourceManager(); + [[nodiscard]] bool initSDL(); + [[nodiscard]] bool initTime(); + [[nodiscard]] bool initResourceManager(); + [[nodiscard]] bool initRenderer(); + [[nodiscard]] bool initCamera(); // 测试用函数 void testResourceManager(); + void testRenderer(); + void testCamera(); }; } // namespace engine::core diff --git a/src/engine/render/camera.cpp b/src/engine/render/camera.cpp new file mode 100644 index 0000000..a19f430 --- /dev/null +++ b/src/engine/render/camera.cpp @@ -0,0 +1,80 @@ +#include "camera.h" +#include "../utils/math.h" +#include + +namespace engine::render { + +Camera::Camera(const glm::vec2& viewport_size, const glm::vec2& position, const std::optional limit_bounds) + : viewport_size_(viewport_size), position_(position), limit_bounds_(limit_bounds) { + spdlog::trace("Camera 初始化成功,位置: {},{}", position_.x, position_.y); +} + +void Camera::setPosition(const glm::vec2& position) { + position_ = position; + clampPosition(); +} + +void Camera::update(float /* delta_time */) +{ + // TODO 自动跟随目标 +} + +void Camera::move(const glm::vec2 &offset) +{ + position_ += offset; + clampPosition(); +} + +void Camera::setLimitBounds(const engine::utils::Rect& bounds) +{ + limit_bounds_ = bounds; + clampPosition(); // 设置边界后,立即应用限制 +} + +const glm::vec2& Camera::getPosition() const { + return position_; +} + +void Camera::clampPosition() +{ + // 边界检查需要确保相机视图(position 到 position + viewport_size)在 limit_bounds 内 + if (limit_bounds_.has_value() && limit_bounds_->size.x > 0 && limit_bounds_->size.y > 0) { + // 计算允许的相机位置范围 + glm::vec2 min_cam_pos = limit_bounds_->position; + glm::vec2 max_cam_pos = limit_bounds_->position + limit_bounds_->size - viewport_size_; + + // 确保 max_cam_pos 不小于 min_cam_pos (视口可能比世界还大) + max_cam_pos.x = std::max(min_cam_pos.x, max_cam_pos.x); + max_cam_pos.y = std::max(min_cam_pos.y, max_cam_pos.y); + + position_ = glm::clamp(position_, min_cam_pos, max_cam_pos); + } + // 如果 limit_bounds 无效则不进行限制 +} + +glm::vec2 Camera::worldToScreen(const glm::vec2& world_pos) const { + // 将世界坐标减去相机左上角位置 + return world_pos - position_; +} + +glm::vec2 Camera::worldToScreenWithParallax(const glm::vec2 &world_pos, const glm::vec2 &scroll_factor) const +{ + // 相机位置应用滚动因子 + return world_pos - position_ * scroll_factor; +} + +glm::vec2 Camera::screenToWorld(const glm::vec2 &screen_pos) const +{ + // 将屏幕坐标加上相机左上角位置 + return screen_pos + position_; +} + +glm::vec2 Camera::getViewportSize() const { + return viewport_size_; +} + +std::optional Camera::getLimitBounds() const { + return limit_bounds_; +} + +} // namespace engine::render \ No newline at end of file diff --git a/src/engine/render/camera.h b/src/engine/render/camera.h new file mode 100644 index 0000000..489255e --- /dev/null +++ b/src/engine/render/camera.h @@ -0,0 +1,45 @@ +#pragma once +#include "../utils/math.h" +#include + +namespace engine::render { + +/** + * @brief 相机类负责管理相机位置和视口大小,并提供坐标转换功能。 + * 它还包含限制相机移动范围的边界。 + */ +class Camera final { +private: + glm::vec2 viewport_size_; ///< @brief 视口大小(屏幕大小) + glm::vec2 position_; ///< @brief 相机左上角的世界坐标 + std::optional limit_bounds_; ///< @brief 限制相机的移动范围,空值表示不限制 + +public: + + Camera(const glm::vec2& viewport_size, const glm::vec2& position = glm::vec2(0.0f, 0.0f), const std::optional limit_bounds = std::nullopt); + + void update(float delta_time); ///< @brief 更新相机位置 + void move(const glm::vec2& offset); ///< @brief 移动相机 + + glm::vec2 worldToScreen(const glm::vec2& world_pos) const; ///< @brief 世界坐标转屏幕坐标 + glm::vec2 worldToScreenWithParallax(const glm::vec2& world_pos, const glm::vec2& scroll_factor) const; ///< @brief 世界坐标转屏幕坐标,考虑视差滚动 + glm::vec2 screenToWorld(const glm::vec2& screen_pos) const; ///< @brief 屏幕坐标转世界坐标 + + void setPosition(const glm::vec2& position); ///< @brief 设置相机位置 + void setLimitBounds(const engine::utils::Rect& bounds); ///< @brief 设置限制相机的移动范围 + + const glm::vec2& getPosition() const; ///< @brief 获取相机位置 + std::optional getLimitBounds() const; ///< @brief 获取限制相机的移动范围 + glm::vec2 getViewportSize() const; ///< @brief 获取视口大小 + + // 禁用拷贝和移动语义 + Camera(const Camera&) = delete; + Camera& operator=(const Camera&) = delete; + Camera(Camera&&) = delete; + Camera& operator=(Camera&&) = delete; + +private: + void clampPosition(); ///< @brief 限制相机位置在边界内 +}; + +} // namespace engine::render \ No newline at end of file diff --git a/src/engine/render/renderer.cpp b/src/engine/render/renderer.cpp new file mode 100644 index 0000000..c09cb21 --- /dev/null +++ b/src/engine/render/renderer.cpp @@ -0,0 +1,199 @@ +#include "renderer.h" +#include "../resource/resource_manager.h" +#include "camera.h" +#include "sprite.h" +#include +#include // For std::runtime_error +#include + +namespace engine::render { + +// 构造函数: 执行初始化,增加 ResourceManager +Renderer::Renderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager) + : renderer_(sdl_renderer), resource_manager_(resource_manager) +{ + spdlog::trace("构造 Renderer..."); + if (!renderer_) { + throw std::runtime_error("Renderer 构造失败: 提供的 SDL_Renderer 指针为空。"); + } + if (!resource_manager_) { + // ResourceManager 是 drawSprite 所必需的 + throw std::runtime_error("Renderer 构造失败: 提供的 ResourceManager 指针为空。"); + } + setDrawColor(0, 0, 0, 255); + spdlog::trace("Renderer 构造成功。"); +} + +void Renderer::drawSprite(const Camera& camera, const Sprite& sprite, const glm::vec2& position, const glm::vec2& scale, double angle) { + auto texture = resource_manager_->getTexture(sprite.getTextureId()); + if (!texture) { + spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId()); + return; + } + + auto src_rect = getSpriteSrcRect(sprite); + if (!src_rect.has_value()) { + spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId()); + return; + } + + // 应用相机变换 + glm::vec2 position_screen = camera.worldToScreen(position); + + // 计算目标矩形,注意 position 是精灵的左上角坐标 + float scaled_w = src_rect.value().w * scale.x; + float scaled_h = src_rect.value().h * scale.y; + SDL_FRect dest_rect = { + position_screen.x, + position_screen.y, + scaled_w, + scaled_h + }; + + if (!isRectInViewport(camera, dest_rect)) { // 视口裁剪:如果精灵超出视口,则不绘制 + // spdlog::info("精灵超出视口范围,ID: {}", sprite.getTextureId()); + return; + } + + // 执行绘制(默认旋转中心为精灵的中心点) + if (!SDL_RenderTextureRotated(renderer_, texture, &src_rect.value(), &dest_rect, angle, NULL, sprite.isFlipped() ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE)) { + spdlog::error("渲染旋转纹理失败(ID: {}):{}", sprite.getTextureId(), SDL_GetError()); + } +} + +void Renderer::drawParallax(const Camera &camera, const Sprite &sprite, const glm::vec2 &position, const glm::vec2 &scroll_factor, const glm::bvec2 &repeat, const glm::vec2 &scale) +{ + auto texture = resource_manager_->getTexture(sprite.getTextureId()); + if (!texture) { + spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId()); + return; + } + + auto src_rect = getSpriteSrcRect(sprite); + if (!src_rect.has_value()) { + spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId()); + return; + } + + // 应用相机变换 + glm::vec2 position_screen = camera.worldToScreenWithParallax(position, scroll_factor); + + // 计算缩放后的纹理尺寸 + float scaled_tex_w = src_rect.value().w * scale.x; + float scaled_tex_h = src_rect.value().h * scale.y; + + glm::vec2 start, stop; + glm::vec2 viewport_size = camera.getViewportSize(); + + if (repeat.x) { + // 使用 glm::mod 进行浮点数取模 + start.x = glm::mod(position_screen.x, scaled_tex_w) - scaled_tex_w; + stop.x = viewport_size.x; + } else { + start.x = position_screen.x; + stop.x = glm::min(position_screen.x + scaled_tex_w, viewport_size.x); // 结束点是一个纹理宽度之后,但不超过视口宽度 + } + if (repeat.y) { + start.y = glm::mod(position_screen.y, scaled_tex_h) - scaled_tex_h; + stop.y = viewport_size.y; + } else { + start.y = position_screen.y; + stop.y = glm::min(position_screen.y + scaled_tex_h, viewport_size.y); // 结束点是一个纹理高度之后,但不超过视口高度 + } + + for (float y = start.y; y < stop.y; y += scaled_tex_h) { + for (float x = start.x; x < stop.x; x += scaled_tex_w) { + SDL_FRect dest_rect = {x, y, scaled_tex_w, scaled_tex_h}; + if (!SDL_RenderTexture(renderer_, texture, nullptr, &dest_rect)) { + spdlog::error("渲染视差纹理失败(ID: {}):{}", sprite.getTextureId(), SDL_GetError()); + return; + } + } + } +} + +void Renderer::drawUISprite(const Sprite& sprite, const glm::vec2& position, const std::optional& size) { + auto texture = resource_manager_->getTexture(sprite.getTextureId()); + if (!texture) { + spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId()); + return; + } + + auto src_rect = getSpriteSrcRect(sprite); + if (!src_rect.has_value()) { + spdlog::error("无法获取精灵的源矩形,ID: {}", sprite.getTextureId()); + return; + } + + SDL_FRect dest_rect = {position.x, position.y, 0, 0}; // 首先确定目标矩形的左上角坐标 + if (size.has_value()) { // 如果提供了尺寸,则使用提供的尺寸 + dest_rect.w = size.value().x; + dest_rect.h = size.value().y; + } else { // 如果未提供尺寸,则使用纹理的原始尺寸 + dest_rect.w = src_rect.value().w; + dest_rect.h = src_rect.value().h; + } + + // 执行绘制(未考虑UI旋转) + if (!SDL_RenderTextureRotated(renderer_, texture, &src_rect.value(), &dest_rect, 0.0, nullptr, sprite.isFlipped() ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE)) { + spdlog::error("渲染 UI Sprite 失败 (ID: {}): {}", sprite.getTextureId(), SDL_GetError()); + } +} + +void Renderer::setDrawColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + if (!SDL_SetRenderDrawColor(renderer_, r, g, b, a)) { + spdlog::error("设置渲染绘制颜色失败:{}", SDL_GetError()); + } +} + +void Renderer::setDrawColorFloat(float r, float g, float b, float a) +{ + if (!SDL_SetRenderDrawColorFloat(renderer_, r, g, b, a)) { + spdlog::error("设置渲染绘制颜色失败:{}", SDL_GetError()); + } +} + +void Renderer::clearScreen() { + if (!SDL_RenderClear(renderer_)) { + spdlog::error("清除渲染器失败:{}", SDL_GetError()); + } +} + +void Renderer::present() +{ + SDL_RenderPresent(renderer_); +} + +std::optional Renderer::getSpriteSrcRect(const Sprite &sprite) +{ + SDL_Texture* texture = resource_manager_->getTexture(sprite.getTextureId()); + if (!texture) { + spdlog::error("无法为 ID {} 获取纹理。", sprite.getTextureId()); + return std::nullopt; + } + + auto src_rect = sprite.getSourceRect(); + if (src_rect.has_value()) { // 如果Sprite中存在指定rect,则判断尺寸是否有效 + if (src_rect.value().w <= 0 || src_rect.value().h <= 0) { + spdlog::error("源矩形尺寸无效,ID: {}", sprite.getTextureId()); + return std::nullopt; + } + return src_rect; + } else { // 否则获取纹理尺寸并返回整个纹理大小 + SDL_FRect result = {0, 0, 0, 0}; + if (!SDL_GetTextureSize(texture, &result.w, &result.h)) { + spdlog::error("无法获取纹理尺寸,ID: {}", sprite.getTextureId()); + return std::nullopt; + } + return result; + } +} + +bool Renderer::isRectInViewport(const Camera& camera, const SDL_FRect &rect) +{ + glm::vec2 viewport_size = camera.getViewportSize(); + return rect.x + rect.w >= 0 && rect.x <= viewport_size.x && // 相当于 AABB碰撞检测 + rect.y + rect.h >= 0 && rect.y <= viewport_size.y; +} + +} // namespace engine::render diff --git a/src/engine/render/renderer.h b/src/engine/render/renderer.h new file mode 100644 index 0000000..ce434c9 --- /dev/null +++ b/src/engine/render/renderer.h @@ -0,0 +1,91 @@ +#pragma once +#include "sprite.h" +#include +#include // For std::optional +#include + +struct SDL_Renderer; +struct SDL_FRect; + +namespace engine::resource { + class ResourceManager; +} + +namespace engine::render { +class Camera; + +/** + * @brief 封装 SDL3 渲染操作 + * + * 包装 SDL_Renderer 并提供清除屏幕、绘制精灵和呈现最终图像的方法。 + * 在构造时初始化。依赖于一个有效的 SDL_Renderer 和 ResourceManager。 + * 构造失败会抛出异常。 + */ +class Renderer final{ +private: + SDL_Renderer* renderer_ = nullptr; ///< @brief 指向 SDL_Renderer 的非拥有指针 + engine::resource::ResourceManager* resource_manager_ = nullptr; ///< @brief 指向 ResourceManager 的非拥有指针 + +public: + /** + * @brief 构造函数 + * + * @param sdl_renderer 指向有效的 SDL_Renderer 的指针。不能为空。 + * @param resource_manager 指向有效的 ResourceManager 的指针。不能为空。 + * @throws std::runtime_error 如果任一指针为 nullptr。 + */ + Renderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager); + + /** + * @brief 绘制一个精灵 + * + * @param sprite 包含纹理ID、源矩形和翻转状态的 Sprite 对象。 + * @param position 世界坐标中的左上角位置。 + * @param scale 缩放因子。 + * @param angle 旋转角度(度)。 + */ + void drawSprite(const Camera& camera, const Sprite& sprite, const glm::vec2& position, + const glm::vec2& scale = {1.0f, 1.0f}, double angle = 0.0f); + + /** + * @brief 绘制视差滚动背景 + * + * @param sprite 包含纹理ID、源矩形和翻转状态的 Sprite 对象。 + * @param position 世界坐标。 + * @param scroll_factor 滚动因子。 + * @param scale 缩放因子。 + */ + void drawParallax(const Camera& camera, const Sprite& sprite, const glm::vec2& position, + const glm::vec2& scroll_factor, const glm::bvec2& repeat = {true, true}, const glm::vec2& scale = {1.0f, 1.0f}); + + /** + * @brief 在屏幕坐标中直接渲染一个用于UI的Sprite对象。 + * + * @param sprite 包含纹理ID、源矩形和翻转状态的Sprite对象。 + * @param position 屏幕坐标中的左上角位置。 + * @param size 可选:目标矩形的大小。如果为 std::nullopt,则使用Sprite的原始大小。 + */ + void drawUISprite(const Sprite& sprite, const glm::vec2& position, const std::optional& size = std::nullopt); + + + void present(); ///< @brief 更新屏幕,包装 SDL_RenderPresent 函数 + void clearScreen(); ///< @brief 清屏,包装 SDL_RenderClear 函数 + + void setDrawColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255); ///< @brief 设置绘制颜色,包装 SDL_SetRenderDrawColor 函数,使用 Uint8 类型 + void setDrawColorFloat(float r, float g, float b, float a = 1.0f); ///< @brief 设置绘制颜色,包装 SDL_SetRenderDrawColorFloat 函数,使用 float 类型 + + SDL_Renderer* getSDLRenderer() const { return renderer_; } ///< @brief 获取底层的 SDL_Renderer 指针 + + // 禁用拷贝和移动语义 + Renderer(const Renderer&) = delete; + Renderer& operator=(const Renderer&) = delete; + Renderer(Renderer&&) = delete; + Renderer& operator=(Renderer&&) = delete; + +private: + std::optional getSpriteSrcRect(const Sprite& sprite); ///< @brief 获取精灵的源矩形,用于具体绘制。出现错误则返回std::nullopt并跳过绘制 + bool isRectInViewport(const Camera& camera, const SDL_FRect& rect); ///< @brief 判断矩形是否在视口中,用于视口裁剪 + +}; + +} // namespace engine::render diff --git a/src/engine/render/sprite.h b/src/engine/render/sprite.h new file mode 100644 index 0000000..fe92cec --- /dev/null +++ b/src/engine/render/sprite.h @@ -0,0 +1,46 @@ +#pragma once +#include // 用于 SDL_FRect +#include // 用于 std::optional 表示可选的源矩形 +#include + +namespace engine::render { + +/** + * @brief 表示要绘制的视觉精灵的数据。 + * + * 包含纹理标识符、要绘制的纹理部分(源矩形)以及翻转状态。 + * 位置、缩放和旋转由外部(例如 SpriteComponent)标识。 + * 渲染工作由 Renderer 类完成。(传入Sprite作为参数) + */ +class Sprite final{ +private: + std::string texture_id_; ///< @brief 纹理资源的标识符 + std::optional source_rect_; ///< @brief 可选:要绘制的纹理部分 + bool is_flipped_ = false; ///< @brief 是否水平翻转 + +public: + /** + * @brief 构造一个精灵 + * + * @param texture_id 纹理资源的标识符。不应为空。 + * @param source_rect 可选的源矩形(SDL_FRect),定义要使用的纹理部分。如果为 std::nullopt,则使用整个纹理。 + * @param is_flipped 是否水平翻转 + */ + Sprite(const std::string& texture_id, const std::optional& source_rect = std::nullopt, bool is_flipped = false) + : texture_id_(texture_id), + source_rect_(source_rect), + is_flipped_(is_flipped) + {} + + // --- getters and setters --- + const std::string& getTextureId() const { return texture_id_; } ///< @brief 获取纹理 ID + const std::optional& getSourceRect() const { return source_rect_; } ///< @brief 获取源矩形 (如果使用整个纹理则为 std::nullopt) + bool isFlipped() const { return is_flipped_; } ///< @brief 获取是否水平翻转 + + void setTextureId(const std::string& texture_id) { texture_id_ = texture_id; } ///< @brief 设置纹理 ID + void setSourceRect(const std::optional& source_rect) { source_rect_ = source_rect; } ///< @brief 设置源矩形 (如果使用整个纹理则为 std::nullopt) + void setFlipped(bool flipped) { is_flipped_ = flipped; } ///< @brief 设置是否水平翻转 + +}; + +} // namespace engine::render diff --git a/src/engine/utils/math.h b/src/engine/utils/math.h new file mode 100644 index 0000000..ca42b7e --- /dev/null +++ b/src/engine/utils/math.h @@ -0,0 +1,12 @@ +#pragma once +#include + +namespace engine::utils { + +struct Rect +{ + glm::vec2 position; + glm::vec2 size; +}; + +} // namespace engine::utils \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ce76ce5..738b873 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include int main(int /* argc */, char* /* argv */[]) { - spdlog::set_level(spdlog::level::debug); + // spdlog::set_level(spdlog::level::debug); engine::core::GameApp app; app.run();