refactor(audio): 示例程序重构音频系统为单例管理器模式

feat(text): 为Text类添加格式化文本创建和设置功能
refactor(ui): 将碰撞演示和空间索引演示的UI改为Text组件实现
chore: 移除不再使用的音频上下文和菜单按钮相关代码
This commit is contained in:
ChestnutYueyue 2026-02-11 15:30:32 +08:00
parent 2751b27d90
commit 1f10ce999c
15 changed files with 446 additions and 350 deletions

View File

@ -4,6 +4,9 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/font.h> #include <extra2d/graphics/font.h>
#include <extra2d/ui/widget.h> #include <extra2d/ui/widget.h>
#include <cstdarg>
#include <cstdio>
#include <string>
namespace extra2d { namespace extra2d {
@ -29,6 +32,15 @@ public:
static Ptr<Text> create(const String &text); static Ptr<Text> create(const String &text);
static Ptr<Text> create(const String &text, Ptr<FontAtlas> font); static Ptr<Text> create(const String &text, Ptr<FontAtlas> font);
// ------------------------------------------------------------------------
// 格式化创建方法(类似 printf
// 使用示例:
// auto text = Text::createFormat("FPS: %d", 60);
// auto text = Text::createFormat(font, "得分: %d", score);
// ------------------------------------------------------------------------
static Ptr<Text> createFormat(const char *fmt, ...);
static Ptr<Text> createFormat(Ptr<FontAtlas> font, const char *fmt, ...);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 链式调用构建器方法 // 链式调用构建器方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -48,6 +60,38 @@ public:
void setText(const String &text); void setText(const String &text);
const String &getText() const { return 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;
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 字体 // 字体
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -1,6 +1,8 @@
#include <extra2d/ui/text.h> #include <extra2d/ui/text.h>
#include <extra2d/graphics/render_backend.h> #include <extra2d/graphics/render_backend.h>
#include <extra2d/core/string.h> #include <extra2d/core/string.h>
#include <cstdarg>
#include <cstdio>
namespace extra2d { namespace extra2d {
@ -203,6 +205,29 @@ Ptr<Text> Text::create(const String &text, Ptr<FontAtlas> font) {
return t; return t;
} }
// ------------------------------------------------------------------------
// 格式化创建方法
// ------------------------------------------------------------------------
Ptr<Text> 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<Text>(buffer);
}
Ptr<Text> Text::createFormat(Ptr<FontAtlas> 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<Text>(buffer);
t->setFont(font);
return t;
}
Rect Text::getBoundingBox() const { Rect Text::getBoundingBox() const {
if (!font_ || text_.empty()) { if (!font_ || text_.empty()) {
return Rect(); return Rect();

View File

@ -1,7 +1,5 @@
#include <cmath> #include <cmath>
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
#include <sstream>
using namespace extra2d; using namespace extra2d;
@ -73,7 +71,7 @@ public:
centerBox_->setPosition(Vec2(centerX, centerY)); centerBox_->setPosition(Vec2(centerX, centerY));
addChild(centerBox_); addChild(centerBox_);
// 加载字体 // 加载字体并创建UI
loadFonts(); loadFonts();
E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1); E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1);
@ -102,6 +100,9 @@ public:
// 执行碰撞检测 // 执行碰撞检测
performCollisionDetection(); performCollisionDetection();
// 更新UI文本
updateUI();
// 检查退出按键 // 检查退出按键
auto &input = Application::instance().input(); auto &input = Application::instance().input();
if (input.isButtonPressed(GamepadButton::Start)) { if (input.isButtonPressed(GamepadButton::Start)) {
@ -110,28 +111,60 @@ public:
} }
} }
void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
// 绘制说明文字
drawUI(renderer);
}
private: private:
/** /**
* @brief * @brief UI文本
*/ */
void loadFonts() { void loadFonts() {
auto &resources = Application::instance().resources(); auto &resources = Application::instance().resources();
titleFont_ = resources.loadFont("assets/font.ttf", 60, true); titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
infoFont_ = resources.loadFont("assets/font.ttf", 28, true); infoFont_ = resources.loadFont("assets/font.ttf", 28, true);
if (!titleFont_) { // 创建标题文本
E2D_LOG_WARN("无法加载标题字体"); titleText_ = Text::create("碰撞检测演示", titleFont_);
} titleText_->setPosition(50.0f, 30.0f);
if (!infoFont_) { titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
E2D_LOG_WARN("无法加载信息字体"); 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<float>(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<float>(app.getConfig().height);
renderer.drawText(*infoFont_, "按 + 键退出",
Vec2(50.0f, screenHeight - 50.0f),
Color(0.8f, 0.8f, 0.8f, 1.0f));
}
Ptr<CollisionBox> centerBox_; Ptr<CollisionBox> centerBox_;
std::vector<Ptr<CollisionBox>> boxes_; std::vector<Ptr<CollisionBox>> boxes_;
float rotationAngle_ = 0.0f; float rotationAngle_ = 0.0f;
@ -229,6 +224,14 @@ private:
// 字体资源 // 字体资源
Ptr<FontAtlas> titleFont_; Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_; Ptr<FontAtlas> infoFont_;
// UI 文本组件
Ptr<Text> titleText_;
Ptr<Text> descText_;
Ptr<Text> collideHintText_;
Ptr<Text> collisionText_;
Ptr<Text> fpsText_;
Ptr<Text> exitHintText_;
}; };
// ============================================================================ // ============================================================================

View File

@ -1,7 +1,6 @@
#include "PlayScene.h" #include "PlayScene.h"
#include "audio_context.h" #include "audio_manager.h"
#include "audio_controller.h"
#include "storage.h" #include "storage.h"
#include "StartScene.h" #include "StartScene.h"
#include "SuccessScene.h" #include "SuccessScene.h"
@ -93,11 +92,6 @@ PlayScene::PlayScene(int level) {
mapLayer_->setPosition(0.0f, 0.0f); mapLayer_->setPosition(0.0f, 0.0f);
addChild(mapLayer_); addChild(mapLayer_);
auto audioNode = AudioController::create();
audioNode->setName("AudioController");
addChild(audioNode);
setAudioController(audioNode);
setLevel(level); setLevel(level);
} }
@ -136,12 +130,10 @@ void PlayScene::onUpdate(float dt) {
return; return;
} }
// X 键直接切换音效 // X键直接切换音效
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) { if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
g_SoundOpen = !g_SoundOpen; g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) { AudioManager::instance().setEnabled(g_SoundOpen);
audio->setEnabled(g_SoundOpen);
}
updateSoundIcon(); updateSoundIcon();
return; return;
} }
@ -189,9 +181,7 @@ void PlayScene::executeMenuItem() {
break; break;
case 1: // 切换音效 case 1: // 切换音效
g_SoundOpen = !g_SoundOpen; g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) { AudioManager::instance().setEnabled(g_SoundOpen);
audio->setEnabled(g_SoundOpen);
}
updateSoundIcon(); updateSoundIcon();
break; break;
} }
@ -329,9 +319,7 @@ void PlayScene::move(int dx, int dy, int direct) {
g_Pushing = false; g_Pushing = false;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
map_.value[targetY][targetX].type = TYPE::Man; map_.value[targetY][targetX].type = TYPE::Man;
if (auto audio = getAudioController()) { AudioManager::instance().playManMove();
audio->playManMove();
}
} else if (map_.value[targetY][targetX].type == TYPE::Box) { } else if (map_.value[targetY][targetX].type == TYPE::Box) {
g_Pushing = true; 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[targetY][targetX].type = TYPE::Man;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
if (auto audio = getAudioController()) { AudioManager::instance().playBoxMove();
audio->playBoxMove();
}
} else { } else {
return; return;
} }

