diff --git a/CMakeLists.txt b/CMakeLists.txt index 95e8648..99be4e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(${TARGET} src/engine/render/renderer.cpp src/engine/render/camera.cpp src/engine/render/animation.cpp + src/engine/render/text_renderer.cpp src/engine/input/input_manager.cpp src/engine/object/game_object.cpp src/engine/component/sprite_component.cpp diff --git a/assets/maps/level1.tmj b/assets/maps/level1.tmj index 3218b21..3b1a311 100644 --- a/assets/maps/level1.tmj +++ b/assets/maps/level1.tmj @@ -126,8 +126,8 @@ "type":"", "visible":true, "width":32, - "x":1330.17424242424, - "y":156.628787878788 + "x":78.17424242424, + "y":155.128787878788 }, { "gid":607, diff --git a/src/engine/core/context.cpp b/src/engine/core/context.cpp index 17fdf1e..12fcfc7 100644 --- a/src/engine/core/context.cpp +++ b/src/engine/core/context.cpp @@ -2,6 +2,7 @@ #include "../input/input_manager.h" #include "../render/renderer.h" #include "../render/camera.h" +#include "../render/text_renderer.h" #include "../resource/resource_manager.h" #include "../physics/physics_engine.h" #include "../audio/audio_player.h" @@ -12,12 +13,14 @@ namespace engine::core { Context::Context(engine::input::InputManager& input_manager, engine::render::Renderer& renderer, engine::render::Camera& camera, + engine::render::TextRenderer& text_renderer, engine::resource::ResourceManager& resource_manager, engine::physics::PhysicsEngine& physics_engine, engine::audio::AudioPlayer& audio_player) : input_manager_(input_manager), renderer_(renderer), camera_(camera), + text_renderer_(text_renderer), resource_manager_(resource_manager), physics_engine_(physics_engine), audio_player_(audio_player) diff --git a/src/engine/core/context.h b/src/engine/core/context.h index 16efbe8..31959a1 100644 --- a/src/engine/core/context.h +++ b/src/engine/core/context.h @@ -7,6 +7,7 @@ namespace engine::input { namespace engine::render { class Renderer; class Camera; + class TextRenderer; } namespace engine::resource { @@ -34,6 +35,7 @@ private: engine::input::InputManager& input_manager_; ///< @brief 输入管理器 engine::render::Renderer& renderer_; ///< @brief 渲染器 engine::render::Camera& camera_; ///< @brief 相机 + engine::render::TextRenderer& text_renderer_; ///< @brief 文本渲染器 engine::resource::ResourceManager& resource_manager_; ///< @brief 资源管理器 engine::physics::PhysicsEngine& physics_engine_; ///< @brief 物理引擎 engine::audio::AudioPlayer& audio_player_; ///< @brief 音频播放器 @@ -50,6 +52,7 @@ public: Context(engine::input::InputManager& input_manager, engine::render::Renderer& renderer, engine::render::Camera& camera, + engine::render::TextRenderer& text_renderer, engine::resource::ResourceManager& resource_manager, engine::physics::PhysicsEngine& physics_engine, engine::audio::AudioPlayer& audio_player); @@ -64,6 +67,7 @@ public: engine::input::InputManager& getInputManager() const { return input_manager_; } ///< @brief 获取输入管理器 engine::render::Renderer& getRenderer() const { return renderer_; } ///< @brief 获取渲染器 engine::render::Camera& getCamera() const { return camera_; } ///< @brief 获取相机 + engine::render::TextRenderer& getTextRenderer() const { return text_renderer_; } ///< @brief 获取文本渲染器 engine::resource::ResourceManager& getResourceManager() const { return resource_manager_; } ///< @brief 获取资源管理器 engine::physics::PhysicsEngine& getPhysicsEngine() const { return physics_engine_; } ///< @brief 获取物理引擎 engine::audio::AudioPlayer& getAudioPlayer() const { return audio_player_; } ///< @brief 获取音频播放器 diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index cd760bd..bdc9c60 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -6,6 +6,7 @@ #include "../audio/audio_player.h" #include "../render/renderer.h" #include "../render/camera.h" +#include "../render/text_renderer.h" #include "../input/input_manager.h" #include "../physics/physics_engine.h" #include "../scene/scene_manager.h" @@ -54,6 +55,7 @@ bool GameApp::init() { if (!initAudioPlayer()) return false; if (!initRenderer()) return false; if (!initCamera()) return false; + if (!initTextRenderer()) return false; if (!initInputManager()) return false; if (!initPhysicsEngine()) return false; @@ -214,6 +216,18 @@ bool GameApp::initCamera() { return true; } +bool GameApp::initTextRenderer() +{ + try { + text_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::initInputManager() { try { @@ -245,6 +259,7 @@ bool GameApp::initContext() context_ = std::make_unique(*input_manager_, *renderer_, *camera_, + *text_renderer_, *resource_manager_, *physics_engine_, *audio_player_); diff --git a/src/engine/core/game_app.h b/src/engine/core/game_app.h index 521b1c1..8b3c09d 100644 --- a/src/engine/core/game_app.h +++ b/src/engine/core/game_app.h @@ -12,6 +12,7 @@ class ResourceManager; namespace engine::render { class Renderer; class Camera; +class TextRenderer; } namespace engine::input { @@ -49,6 +50,7 @@ private: std::unique_ptr resource_manager_; std::unique_ptr renderer_; std::unique_ptr camera_; + std::unique_ptr text_renderer_; std::unique_ptr config_; std::unique_ptr input_manager_; std::unique_ptr context_; @@ -85,6 +87,7 @@ private: [[nodiscard]] bool initResourceManager(); [[nodiscard]] bool initAudioPlayer(); [[nodiscard]] bool initRenderer(); + [[nodiscard]] bool initTextRenderer(); [[nodiscard]] bool initCamera(); [[nodiscard]] bool initInputManager(); [[nodiscard]] bool initPhysicsEngine(); diff --git a/src/engine/render/text_renderer.cpp b/src/engine/render/text_renderer.cpp new file mode 100644 index 0000000..73a940d --- /dev/null +++ b/src/engine/render/text_renderer.cpp @@ -0,0 +1,113 @@ +#include "text_renderer.h" +#include "camera.h" +#include "../resource/resource_manager.h" +#include +#include +#include + +namespace engine::render { + +TextRenderer::TextRenderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager) + : sdl_renderer_(sdl_renderer), + resource_manager_(resource_manager) +{ + if (!sdl_renderer_ || !resource_manager_) { + throw std::runtime_error("TextRenderer 需要一个有效的 SDL_Renderer 和 ResourceManager。"); + } + // 初始化 SDL_ttf + if (!TTF_WasInit() && TTF_Init() == false) { + throw std::runtime_error("初始化 SDL_ttf 失败: " + std::string(SDL_GetError())); + } + + text_engine_ = TTF_CreateRendererTextEngine(sdl_renderer_); + if (!text_engine_) { + spdlog::error("创建 TTF_TextEngine 失败: {}", SDL_GetError()); + throw std::runtime_error("创建 TTF_TextEngine 失败。"); + } + spdlog::trace("TextRenderer 初始化成功."); +} + +TextRenderer::~TextRenderer() { + if (text_engine_) { + close(); + } +} + +void TextRenderer::close() +{ + if (text_engine_) { + TTF_DestroyRendererTextEngine(text_engine_); + text_engine_ = nullptr; + spdlog::trace("TTF_TextEngine 销毁。"); + } + TTF_Quit(); // 一定要确保在ResourceManager销毁之后调用 +} + +void TextRenderer::drawUIText(const std::string &text, const std::string &font_id, int font_size, + const glm::vec2 &position, const SDL_FColor &color) +{ + /* 构造函数已经保证了必要指针不会为空,这里不需要再检查 */ + TTF_Font* font = resource_manager_->getFont(font_id, font_size); + if (!font) { + spdlog::warn("drawUIText 获取字体失败: {} 大小 {}", font_id, font_size); + return; + } + + // 创建临时 TTF_Text 对象 (目前效率不高,未来可以考虑使用缓存优化) + TTF_Text* temp_text_object = TTF_CreateText(text_engine_, font, text.c_str(), 0); + if (!temp_text_object) { + spdlog::error("drawUIText 创建临时 TTF_Text 失败: {}", SDL_GetError()); + return; + } + + // 先渲染一次黑色文字模拟阴影 + TTF_SetTextColorFloat(temp_text_object, 0.0f, 0.0f, 0.0f, 1.0f); + if (!TTF_DrawRendererText(temp_text_object, position.x + 2, position.y + 2)) { + spdlog::error("drawUIText 绘制临时 TTF_Text 失败: {}", SDL_GetError()); + } + + // 然后正常绘制 + TTF_SetTextColorFloat(temp_text_object, color.r, color.g, color.b, color.a); + if (!TTF_DrawRendererText(temp_text_object, position.x, position.y)) { + spdlog::error("drawUIText 绘制临时 TTF_Text 失败: {}", SDL_GetError()); + } + + // 销毁临时 TTF_Text 对象 + TTF_DestroyText(temp_text_object); +} + +void TextRenderer::drawText(const Camera &camera, const std::string &text, const std::string &font_id, int font_size, + const glm::vec2 &position, const SDL_FColor &color) +{ + // 应用相机变换 + glm::vec2 position_screen = camera.worldToScreen(position); + + // 用新坐标调用drawUIText即可 + drawUIText(text, font_id, font_size, position_screen, color); +} + +glm::vec2 TextRenderer::getTextSize(const std::string& text, const std::string& font_id, int font_size) { + /* 构造函数已经保证了必要指针不会为空,这里不需要再检查 */ + TTF_Font* font = resource_manager_->getFont(font_id, font_size); + if (!font) { + spdlog::warn("getTextSize 获取字体失败: {} 大小 {}", font_id, font_size); + return glm::vec2(0.0f, 0.0f); + } + + // 创建临时 TTF_Text 对象 + TTF_Text* temp_text_object = TTF_CreateText(text_engine_, font, text.c_str(), 0); + if (!temp_text_object) { + spdlog::error("getTextSize 创建临时 TTF_Text 失败: {}", SDL_GetError()); + return glm::vec2(0.0f, 0.0f); + } + + int width, height; + TTF_GetTextSize(temp_text_object, &width, &height); + + // 销毁临时 TTF_Text 对象 + TTF_DestroyText(temp_text_object); + + return glm::vec2(static_cast(width), static_cast(height)); +} + +} // namespace engine::render \ No newline at end of file diff --git a/src/engine/render/text_renderer.h b/src/engine/render/text_renderer.h new file mode 100644 index 0000000..b384b3a --- /dev/null +++ b/src/engine/render/text_renderer.h @@ -0,0 +1,84 @@ +#pragma once +#include +#include +#include + +struct TTF_TextEngine; + +namespace engine::resource { + class ResourceManager; +} + +namespace engine::render { + class Camera; +/** + * @brief 使用 SDL_ttf 和 TTF_Text 对象处理文本渲染。 + * + * 封装 TTF_TextEngine 并提供创建和绘制 TTF_Text 对象的方法, + * 管理字体加载和颜色设置。 + */ +class TextRenderer final { +private: + SDL_Renderer* sdl_renderer_ = nullptr; ///< @brief 持有渲染器的非拥有指针 + engine::resource::ResourceManager* resource_manager_ = nullptr; ///< @brief 持有资源管理器的非拥有指针 + + TTF_TextEngine* text_engine_ = nullptr; ///< @brief 使用SDL3引入的 TTF_TextEngine 来进行绘制 + +public: + /** + * @brief 构造 TextRenderer。 + * + * @param sdl_renderer 有效的 SDL_Renderer 指针。 + * @param resource_manager 有效的 ResourceManager 指针(用于字体加载)。 + * @throws std::runtime_error 如果初始化失败。 + */ + TextRenderer(SDL_Renderer* sdl_renderer, engine::resource::ResourceManager* resource_manager); + + ~TextRenderer(); ///< @brief 析构函数,按需调用close()。 + + void close(); ///< @brief 显式关闭。清理 TTF_TextEngine 并关闭SDL_ttf。 + + /** + * @brief 绘制UI上的字符串。 + * + * @param text UTF-8 字符串内容。 + * @param font_id 字体 ID。 + * @param font_size 字体大小。 + * @param position 左上角屏幕位置。 + * @param color 文本颜色。(默认为白色) + */ + void drawUIText(const std::string& text, const std::string& font_id, int font_size, + const glm::vec2& position, const SDL_FColor& color = {1.0f, 1.0f, 1.0f, 1.0f}); + + /** + * @brief 绘制地图上的字符串。 + * + * @param camera 相机 + * @param text UTF-8 字符串内容。 + * @param font_id 字体 ID。 + * @param font_size 字体大小。 + * @param position 左上角屏幕位置。 + * @param color 文本颜色。 + */ + void drawText(const Camera& camera, const std::string& text, const std::string& font_id, int font_size, + const glm::vec2& position, const SDL_FColor& color = {1.0f, 1.0f, 1.0f, 1.0f}); + + /** + * @brief 获取文本的尺寸。 + * + * @param text 要测量的文本。 + * @param font_id 字体 ID。 + * @param font_size 字体大小。 + * @return 文本的尺寸。 + */ + glm::vec2 getTextSize(const std::string& text, const std::string& font_id, int font_size); + + // 禁用拷贝和移动语义 + TextRenderer(const TextRenderer&) = delete; + TextRenderer& operator=(const TextRenderer&) = delete; + TextRenderer(TextRenderer&&) = delete; + TextRenderer& operator=(TextRenderer&&) = delete; + +}; // class TextRenderer + +} // namespace engine::render \ No newline at end of file diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index 5714068..6eb8242 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -15,6 +15,7 @@ #include "../../engine/input/input_manager.h" #include "../../engine/render/camera.h" #include "../../engine/render/animation.h" +#include "../../engine/render/text_renderer.h" #include "../../engine/audio/audio_player.h" #include "../component/ai_component.h" #include "../component/ai/patrol_behavior.h" @@ -78,11 +79,11 @@ void GameScene::update(float delta_time) { void GameScene::render() { Scene::render(); + testTextRenderer(); } void GameScene::handleInput() { Scene::handleInput(); - testSaveAndLoad(); } void GameScene::clean() { @@ -351,17 +352,13 @@ void GameScene::createEffect(const glm::vec2& center_pos, const std::string &tag spdlog::debug("创建特效: {}", tag); } -void GameScene::testSaveAndLoad() +void GameScene::testTextRenderer() { - auto input_manager = context_.getInputManager(); - if (input_manager.isActionPressed("attack")) { - game_session_data_->saveToFile("assets/save.json"); - } - if (input_manager.isActionPressed("pause")) { - game_session_data_->loadFromFile("assets/save.json"); - spdlog::info("当前生命值: {}", game_session_data_->getCurrentHealth()); - spdlog::info("当前得分: {}", game_session_data_->getCurrentScore()); - } + auto& text_renderer = context_.getTextRenderer(); + const auto& camera = context_.getCamera(); + // UI和地图各渲染一次,测试是否正常 + text_renderer.drawUIText("UI Text", "assets/fonts/VonwaonBitmap-16px.ttf", 32, glm::vec2(100.0f), {0, 1.0f, 0, 1.0f}); + text_renderer.drawText(camera, "Map Text", "assets/fonts/VonwaonBitmap-16px.ttf", 32, glm::vec2(200.0f)); } } // namespace game::scene \ No newline at end of file diff --git a/src/game/scene/game_scene.h b/src/game/scene/game_scene.h index 0f3dd13..ce2aae3 100644 --- a/src/game/scene/game_scene.h +++ b/src/game/scene/game_scene.h @@ -57,7 +57,7 @@ private: void createEffect(const glm::vec2& center_pos, const std::string& tag); // 测试函数 - void testSaveAndLoad(); + void testTextRenderer(); }; } // namespace game::scene \ No newline at end of file