完成文字渲染引擎

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent a03a5cdf74
commit f16cac3097
10 changed files with 234 additions and 14 deletions
+3
View File
@@ -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)
+4
View File
@@ -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 获取音频播放器
+15
View File
@@ -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<engine::render::TextRenderer>(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<engine::core::Context>(*input_manager_,
*renderer_,
*camera_,
*text_renderer_,
*resource_manager_,
*physics_engine_,
*audio_player_);
+3
View File
@@ -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<engine::resource::ResourceManager> resource_manager_;
std::unique_ptr<engine::render::Renderer> renderer_;
std::unique_ptr<engine::render::Camera> camera_;
std::unique_ptr<engine::render::TextRenderer> text_renderer_;
std::unique_ptr<engine::core::Config> config_;
std::unique_ptr<engine::input::InputManager> input_manager_;
std::unique_ptr<engine::core::Context> 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();
+113
View File
@@ -0,0 +1,113 @@
#include "text_renderer.h"
#include "camera.h"
#include "../resource/resource_manager.h"
#include <SDL3_ttf/SDL_ttf.h>
#include <spdlog/spdlog.h>
#include <stdexcept>
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<float>(width), static_cast<float>(height));
}
} // namespace engine::render
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <SDL3/SDL_render.h>
#include <string>
#include <glm/vec2.hpp>
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
+8 -11
View File
@@ -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
+1 -1
View File
@@ -57,7 +57,7 @@ private:
void createEffect(const glm::vec2& center_pos, const std::string& tag);
// 测试函数
void testSaveAndLoad();
void testTextRenderer();
};
} // namespace game::scene