View File

@ -1,7 +1,6 @@
#include "StartScene.h" #include "StartScene.h"
#include "audio_context.h" #include "audio_manager.h"
#include "audio_controller.h"
#include "data.h" #include "data.h"
#include "PlayScene.h" #include "PlayScene.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
@ -28,10 +27,6 @@ void StartScene::onEnter() {
setBackgroundColor(extra2d::Colors::Black); setBackgroundColor(extra2d::Colors::Black);
if (getChildren().empty()) { if (getChildren().empty()) {
auto audioNode = AudioController::create();
audioNode->setName("audio_controller");
addChild(audioNode);
setAudioController(audioNode);
float screenW = static_cast<float>(app.getConfig().width); float screenW = static_cast<float>(app.getConfig().width);
float screenH = static_cast<float>(app.getConfig().height); float screenH = static_cast<float>(app.getConfig().height);
@ -138,9 +133,7 @@ void StartScene::onUpdate(float dt) {
// X键切换音效 // X键切换音效
if (input.isButtonPressed(extra2d::GamepadButton::X)) { if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen; g_SoundOpen = !g_SoundOpen;
if (auto audio = getAudioController()) { AudioManager::instance().setEnabled(g_SoundOpen);
audio->setEnabled(g_SoundOpen);
}
updateSoundIcon(); updateSoundIcon();
} }
} }

View File

@ -1,17 +0,0 @@
#include "audio_context.h"
#include "audio_controller.h"
namespace pushbox {
static extra2d::WeakPtr<AudioController> g_audioController;
void setAudioController(const extra2d::Ptr<AudioController>& controller) {
g_audioController = controller;
}
extra2d::Ptr<AudioController> getAudioController() {
return g_audioController.lock();
}
} // namespace pushbox

View File

@ -1,12 +0,0 @@
#pragma once
#include <extra2d/extra2d.h>
namespace pushbox {
class AudioController;
void setAudioController(const extra2d::Ptr<AudioController>& controller);
extra2d::Ptr<AudioController> getAudioController();
} // namespace pushbox

View File

@ -1,62 +0,0 @@
#include "audio_controller.h"
#include "storage.h"
namespace pushbox {
extra2d::Ptr<AudioController> AudioController::create() {
return extra2d::makePtr<AudioController>();
}
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

View File

@ -1,29 +0,0 @@
#pragma once
#include "data.h"
#include <extra2d/extra2d.h>
namespace pushbox {
class AudioController : public extra2d::Node {
public:
static extra2d::Ptr<AudioController> 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<extra2d::Sound> background_;
extra2d::Ptr<extra2d::Sound> manMove_;
extra2d::Ptr<extra2d::Sound> boxMove_;
};
} // namespace pushbox

View File

@ -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

View File

@ -0,0 +1,47 @@
#pragma once
#include "data.h"
#include <extra2d/extra2d.h>
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<extra2d::Sound> background_;
extra2d::Ptr<extra2d::Sound> manMove_;
extra2d::Ptr<extra2d::Sound> boxMove_;
};
} // namespace pushbox

