From 1f10ce999cca41222e428405641401d93a6b7097 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Wed, 11 Feb 2026 15:30:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor(audio):=20=E7=A4=BA=E4=BE=8B=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E9=87=8D=E6=9E=84=E9=9F=B3=E9=A2=91=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E4=B8=BA=E5=8D=95=E4=BE=8B=E7=AE=A1=E7=90=86=E5=99=A8=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(text): 为Text类添加格式化文本创建和设置功能 refactor(ui): 将碰撞演示和空间索引演示的UI改为Text组件实现 chore: 移除不再使用的音频上下文和菜单按钮相关代码 --- Extra2D/include/extra2d/ui/text.h | 44 +++++ Extra2D/src/ui/text.cpp | 25 +++ examples/collision_demo/main.cpp | 113 ++++++------ examples/push_box/PlayScene.cpp | 26 +-- examples/push_box/StartScene.cpp | 11 +- examples/push_box/audio_context.cpp | 17 -- examples/push_box/audio_context.h | 12 -- examples/push_box/audio_controller.cpp | 62 ------- examples/push_box/audio_controller.h | 29 --- examples/push_box/audio_manager.cpp | 112 ++++++++++++ examples/push_box/audio_manager.h | 47 +++++ examples/push_box/main.cpp | 4 + examples/push_box/menu_button.cpp | 40 ----- examples/push_box/menu_button.h | 21 --- examples/spatial_index_demo/main.cpp | 233 ++++++++++++++++--------- 15 files changed, 446 insertions(+), 350 deletions(-) delete mode 100644 examples/push_box/audio_context.cpp delete mode 100644 examples/push_box/audio_context.h delete mode 100644 examples/push_box/audio_controller.cpp delete mode 100644 examples/push_box/audio_controller.h create mode 100644 examples/push_box/audio_manager.cpp create mode 100644 examples/push_box/audio_manager.h delete mode 100644 examples/push_box/menu_button.cpp delete mode 100644 examples/push_box/menu_button.h diff --git a/Extra2D/include/extra2d/ui/text.h b/Extra2D/include/extra2d/ui/text.h index cc9c01d..d2662fa 100644 --- a/Extra2D/include/extra2d/ui/text.h +++ b/Extra2D/include/extra2d/ui/text.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include namespace extra2d { @@ -29,6 +32,15 @@ public: static Ptr create(const String &text); static Ptr create(const String &text, Ptr font); + // ------------------------------------------------------------------------ + // 格式化创建方法(类似 printf) + // 使用示例: + // auto text = Text::createFormat("FPS: %d", 60); + // auto text = Text::createFormat(font, "得分: %d", score); + // ------------------------------------------------------------------------ + static Ptr createFormat(const char *fmt, ...); + static Ptr createFormat(Ptr font, const char *fmt, ...); + // ------------------------------------------------------------------------ // 链式调用构建器方法 // ------------------------------------------------------------------------ @@ -48,6 +60,38 @@ public: void setText(const String &text); const String &getText() const { return text_; } + // ------------------------------------------------------------------------ + // 格式化文本设置(类似 printf) + // 使用示例: + // text->setFormat("FPS: %d", 60); + // text->setFormat("位置: (%.1f, %.1f)", x, y); + // text->setFormat("生命值: %d/100", hp); + // ------------------------------------------------------------------------ + void setFormat(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + text_ = buffer; + sizeDirty_ = true; + updateSpatialIndex(); + } + + // 链式调用的格式化方法 + // 注意:由于C++可变参数的限制,链式调用需要单独设置格式和值 + Text *withFormattedText(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + text_ = buffer; + sizeDirty_ = true; + updateSpatialIndex(); + return this; + } + // ------------------------------------------------------------------------ // 字体 // ------------------------------------------------------------------------ diff --git a/Extra2D/src/ui/text.cpp b/Extra2D/src/ui/text.cpp index 743f09b..863eff2 100644 --- a/Extra2D/src/ui/text.cpp +++ b/Extra2D/src/ui/text.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include namespace extra2d { @@ -203,6 +205,29 @@ Ptr Text::create(const String &text, Ptr font) { return t; } +// ------------------------------------------------------------------------ +// 格式化创建方法 +// ------------------------------------------------------------------------ +Ptr Text::createFormat(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + return makePtr(buffer); +} + +Ptr Text::createFormat(Ptr font, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + auto t = makePtr(buffer); + t->setFont(font); + return t; +} + Rect Text::getBoundingBox() const { if (!font_ || text_.empty()) { return Rect(); diff --git a/examples/collision_demo/main.cpp b/examples/collision_demo/main.cpp index 0afbb79..9b0c105 100644 --- a/examples/collision_demo/main.cpp +++ b/examples/collision_demo/main.cpp @@ -1,7 +1,5 @@ #include #include -#include - using namespace extra2d; @@ -73,7 +71,7 @@ public: centerBox_->setPosition(Vec2(centerX, centerY)); addChild(centerBox_); - // 加载字体 + // 加载字体并创建UI loadFonts(); E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1); @@ -102,6 +100,9 @@ public: // 执行碰撞检测 performCollisionDetection(); + // 更新UI文本 + updateUI(); + // 检查退出按键 auto &input = Application::instance().input(); if (input.isButtonPressed(GamepadButton::Start)) { @@ -110,28 +111,60 @@ public: } } - void onRender(RenderBackend &renderer) override { - Scene::onRender(renderer); - - // 绘制说明文字 - drawUI(renderer); - } - private: /** - * @brief 加载字体资源 + * @brief 加载字体资源并创建UI文本 */ void loadFonts() { auto &resources = Application::instance().resources(); titleFont_ = resources.loadFont("assets/font.ttf", 60, true); infoFont_ = resources.loadFont("assets/font.ttf", 28, true); - if (!titleFont_) { - E2D_LOG_WARN("无法加载标题字体"); - } - if (!infoFont_) { - E2D_LOG_WARN("无法加载信息字体"); - } + // 创建标题文本 + titleText_ = Text::create("碰撞检测演示", titleFont_); + titleText_->setPosition(50.0f, 30.0f); + titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); + addChild(titleText_); + + // 创建说明文本 + descText_ = Text::create("蓝色方块旋转并检测碰撞", infoFont_); + descText_->setPosition(50.0f, 80.0f); + descText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(descText_); + + collideHintText_ = Text::create("红色 = 检测到碰撞", infoFont_); + collideHintText_->setPosition(50.0f, 105.0f); + collideHintText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f)); + addChild(collideHintText_); + + // 创建动态统计文本 + collisionText_ = Text::create("", infoFont_); + collisionText_->setPosition(50.0f, 150.0f); + collisionText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f)); + addChild(collisionText_); + + fpsText_ = Text::create("", infoFont_); + fpsText_->setPosition(50.0f, 175.0f); + fpsText_->setTextColor(Color(0.8f, 1.0f, 0.8f, 1.0f)); + addChild(fpsText_); + + // 创建退出提示文本 + float screenHeight = static_cast(Application::instance().getConfig().height); + exitHintText_ = Text::create("按 + 键退出", infoFont_); + exitHintText_->setPosition(50.0f, screenHeight - 50.0f); + exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(exitHintText_); + } + + /** + * @brief 更新UI文本 + */ + void updateUI() { + auto &app = Application::instance(); + + // 使用 setFormat 更新动态文本 + collisionText_->setFormat("碰撞数: %zu", collisionCount_); + fpsText_->setFormat("FPS: %u", app.fps()); } /** @@ -182,44 +215,6 @@ private: } } - /** - * @brief 绘制UI界面 - */ - void drawUI(RenderBackend &renderer) { - if (!titleFont_ || !infoFont_) - return; - - auto &app = Application::instance(); - - // 绘制标题 - renderer.drawText(*titleFont_, "碰撞检测演示", Vec2(50.0f, 30.0f), - Color(1.0f, 1.0f, 1.0f, 1.0f)); - - // 绘制说明文字 - renderer.drawText(*infoFont_, "蓝色方块旋转并检测碰撞", Vec2(50.0f, 80.0f), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - renderer.drawText(*infoFont_, "红色 = 检测到碰撞", Vec2(50.0f, 105.0f), - Color(1.0f, 0.5f, 0.5f, 1.0f)); - - // 绘制碰撞统计 - std::stringstream ss; - ss << "碰撞数: " << collisionCount_; - renderer.drawText(*infoFont_, ss.str(), Vec2(50.0f, 150.0f), - Color(1.0f, 1.0f, 0.5f, 1.0f)); - - // 绘制 FPS - ss.str(""); - ss << "FPS: " << app.fps(); - renderer.drawText(*infoFont_, ss.str(), Vec2(50.0f, 175.0f), - Color(0.8f, 1.0f, 0.8f, 1.0f)); - - // 绘制操作提示 - float screenHeight = static_cast(app.getConfig().height); - renderer.drawText(*infoFont_, "按 + 键退出", - Vec2(50.0f, screenHeight - 50.0f), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - } - Ptr centerBox_; std::vector> boxes_; float rotationAngle_ = 0.0f; @@ -229,6 +224,14 @@ private: // 字体资源 Ptr titleFont_; Ptr infoFont_; + + // UI 文本组件 + Ptr titleText_; + Ptr descText_; + Ptr collideHintText_; + Ptr collisionText_; + Ptr fpsText_; + Ptr exitHintText_; }; // ============================================================================ diff --git a/examples/push_box/PlayScene.cpp b/examples/push_box/PlayScene.cpp index fcd3af7..098999d 100644 --- a/examples/push_box/PlayScene.cpp +++ b/examples/push_box/PlayScene.cpp @@ -1,7 +1,6 @@ #include "PlayScene.h" -#include "audio_context.h" -#include "audio_controller.h" +#include "audio_manager.h" #include "storage.h" #include "StartScene.h" #include "SuccessScene.h" @@ -93,11 +92,6 @@ PlayScene::PlayScene(int level) { mapLayer_->setPosition(0.0f, 0.0f); addChild(mapLayer_); - auto audioNode = AudioController::create(); - audioNode->setName("AudioController"); - addChild(audioNode); - setAudioController(audioNode); - setLevel(level); } @@ -136,12 +130,10 @@ void PlayScene::onUpdate(float dt) { return; } - // X 键直接切换音效 + // X键直接切换音效 if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) { g_SoundOpen = !g_SoundOpen; - if (auto audio = getAudioController()) { - audio->setEnabled(g_SoundOpen); - } + AudioManager::instance().setEnabled(g_SoundOpen); updateSoundIcon(); return; } @@ -189,9 +181,7 @@ void PlayScene::executeMenuItem() { break; case 1: // 切换音效 g_SoundOpen = !g_SoundOpen; - if (auto audio = getAudioController()) { - audio->setEnabled(g_SoundOpen); - } + AudioManager::instance().setEnabled(g_SoundOpen); updateSoundIcon(); break; } @@ -329,9 +319,7 @@ void PlayScene::move(int dx, int dy, int direct) { g_Pushing = false; map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; map_.value[targetY][targetX].type = TYPE::Man; - if (auto audio = getAudioController()) { - audio->playManMove(); - } + AudioManager::instance().playManMove(); } else if (map_.value[targetY][targetX].type == TYPE::Box) { g_Pushing = true; @@ -370,9 +358,7 @@ void PlayScene::move(int dx, int dy, int direct) { map_.value[targetY][targetX].type = TYPE::Man; map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; - if (auto audio = getAudioController()) { - audio->playBoxMove(); - } + AudioManager::instance().playBoxMove(); } else { return; } diff --git a/examples/push_box/StartScene.cpp b/examples/push_box/StartScene.cpp index 57ef6be..36ec876 100644 --- a/examples/push_box/StartScene.cpp +++ b/examples/push_box/StartScene.cpp @@ -1,7 +1,6 @@ #include "StartScene.h" -#include "audio_context.h" -#include "audio_controller.h" +#include "audio_manager.h" #include "data.h" #include "PlayScene.h" #include @@ -28,10 +27,6 @@ void StartScene::onEnter() { setBackgroundColor(extra2d::Colors::Black); if (getChildren().empty()) { - auto audioNode = AudioController::create(); - audioNode->setName("audio_controller"); - addChild(audioNode); - setAudioController(audioNode); float screenW = static_cast(app.getConfig().width); float screenH = static_cast(app.getConfig().height); @@ -138,9 +133,7 @@ void StartScene::onUpdate(float dt) { // X键切换音效 if (input.isButtonPressed(extra2d::GamepadButton::X)) { g_SoundOpen = !g_SoundOpen; - if (auto audio = getAudioController()) { - audio->setEnabled(g_SoundOpen); - } + AudioManager::instance().setEnabled(g_SoundOpen); updateSoundIcon(); } } diff --git a/examples/push_box/audio_context.cpp b/examples/push_box/audio_context.cpp deleted file mode 100644 index 0b16dab..0000000 --- a/examples/push_box/audio_context.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "audio_context.h" - -#include "audio_controller.h" - -namespace pushbox { - -static extra2d::WeakPtr g_audioController; - -void setAudioController(const extra2d::Ptr& controller) { - g_audioController = controller; -} - -extra2d::Ptr getAudioController() { - return g_audioController.lock(); -} - -} // namespace pushbox diff --git a/examples/push_box/audio_context.h b/examples/push_box/audio_context.h deleted file mode 100644 index fab8de8..0000000 --- a/examples/push_box/audio_context.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -namespace pushbox { - -class AudioController; - -void setAudioController(const extra2d::Ptr& controller); -extra2d::Ptr getAudioController(); - -} // namespace pushbox diff --git a/examples/push_box/audio_controller.cpp b/examples/push_box/audio_controller.cpp deleted file mode 100644 index 6564a8e..0000000 --- a/examples/push_box/audio_controller.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "audio_controller.h" - -#include "storage.h" - -namespace pushbox { - -extra2d::Ptr AudioController::create() { - return extra2d::makePtr(); -} - -void AudioController::onEnter() { - Node::onEnter(); - - if (!loaded_) { - auto& resources = extra2d::Application::instance().resources(); - - background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav"); - manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav"); - boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav"); - - if (background_) { - background_->setLooping(true); - background_->play(); - } - - loaded_ = true; - } - - setEnabled(g_SoundOpen); -} - -void AudioController::setEnabled(bool enabled) { - enabled_ = enabled; - g_SoundOpen = enabled; - saveSoundOpen(enabled); - - if (!background_) { - return; - } - - if (enabled_) { - background_->resume(); - } else { - background_->pause(); - } -} - -void AudioController::playManMove() { - if (!enabled_ || !manMove_) { - return; - } - manMove_->play(); -} - -void AudioController::playBoxMove() { - if (!enabled_ || !boxMove_) { - return; - } - boxMove_->play(); -} - -} // namespace pushbox diff --git a/examples/push_box/audio_controller.h b/examples/push_box/audio_controller.h deleted file mode 100644 index c73ee3e..0000000 --- a/examples/push_box/audio_controller.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "data.h" -#include - -namespace pushbox { - -class AudioController : public extra2d::Node { -public: - static extra2d::Ptr create(); - - void onEnter() override; - - void setEnabled(bool enabled); - bool isEnabled() const { return enabled_; } - - void playManMove(); - void playBoxMove(); - -private: - bool loaded_ = false; - bool enabled_ = true; - - extra2d::Ptr background_; - extra2d::Ptr manMove_; - extra2d::Ptr boxMove_; -}; - -} // namespace pushbox diff --git a/examples/push_box/audio_manager.cpp b/examples/push_box/audio_manager.cpp new file mode 100644 index 0000000..92a8667 --- /dev/null +++ b/examples/push_box/audio_manager.cpp @@ -0,0 +1,112 @@ +#include "audio_manager.h" + +#include "storage.h" + +namespace pushbox { + +// ============================================================================ +// 单例实现 +// ============================================================================ +AudioManager& AudioManager::instance() { + static AudioManager instance; + return instance; +} + +// ============================================================================ +// 初始化音频资源 +// ============================================================================ +void AudioManager::init() { + if (initialized_) { + return; + } + + auto& resources = extra2d::Application::instance().resources(); + + // 加载音效资源 + background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav"); + manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav"); + boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav"); + + // 设置背景音乐循环播放 + if (background_) { + background_->setLooping(true); + } + + // 从存储中读取音效设置 + enabled_ = g_SoundOpen; + + initialized_ = true; + + // 如果音效开启,播放背景音乐 + if (enabled_ && background_) { + background_->play(); + } +} + +// ============================================================================ +// 启用/禁用音效 +// ============================================================================ +void AudioManager::setEnabled(bool enabled) { + enabled_ = enabled; + g_SoundOpen = enabled; + saveSoundOpen(enabled); + + if (!background_) { + return; + } + + if (enabled_) { + background_->resume(); + } else { + background_->pause(); + } +} + +// ============================================================================ +// 播放角色移动音效 +// ============================================================================ +void AudioManager::playManMove() { + if (!enabled_ || !manMove_) { + return; + } + manMove_->play(); +} + +// ============================================================================ +// 播放箱子移动音效 +// ============================================================================ +void AudioManager::playBoxMove() { + if (!enabled_ || !boxMove_) { + return; + } + boxMove_->play(); +} + +// ============================================================================ +// 背景音乐控制 +// ============================================================================ +void AudioManager::playBackground() { + if (background_) { + background_->play(); + } +} + +void AudioManager::pauseBackground() { + if (background_) { + background_->pause(); + } +} + +void AudioManager::resumeBackground() { + if (background_) { + background_->resume(); + } +} + +void AudioManager::stopBackground() { + if (background_) { + background_->stop(); + } +} + +} // namespace pushbox diff --git a/examples/push_box/audio_manager.h b/examples/push_box/audio_manager.h new file mode 100644 index 0000000..d9c6e19 --- /dev/null +++ b/examples/push_box/audio_manager.h @@ -0,0 +1,47 @@ +#pragma once + +#include "data.h" +#include + +namespace pushbox { + +// ============================================================================ +// 全局音频管理器 - 单例模式,不依赖场景生命周期 +// ============================================================================ +class AudioManager { +public: + // 获取单例实例 + static AudioManager& instance(); + + // 初始化音频资源 + void init(); + + // 启用/禁用音效 + void setEnabled(bool enabled); + bool isEnabled() const { return enabled_; } + + // 播放音效 + void playManMove(); + void playBoxMove(); + + // 背景音乐控制 + void playBackground(); + void pauseBackground(); + void resumeBackground(); + void stopBackground(); + +private: + AudioManager() = default; + ~AudioManager() = default; + AudioManager(const AudioManager&) = delete; + AudioManager& operator=(const AudioManager&) = delete; + + bool initialized_ = false; + bool enabled_ = true; + + extra2d::Ptr background_; + extra2d::Ptr manMove_; + extra2d::Ptr boxMove_; +}; + +} // namespace pushbox diff --git a/examples/push_box/main.cpp b/examples/push_box/main.cpp index 148cdbd..3c3f5d4 100644 --- a/examples/push_box/main.cpp +++ b/examples/push_box/main.cpp @@ -2,6 +2,7 @@ #include "StartScene.h" #include "data.h" #include "storage.h" +#include "audio_manager.h" using namespace extra2d; @@ -40,6 +41,9 @@ int main(int argc, char **argv) } pushbox::g_SoundOpen = pushbox::loadSoundOpen(true); + // 初始化全局音频管理器 + pushbox::AudioManager::instance().init(); + // 进入开始场景(主界面) app.enterScene(makePtr()); diff --git a/examples/push_box/menu_button.cpp b/examples/push_box/menu_button.cpp deleted file mode 100644 index 101c76f..0000000 --- a/examples/push_box/menu_button.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "menu_button.h" - -#include - -namespace pushbox { - -extra2d::Ptr MenuButton::create(extra2d::Ptr font, - const extra2d::String& text, - extra2d::Function onClick) { - auto btn = extra2d::makePtr(); - btn->setFont(font); - btn->setText(text); - btn->setPadding(extra2d::Vec2(0.0f, 0.0f)); - btn->setBackgroundColor(extra2d::Colors::Transparent, extra2d::Colors::Transparent, - extra2d::Colors::Transparent); - btn->setBorder(extra2d::Colors::Transparent, 0.0f); - btn->setTextColor(extra2d::Colors::Black); - - btn->onClick_ = std::move(onClick); - btn->setOnClick([wbtn = extra2d::WeakPtr(btn)]() { - if (auto self = wbtn.lock()) { - if (self->enabled_ && self->onClick_) { - self->onClick_(); - } - } - }); - - // 使用事件监听来处理悬停效果 - // Note: Extra2D 的 Button 类可能有不同的悬停检测机制 - // 这里简化处理,仅保留基本功能 - - return btn; -} - -void MenuButton::setEnabled(bool enabled) { - enabled_ = enabled; - setTextColor(enabled ? extra2d::Colors::Black : extra2d::Colors::LightGray); -} - -} // namespace pushbox diff --git a/examples/push_box/menu_button.h b/examples/push_box/menu_button.h deleted file mode 100644 index 119393b..0000000 --- a/examples/push_box/menu_button.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace pushbox { - -class MenuButton : public extra2d::Button { -public: - static extra2d::Ptr create(extra2d::Ptr font, - const extra2d::String& text, - extra2d::Function onClick); - - void setEnabled(bool enabled); - bool isEnabled() const { return enabled_; } - -private: - bool enabled_ = true; - extra2d::Function onClick_; -}; - -} // namespace pushbox diff --git a/examples/spatial_index_demo/main.cpp b/examples/spatial_index_demo/main.cpp index c444bed..2f95c09 100644 --- a/examples/spatial_index_demo/main.cpp +++ b/examples/spatial_index_demo/main.cpp @@ -178,30 +178,135 @@ public: } void onRender(RenderBackend &renderer) override { - Scene::onRender(renderer); - auto renderStart = std::chrono::high_resolution_clock::now(); - // 节点渲染由Scene自动处理 + Scene::onRender(renderer); auto renderEnd = std::chrono::high_resolution_clock::now(); stats_.renderTime = std::chrono::duration(renderEnd - renderStart) .count(); - // 绘制UI - drawUI(renderer); + // 更新UI文本内容 + updateUI(); + + // 绘制图例方块(Text组件会自动渲染) + drawLegend(renderer); } private: /** - * @brief 加载字体资源 + * @brief 加载字体资源并创建UI文本组件 */ void loadFonts() { auto &resources = Application::instance().resources(); titleFont_ = resources.loadFont("assets/font.ttf", 28, true); infoFont_ = resources.loadFont("assets/font.ttf", 16, true); + + // 创建标题文本 + titleText_ = Text::create("引擎空间索引演示", titleFont_); + titleText_->setPosition(30.0f, 20.0f); + titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); + addChild(titleText_); + + float x = 30.0f; + float y = 60.0f; + float lineHeight = 22.0f; + + // 创建统计信息文本 + nodeCountText_ = Text::create("", infoFont_); + nodeCountText_->setPosition(x, y); + nodeCountText_->setTextColor(Color(0.9f, 0.9f, 0.9f, 1.0f)); + addChild(nodeCountText_); + y += lineHeight; + + strategyText_ = Text::create("", infoFont_); + strategyText_->setPosition(x, y); + strategyText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f)); + addChild(strategyText_); + y += lineHeight; + + collisionText_ = Text::create("", infoFont_); + collisionText_->setPosition(x, y); + collisionText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f)); + addChild(collisionText_); + y += lineHeight; + + updateTimeText_ = Text::create("", infoFont_); + updateTimeText_->setPosition(x, y); + updateTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(updateTimeText_); + y += lineHeight; + + collisionTimeText_ = Text::create("", infoFont_); + collisionTimeText_->setPosition(x, y); + collisionTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(collisionTimeText_); + y += lineHeight; + + renderTimeText_ = Text::create("", infoFont_); + renderTimeText_->setPosition(x, y); + renderTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(renderTimeText_); + y += lineHeight; + + fpsText_ = Text::create("", infoFont_); + fpsText_->setPosition(x, y); + fpsText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f)); + addChild(fpsText_); + y += lineHeight * 1.5f; + + // 创建操作说明文本 + helpTitleText_ = Text::create("操作说明:", infoFont_); + helpTitleText_->setPosition(x, y); + helpTitleText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f)); + addChild(helpTitleText_); + y += lineHeight; + + helpAddText_ = Text::create("A键 - 添加100个节点", infoFont_); + helpAddText_->setPosition(x + 10, y); + helpAddText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(helpAddText_); + y += lineHeight; + + helpRemoveText_ = Text::create("B键 - 移除100个节点", infoFont_); + helpRemoveText_->setPosition(x + 10, y); + helpRemoveText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(helpRemoveText_); + y += lineHeight; + + helpToggleText_ = Text::create("X键 - 切换索引策略", infoFont_); + helpToggleText_->setPosition(x + 10, y); + helpToggleText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(helpToggleText_); + y += lineHeight; + + helpExitText_ = Text::create("+键 - 退出程序", infoFont_); + helpExitText_->setPosition(x + 10, y); + helpExitText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(helpExitText_); + + // 创建图例文本 + float legendX = screenWidth_ - 200.0f; + float legendY = 20.0f; + + legendTitleText_ = Text::create("图例:", infoFont_); + legendTitleText_->setPosition(legendX, legendY); + legendTitleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); + addChild(legendTitleText_); + legendY += 25.0f; + + legendNormalText_ = Text::create("- 正常", infoFont_); + legendNormalText_->setPosition(legendX + 20.0f, legendY); + legendNormalText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(legendNormalText_); + legendY += 25.0f; + + legendCollidingText_ = Text::create("- 碰撞中", infoFont_); + legendCollidingText_->setPosition(legendX + 20.0f, legendY); + legendCollidingText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); + addChild(legendCollidingText_); } /** @@ -302,99 +407,39 @@ private: } /** - * @brief 绘制UI界面 + * @brief 更新UI文本内容 */ - void drawUI(RenderBackend &renderer) { - if (!titleFont_ || !infoFont_) + void updateUI() { + if (!nodeCountText_) return; auto &app = Application::instance(); - // 绘制标题 - renderer.drawText(*titleFont_, "引擎空间索引演示", Vec2(30.0f, 20.0f), - Color(1.0f, 1.0f, 1.0f, 1.0f)); + // 使用 setFormat 格式化文本 + nodeCountText_->setFormat("节点数量: %zu", stats_.nodeCount); + strategyText_->setFormat("索引策略: %s", stats_.strategyName); + collisionText_->setFormat("碰撞对数: %zu", stats_.collisionCount); + updateTimeText_->setFormat("更新时间: %.2f ms", stats_.updateTime); + collisionTimeText_->setFormat("碰撞检测: %.2f ms", stats_.collisionTime); + renderTimeText_->setFormat("渲染时间: %.2f ms", stats_.renderTime); + fpsText_->setFormat("FPS: %u", app.fps()); + } - // 绘制性能统计 - std::stringstream ss; - float x = 30.0f; - float y = 60.0f; - float lineHeight = 22.0f; - - ss << "节点数量: " << stats_.nodeCount; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.9f, 0.9f, 0.9f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << "索引策略: " << stats_.strategyName; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.5f, 1.0f, 0.5f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << "碰撞对数: " << stats_.collisionCount; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(1.0f, 0.5f, 0.5f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << std::fixed << std::setprecision(2); - ss << "更新时间: " << stats_.updateTime << " ms"; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << "碰撞检测: " << stats_.collisionTime << " ms"; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << "渲染时间: " << stats_.renderTime << " ms"; - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - - ss.str(""); - ss << "FPS: " << app.fps(); - renderer.drawText(*infoFont_, ss.str(), Vec2(x, y), - Color(0.5f, 1.0f, 0.5f, 1.0f)); - y += lineHeight * 1.5f; - - // 绘制操作说明 - renderer.drawText(*infoFont_, "操作说明:", Vec2(x, y), - Color(1.0f, 1.0f, 0.5f, 1.0f)); - y += lineHeight; - renderer.drawText(*infoFont_, "A键 - 添加100个节点", Vec2(x + 10, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - renderer.drawText(*infoFont_, "B键 - 移除100个节点", Vec2(x + 10, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - renderer.drawText(*infoFont_, "X键 - 切换索引策略", Vec2(x + 10, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - y += lineHeight; - renderer.drawText(*infoFont_, "+键 - 退出程序", Vec2(x + 10, y), - Color(0.8f, 0.8f, 0.8f, 1.0f)); - - // 绘制图例 + /** + * @brief 绘制图例方块 + */ + void drawLegend(RenderBackend &renderer) { float legendX = screenWidth_ - 200.0f; - float legendY = 20.0f; - renderer.drawText(*infoFont_, "图例:", Vec2(legendX, legendY), - Color(1.0f, 1.0f, 1.0f, 1.0f)); - legendY += 25.0f; + float legendY = 20.0f + 25.0f; // 在标题下方 + // 绘制正常状态方块 renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), Color(0.5f, 0.5f, 0.9f, 0.7f)); - renderer.drawText(*infoFont_, "- 正常", Vec2(legendX + 20.0f, legendY), - Color(0.8f, 0.8f, 0.8f, 1.0f)); legendY += 25.0f; + // 绘制碰撞状态方块 renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), Color(1.0f, 0.2f, 0.2f, 0.9f)); - renderer.drawText(*infoFont_, "- 碰撞中", Vec2(legendX + 20.0f, legendY), - Color(0.8f, 0.8f, 0.8f, 1.0f)); } std::vector> nodes_; @@ -404,6 +449,24 @@ private: Ptr titleFont_; Ptr infoFont_; + + // UI 文本组件 + Ptr titleText_; + Ptr nodeCountText_; + Ptr strategyText_; + Ptr collisionText_; + Ptr updateTimeText_; + Ptr collisionTimeText_; + Ptr renderTimeText_; + Ptr fpsText_; + Ptr helpTitleText_; + Ptr helpAddText_; + Ptr helpRemoveText_; + Ptr helpToggleText_; + Ptr helpExitText_; + Ptr legendTitleText_; + Ptr legendNormalText_; + Ptr legendCollidingText_; }; // ============================================================================