完成文字渲染引擎
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 获取音频播放器
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
void createEffect(const glm::vec2& center_pos, const std::string& tag);
|
||||
|
||||
// 测试函数
|
||||
void testSaveAndLoad();
|
||||
void testTextRenderer();
|
||||
};
|
||||
|
||||
} // namespace game::scene
|
||||
Reference in New Issue
Block a user