View File

@ -2,6 +2,7 @@
#include "StartScene.h" #include "StartScene.h"
#include "data.h" #include "data.h"
#include "storage.h" #include "storage.h"
#include "audio_manager.h"
using namespace extra2d; using namespace extra2d;
@ -40,6 +41,9 @@ int main(int argc, char **argv)
} }
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true); pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
// 初始化全局音频管理器
pushbox::AudioManager::instance().init();
// 进入开始场景(主界面) // 进入开始场景(主界面)
app.enterScene(makePtr<pushbox::StartScene>()); app.enterScene(makePtr<pushbox::StartScene>());

View File

@ -1,40 +0,0 @@
#include "menu_button.h"
#include <extra2d/extra2d.h>
namespace pushbox {
extra2d::Ptr<MenuButton> MenuButton::create(extra2d::Ptr<extra2d::FontAtlas> font,
const extra2d::String& text,
extra2d::Function<void()> onClick) {
auto btn = extra2d::makePtr<MenuButton>();
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<MenuButton>(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

View File

@ -1,21 +0,0 @@
#pragma once
#include <extra2d/extra2d.h>
namespace pushbox {
class MenuButton : public extra2d::Button {
public:
static extra2d::Ptr<MenuButton> create(extra2d::Ptr<extra2d::FontAtlas> font,
const extra2d::String& text,
extra2d::Function<void()> onClick);
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
private:
bool enabled_ = true;
extra2d::Function<void()> onClick_;
};
} // namespace pushbox

View File

@ -178,30 +178,135 @@ public:
} }
void onRender(RenderBackend &renderer) override { void onRender(RenderBackend &renderer) override {
Scene::onRender(renderer);
auto renderStart = std::chrono::high_resolution_clock::now(); auto renderStart = std::chrono::high_resolution_clock::now();
// 节点渲染由Scene自动处理 Scene::onRender(renderer);
auto renderEnd = std::chrono::high_resolution_clock::now(); auto renderEnd = std::chrono::high_resolution_clock::now();
stats_.renderTime = stats_.renderTime =
std::chrono::duration<double, std::milli>(renderEnd - renderStart) std::chrono::duration<double, std::milli>(renderEnd - renderStart)
.count(); .count();
// 绘制UI // 更新UI文本内容
drawUI(renderer); updateUI();
// 绘制图例方块Text组件会自动渲染
drawLegend(renderer);
} }
private: private:
/** /**
* @brief * @brief UI文本组件
*/ */
void loadFonts() { void loadFonts() {
auto &resources = Application::instance().resources(); auto &resources = Application::instance().resources();
titleFont_ = resources.loadFont("assets/font.ttf", 28, true); titleFont_ = resources.loadFont("assets/font.ttf", 28, true);
infoFont_ = resources.loadFont("assets/font.ttf", 16, 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) { void updateUI() {
if (!titleFont_ || !infoFont_) if (!nodeCountText_)
return; return;
auto &app = Application::instance(); auto &app = Application::instance();
// 绘制标题 // 使用 setFormat 格式化文本
renderer.drawText(*titleFont_, "引擎空间索引演示", Vec2(30.0f, 20.0f), nodeCountText_->setFormat("节点数量: %zu", stats_.nodeCount);
Color(1.0f, 1.0f, 1.0f, 1.0f)); 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; * @brief
float x = 30.0f; */
float y = 60.0f; void drawLegend(RenderBackend &renderer) {
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));
// 绘制图例
float legendX = screenWidth_ - 200.0f; float legendX = screenWidth_ - 200.0f;
float legendY = 20.0f; float legendY = 20.0f + 25.0f; // 在标题下方
renderer.drawText(*infoFont_, "图例:", Vec2(legendX, legendY),
Color(1.0f, 1.0f, 1.0f, 1.0f));
legendY += 25.0f;
// 绘制正常状态方块
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
Color(0.5f, 0.5f, 0.9f, 0.7f)); 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; legendY += 25.0f;
// 绘制碰撞状态方块
renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f),
Color(1.0f, 0.2f, 0.2f, 0.9f)); 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<Ptr<PhysicsNode>> nodes_; std::vector<Ptr<PhysicsNode>> nodes_;
@ -404,6 +449,24 @@ private:
Ptr<FontAtlas> titleFont_; Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_; Ptr<FontAtlas> infoFont_;
// UI 文本组件
Ptr<Text> titleText_;
Ptr<Text> nodeCountText_;
Ptr<Text> strategyText_;
Ptr<Text> collisionText_;
Ptr<Text> updateTimeText_;
Ptr<Text> collisionTimeText_;
Ptr<Text> renderTimeText_;
Ptr<Text> fpsText_;
Ptr<Text> helpTitleText_;
Ptr<Text> helpAddText_;
Ptr<Text> helpRemoveText_;
Ptr<Text> helpToggleText_;
Ptr<Text> helpExitText_;
Ptr<Text> legendTitleText_;
Ptr<Text> legendNormalText_;
Ptr<Text> legendCollidingText_;
}; };
// ============================================================================ // ============================================================================