From 4107e0b12b947e702c2c7cb887a40a5531729574 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Fri, 13 Feb 2026 08:56:27 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E5=A4=9A=E4=B8=AAAPI?= =?UTF-8?q?=E6=95=99=E7=A8=8B=E6=96=87=E6=A1=A3=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为多个API教程文档添加详细示例代码和说明,包括: - 音频系统:添加Flappy Bird音效管理器实现 - 输入处理:补充鼠标输入检测和游戏输入处理示例 - UI系统:增加按钮启用/禁用功能及动画控制示例 - 节点系统:完善动画系统文档和示例 - 资源管理:添加精灵帧和资源加载器实现 - 场景系统:补充视口适配方案和基础场景类实现 --- docs/API_Tutorial/02_Scene_System.md | 191 +++++++++++++++++++- docs/API_Tutorial/03_Node_System.md | 114 ++++++++++++ docs/API_Tutorial/04_Resource_Management.md | 188 +++++++++++++++++++ docs/API_Tutorial/05_Input_Handling.md | 103 +++++++++++ docs/API_Tutorial/07_UI_System.md | 97 ++++++++++ docs/API_Tutorial/08_Audio_System.md | 90 +++++++++ 6 files changed, 779 insertions(+), 4 deletions(-) diff --git a/docs/API_Tutorial/02_Scene_System.md b/docs/API_Tutorial/02_Scene_System.md index 1c68a68..0aadd2a 100644 --- a/docs/API_Tutorial/02_Scene_System.md +++ b/docs/API_Tutorial/02_Scene_System.md @@ -1,14 +1,197 @@ # 02. 场景系统 -Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果。 +Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果,以及视口适配功能。 ## 完整示例 -参考 `examples/push_box/` 中的实现: +参考 `examples/flappy_bird/` 中的实现: +- `BaseScene.h/cpp` - 基础场景类(视口适配) - `StartScene.h/cpp` - 开始菜单场景 -- `PlayScene.h/cpp` - 游戏主场景 -- `SuccessScene.h/cpp` - 通关场景 +- `GameScene.h/cpp` - 游戏主场景 +- `GameOverLayer.h/cpp` - 游戏结束层 + +## 视口适配(重要) + +### 问题背景 + +在游戏开发中,不同设备有不同的屏幕分辨率。如果直接使用窗口坐标,游戏内容可能会变形或显示不完整。 + +Extra2D 提供了**居中视口适配**方案,让游戏在任意分辨率下都能正确显示: + +- 游戏使用固定的逻辑分辨率(如 Flappy Bird 的 288×512) +- 根据窗口大小自动缩放,保持宽高比 +- 游戏内容居中显示,四周显示黑边 + +### 实现方案 + +参考 `examples/flappy_bird/BaseScene.h/cpp`: + +```cpp +// BaseScene.h +#pragma once + +#include + +namespace flappybird { + +// 游戏逻辑分辨率(原始 Flappy Bird 尺寸) +static constexpr float GAME_WIDTH = 288.0f; +static constexpr float GAME_HEIGHT = 512.0f; + +/** + * @brief Flappy Bird 基础场景类 + * 所有游戏场景都应继承此类,以获得统一的居中视口适配功能 + */ +class BaseScene : public extra2d::Scene { +public: + BaseScene(); + + void onEnter() override; + void onRender(extra2d::RenderBackend &renderer) override; + +protected: + void updateViewport(); + + float scaledGameWidth_ = 0.0f; + float scaledGameHeight_ = 0.0f; + float viewportOffsetX_ = 0.0f; + float viewportOffsetY_ = 0.0f; +}; + +} // namespace flappybird +``` + +```cpp +// BaseScene.cpp +#include "BaseScene.h" + +namespace flappybird { + +BaseScene::BaseScene() { + setBackgroundColor(extra2d::Color(0.0f, 0.0f, 0.0f, 1.0f)); +} + +void BaseScene::onEnter() { + extra2d::Scene::onEnter(); + updateViewport(); +} + +void BaseScene::updateViewport() { + auto &app = extra2d::Application::instance(); + float windowWidth = static_cast(app.window().getWidth()); + float windowHeight = static_cast(app.window().getHeight()); + + // 保持游戏原始宽高比,进行"黑边"适配 + float scaleX = windowWidth / GAME_WIDTH; + float scaleY = windowHeight / GAME_HEIGHT; + float scale = std::min(scaleX, scaleY); + + scaledGameWidth_ = GAME_WIDTH * scale; + scaledGameHeight_ = GAME_HEIGHT * scale; + viewportOffsetX_ = (windowWidth - scaledGameWidth_) * 0.5f; + viewportOffsetY_ = (windowHeight - scaledGameHeight_) * 0.5f; + + // 设置视口大小为游戏逻辑分辨率 + setViewportSize(GAME_WIDTH, GAME_HEIGHT); + + // 创建并设置相机 + auto camera = extra2d::makePtr(); + camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f); + setCamera(camera); +} + +void BaseScene::onRender(extra2d::RenderBackend &renderer) { + // 检查窗口大小是否改变 + auto &app = extra2d::Application::instance(); + float currentWindowWidth = static_cast(app.window().getWidth()); + float currentWindowHeight = static_cast(app.window().getHeight()); + + float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f; + float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f; + if (std::abs(currentWindowWidth - expectedWidth) > 1.0f || + std::abs(currentWindowHeight - expectedHeight) > 1.0f) { + updateViewport(); + } + + // 设置视口为居中区域 + renderer.setViewport( + static_cast(viewportOffsetX_), static_cast(viewportOffsetY_), + static_cast(scaledGameWidth_), static_cast(scaledGameHeight_)); + + extra2d::Scene::onRender(renderer); +} + +} // namespace flappybird +``` + +### 使用基础场景类 + +所有游戏场景继承 `BaseScene`,自动获得视口适配功能: + +```cpp +// GameScene.h +class GameScene : public BaseScene { +public: + void onEnter() override; + void onUpdate(float dt) override; +}; + +// GameScene.cpp +void GameScene::onEnter() { + BaseScene::onEnter(); // 必须调用父类方法 + + // 使用游戏逻辑分辨率进行布局 + float screenWidth = GAME_WIDTH; // 288.0f + float screenHeight = GAME_HEIGHT; // 512.0f + + // 所有坐标都基于逻辑分辨率 + auto bird = extra2d::makePtr(); + bird->setPosition(extra2d::Vec2(screenWidth / 2.0f - 50.0f, screenHeight / 2.0f)); + addChild(bird); +} + +void GameScene::onUpdate(float dt) { + // 游戏逻辑更新... + + // 必须调用父类方法,确保子节点动画正常播放 + BaseScene::onUpdate(dt); +} +``` + +### 视口适配原理图 + +``` +┌─────────────────────────────────────────────┐ +│ 窗口 (1280 x 720) │ +│ │ +│ ┌─────────────────────────────────┐ │ +│ │ 黑边区域 │ │ +│ │ │ │ +│ │ ┌───────────────────────────┐ │ │ +│ │ │ 游戏视口 (288 x 512) │ │ │ +│ │ │ 自动缩放并居中显示 │ │ │ +│ │ │ │ │ │ +│ │ │ ┌─────────────────┐ │ │ │ +│ │ │ │ 游戏内容 │ │ │ │ +│ │ │ │ (逻辑分辨率) │ │ │ │ +│ │ │ └─────────────────┘ │ │ │ +│ │ │ │ │ │ +│ │ └───────────────────────────┘ │ │ +│ │ │ │ +│ │ 黑边区域 │ │ +│ └─────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────┘ +``` + +### 关键要点 + +1. **定义游戏逻辑分辨率** - 如 `GAME_WIDTH = 288.0f, GAME_HEIGHT = 512.0f` +2. **在 `onEnter()` 中调用 `updateViewport()`** - 初始化视口 +3. **在 `onRender()` 中设置渲染器视口** - 确保正确渲染区域 +4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小 +5. **处理窗口大小变化** - 在 `onRender()` 中检测并更新 ## 场景基础 diff --git a/docs/API_Tutorial/03_Node_System.md b/docs/API_Tutorial/03_Node_System.md index 00525d8..7c33dbf 100644 --- a/docs/API_Tutorial/03_Node_System.md +++ b/docs/API_Tutorial/03_Node_System.md @@ -564,6 +564,120 @@ void GameOverLayer::initPanel(int score, float screenHeight) { }; ``` +## 动画系统 + +### 动作类型 + +Extra2D 提供了丰富的动作类: + +| 动作类 | 说明 | +|--------|------| +| `MoveTo` | 移动到指定位置 | +| `MoveBy` | 移动指定偏移量 | +| `ScaleTo` | 缩放到指定比例 | +| `ScaleBy` | 缩放指定比例 | +| `RotateTo` | 旋转到指定角度 | +| `RotateBy` | 旋转指定角度 | +| `FadeIn` | 淡入 | +| `FadeOut` | 淡出 | +| `FadeTo` | 淡化到指定透明度 | +| `Delay` | 延迟 | +| `Sequence` | 顺序执行多个动作 | +| `Spawn` | 同时执行多个动作 | +| `Loop` | 循环执行动作 | +| `CallFunc` | 回调函数 | + +### 运行动作 + +```cpp +// 移动动画 +auto moveAction = makePtr(1.0f, Vec2(0.0f, -100.0f)); +node->runAction(moveAction); + +// 缩放动画 +auto scaleAction = makePtr(0.5f, 2.0f); +node->runAction(scaleAction); + +// 淡入淡出 +auto fadeOut = makePtr(0.3f); +auto fadeIn = makePtr(0.3f); +node->runAction(fadeOut); +``` + +### 动画完成回调 + +使用 `setCompletionCallback` 在动画完成时执行回调: + +```cpp +// 参考 examples/flappy_bird/GameOverLayer.cpp +void GameOverLayer::onEnter() { + Node::onEnter(); + + // 创建向上移动的动画 + auto moveAction = extra2d::makePtr( + 1.0f, extra2d::Vec2(0.0f, -screenHeight)); + + // 设置动画完成回调 + moveAction->setCompletionCallback([this]() { + animationDone_ = true; + + // 动画完成后启用按钮 + if (restartBtn_) + restartBtn_->setEnabled(true); + if (menuBtn_) + menuBtn_->setEnabled(true); + }); + + runAction(moveAction); +} +``` + +### 顺序和并行动画 + +```cpp +// 顺序执行:先移动,再缩放,最后淡出 +auto sequence = makePtr({ + new MoveTo(1.0f, Vec2(100, 100)), + new ScaleTo(0.5f, 2.0f), + new FadeOut(0.3f) +}); +node->runAction(sequence); + +// 并行执行:同时移动和旋转 +auto spawn = makePtr({ + new MoveTo(1.0f, Vec2(100, 100)), + new RotateBy(1.0f, 360.0f) +}); +node->runAction(spawn); + +// 循环执行 +auto loop = makePtrrunAction(loop); +``` + +### 动画进度回调 + +```cpp +auto action = makePtr(2.0f, Vec2(100, 100)); +action->setProgressCallback([](float progress) { + // progress: 0.0 - 1.0 + E2D_LOG_INFO("动画进度: {}%", progress * 100); +}); +node->runAction(action); +``` + +### 停止动画 + +```cpp +// 停止所有动画 +node->stopAllActions(); + +// 停止特定动画(需要先设置 tag) +action->setTag(1); +node->runAction(action); +node->stopActionByTag(1); +``` + ## 下一步 - [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载 diff --git a/docs/API_Tutorial/04_Resource_Management.md b/docs/API_Tutorial/04_Resource_Management.md index d08f766..57cb11b 100644 --- a/docs/API_Tutorial/04_Resource_Management.md +++ b/docs/API_Tutorial/04_Resource_Management.md @@ -204,6 +204,194 @@ atlas->addTexture("icon", iconTexture); atlas->pack(); // 执行打包 ``` +## 精灵帧(SpriteFrame) + +### 什么是精灵帧? + +精灵帧是纹理图集中的一个矩形区域,用于从单个大纹理中提取小图像。使用精灵帧可以: + +- 减少纹理切换,提高渲染性能 +- 方便管理动画帧和 UI 元素 +- 支持从 JSON 文件加载精灵帧数据 + +### 完整示例:资源加载器 + +参考 `examples/flappy_bird/ResLoader.h/cpp`: + +```cpp +// ResLoader.h +#pragma once + +#include +#include +#include + +namespace flappybird { + +/** + * @brief 音频类型枚举 + */ +enum class MusicType { + Click, // 按键声音 + Hit, // 小鸟死亡声音 + Fly, // 小鸟飞翔声音 + Point, // 得分声音 + Swoosh // 转场声音 +}; + +/** + * @brief 资源加载器类 + * 管理纹理图集、精灵帧和音频资源的加载 + */ +class ResLoader { +public: + static void init(); + + static extra2d::Ptr getKeyFrame(const std::string& name); + + static void playMusic(MusicType type); + +private: + struct ImageInfo { + float width, height, x, y; + }; + + static extra2d::Ptr atlasTexture_; + static std::map imageMap_; + static std::map> soundMap_; +}; + +} // namespace flappybird +``` + +```cpp +// ResLoader.cpp +#include "ResLoader.h" +#include + +namespace flappybird { + +extra2d::Ptr ResLoader::atlasTexture_; +std::map ResLoader::imageMap_; +std::map> ResLoader::soundMap_; + +void ResLoader::init() { + auto &resources = extra2d::Application::instance().resources(); + + // 加载图集纹理 + atlasTexture_ = resources.loadTexture("assets/images/atlas.png"); + if (!atlasTexture_) { + E2D_LOG_ERROR("无法加载图集纹理 atlas.png"); + return; + } + + // 加载 JSON 文件 + std::string jsonContent = resources.loadJsonFile("assets/images/atlas.json"); + if (jsonContent.empty()) { + E2D_LOG_ERROR("无法加载 atlas.json 文件"); + return; + } + + // 解析 JSON 图集数据 + try { + nlohmann::json jsonData = nlohmann::json::parse(jsonContent); + + for (const auto &sprite : jsonData["sprites"]) { + std::string name = sprite["name"]; + float x = sprite["x"]; + float y = sprite["y"]; + float width = sprite["width"]; + float height = sprite["height"]; + + ImageInfo info = {width, height, x, y}; + imageMap_[name] = info; + } + + E2D_LOG_INFO("成功加载 {} 个精灵帧", imageMap_.size()); + } catch (const std::exception &e) { + E2D_LOG_ERROR("解析 atlas.json 失败: {}", e.what()); + return; + } + + // 加载音效 + soundMap_[MusicType::Click] = resources.loadSound("assets/sound/click.wav"); + soundMap_[MusicType::Hit] = resources.loadSound("assets/sound/hit.wav"); + soundMap_[MusicType::Fly] = resources.loadSound("assets/sound/fly.wav"); + soundMap_[MusicType::Point] = resources.loadSound("assets/sound/point.wav"); + soundMap_[MusicType::Swoosh] = resources.loadSound("assets/sound/swoosh.wav"); + + E2D_LOG_INFO("资源加载完成"); +} + +extra2d::Ptr +ResLoader::getKeyFrame(const std::string &name) { + auto it = imageMap_.find(name); + if (it == imageMap_.end()) { + E2D_LOG_WARN("找不到精灵帧: %s", name.c_str()); + return nullptr; + } + + const ImageInfo &info = it->second; + return extra2d::makePtr( + atlasTexture_, extra2d::Rect(info.x, info.y, info.width, info.height)); +} + +void ResLoader::playMusic(MusicType type) { + auto it = soundMap_.find(type); + if (it != soundMap_.end() && it->second) { + it->second->play(); + } +} + +} // namespace flappybird +``` + +### 使用精灵帧创建精灵 + +```cpp +// 参考 examples/flappy_bird/GameScene.cpp +void GameScene::onEnter() { + BaseScene::onEnter(); + + // 从图集获取精灵帧 + auto bgFrame = ResLoader::getKeyFrame("bg_day"); + if (bgFrame) { + // 使用精灵帧创建精灵 + auto background = extra2d::Sprite::create( + bgFrame->getTexture(), + bgFrame->getRect()); + background->setAnchor(extra2d::Vec2(0.0f, 0.0f)); + background->setPosition(extra2d::Vec2(0.0f, 0.0f)); + addChild(background); + } + + // 创建按钮 + auto buttonFrame = ResLoader::getKeyFrame("button_play"); + if (buttonFrame) { + auto button = extra2d::Button::create(); + button->setBackgroundImage(buttonFrame->getTexture(), buttonFrame->getRect()); + button->setAnchor(extra2d::Vec2(0.5f, 0.5f)); + button->setPosition(extra2d::Vec2(screenWidth / 2.0f, 300.0f)); + button->setOnClick([]() { + // 处理点击 + }); + addChild(button); + } +} +``` + +### JSON 图集格式 + +```json +{ + "sprites": [ + { "name": "bg_day", "x": 0, "y": 0, "width": 288, "height": 512 }, + { "name": "bird0_0", "x": 288, "y": 0, "width": 34, "height": 24 }, + { "name": "button_play", "x": 322, "y": 0, "width": 116, "height": 70 } + ] +} +``` + ## 字体加载 ### 基本用法 diff --git a/docs/API_Tutorial/05_Input_Handling.md b/docs/API_Tutorial/05_Input_Handling.md index 94c5c19..b687646 100644 --- a/docs/API_Tutorial/05_Input_Handling.md +++ b/docs/API_Tutorial/05_Input_Handling.md @@ -76,6 +76,109 @@ player->setPosition(player->getPosition() + leftStick * speed * dt); input.setStickDeadZone(0.2f); ``` +## 鼠标输入 + +### 鼠标按钮检测 + +```cpp +auto& input = Application::instance().input(); + +// 检测鼠标按钮按下(单次触发) +if (input.isMousePressed(MouseButton::Left)) { + // 左键刚按下 +} + +if (input.isMousePressed(MouseButton::Right)) { + // 右键刚按下 +} + +// 检测鼠标按钮状态(持续触发) +if (input.isMouseDown(MouseButton::Left)) { + // 左键保持按下 +} + +// 检测鼠标按钮释放 +if (input.isMouseReleased(MouseButton::Left)) { + // 左键刚释放 +} +``` + +### 鼠标位置 + +```cpp +// 获取鼠标位置(屏幕坐标) +Vec2 mousePos = input.getMousePosition(); + +// 获取鼠标在游戏视口中的位置(考虑视口适配) +// 参考 examples/flappy_bird 的 BaseScene 实现 +``` + +### 鼠标按钮枚举 + +| 枚举值 | 说明 | +|--------|------| +| `MouseButton::Left` | 左键 | +| `MouseButton::Right` | 右键 | +| `MouseButton::Middle` | 中键 | + +### 完整示例:Flappy Bird 输入处理 + +参考 `examples/flappy_bird/GameScene.cpp`: + +```cpp +void GameScene::onUpdate(float dt) { + if (!gameOver_) { + auto &input = extra2d::Application::instance().input(); + + // 同时支持手柄 A 键和鼠标左键 + if (input.isButtonPressed(extra2d::GamepadButton::A) || + input.isMousePressed(extra2d::MouseButton::Left)) { + if (!started_) { + started_ = true; + startGame(); + } + bird_->jump(); + } + + // 游戏逻辑更新... + } + + BaseScene::onUpdate(dt); +} +``` + +### 完整示例:Game Over 界面输入 + +参考 `examples/flappy_bird/GameOverLayer.cpp`: + +```cpp +void GameOverLayer::onUpdate(float dt) { + Node::onUpdate(dt); + + // 动画完成后才响应输入 + if (!animationDone_) + return; + + auto &input = extra2d::Application::instance().input(); + + // A 键重新开始游戏 + if (input.isButtonPressed(extra2d::GamepadButton::A)) { + ResLoader::playMusic(MusicType::Click); + auto &app = extra2d::Application::instance(); + app.scenes().replaceScene(extra2d::makePtr(), + extra2d::TransitionType::Fade, 0.5f); + } + + // B 键返回主菜单 + if (input.isButtonPressed(extra2d::GamepadButton::B)) { + ResLoader::playMusic(MusicType::Click); + auto &app = extra2d::Application::instance(); + app.scenes().replaceScene(extra2d::makePtr(), + extra2d::TransitionType::Fade, 0.5f); + } +} +``` + ## 完整示例 ### 菜单导航 diff --git a/docs/API_Tutorial/07_UI_System.md b/docs/API_Tutorial/07_UI_System.md index 848a1f6..e354cc8 100644 --- a/docs/API_Tutorial/07_UI_System.md +++ b/docs/API_Tutorial/07_UI_System.md @@ -149,6 +149,103 @@ menuBtn->setPosition(centerX, centerY); addChild(menuBtn); ``` +### 按钮启用/禁用 + +Widget 基类提供了 `setEnabled()` 方法控制按钮的交互状态: + +```cpp +// 禁用按钮 +button->setEnabled(false); + +// 启用按钮 +button->setEnabled(true); + +// 检查按钮状态 +bool isEnabled = button->isEnabled(); +``` + +### 完整示例:动画完成后启用按钮 + +参考 `examples/flappy_bird/GameOverLayer.cpp`: + +```cpp +// GameOverLayer.h +class GameOverLayer : public extra2d::Node { +public: + GameOverLayer(int score); + void onEnter() override; + void onUpdate(float dt) override; + +private: + void initButtons(); + + int score_ = 0; + bool animationDone_ = false; + extra2d::Ptr restartBtn_; + extra2d::Ptr menuBtn_; +}; + +// GameOverLayer.cpp +void GameOverLayer::onEnter() { + Node::onEnter(); + + // 初始化按钮(初始禁用) + initButtons(); + + // 创建动画 + auto moveAction = extra2d::makePtr( + 1.0f, extra2d::Vec2(0.0f, -screenHeight)); + + // 动画完成后启用按钮 + moveAction->setCompletionCallback([this]() { + animationDone_ = true; + if (restartBtn_) + restartBtn_->setEnabled(true); + if (menuBtn_) + menuBtn_->setEnabled(true); + }); + + runAction(moveAction); +} + +void GameOverLayer::initButtons() { + auto restartFrame = ResLoader::getKeyFrame("button_restart"); + if (restartFrame) { + restartBtn_ = extra2d::Button::create(); + restartBtn_->setBackgroundImage(restartFrame->getTexture(), + restartFrame->getRect()); + restartBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); + restartBtn_->setPosition(extra2d::Vec2(0.0f, 360.0f)); + restartBtn_->setEnabled(false); // 初始禁用 + restartBtn_->setOnClick([]() { + // 处理点击 + }); + addChild(restartBtn_); + } + + // 菜单按钮类似... +} + +void GameOverLayer::onUpdate(float dt) { + Node::onUpdate(dt); + + // 动画完成后才响应手柄输入 + if (!animationDone_) + return; + + auto &input = extra2d::Application::instance().input(); + if (input.isButtonPressed(extra2d::GamepadButton::A)) { + // 重新开始 + } +} +``` + +### 使用场景 + +- **动画播放期间**:禁用按钮,防止用户过早操作 +- **加载过程中**:禁用按钮,显示加载状态 +- **条件限制**:当条件不满足时禁用按钮(如未选择关卡) + ## 文本(Text) ### 创建文本 diff --git a/docs/API_Tutorial/08_Audio_System.md b/docs/API_Tutorial/08_Audio_System.md index 4ca2c8f..cc663af 100644 --- a/docs/API_Tutorial/08_Audio_System.md +++ b/docs/API_Tutorial/08_Audio_System.md @@ -90,6 +90,96 @@ audio.unloadAllSounds(); ## 完整示例 +### Flappy Bird 音效管理器 + +参考 `examples/flappy_bird/ResLoader.h/cpp`: + +```cpp +// ResLoader.h +#pragma once + +#include +#include + +namespace flappybird { + +enum class MusicType { + Click, // 按键声音 + Hit, // 小鸟死亡声音 + Fly, // 小鸟飞翔声音 + Point, // 得分声音 + Swoosh // 转场声音 +}; + +class ResLoader { +public: + static void init(); + static void playMusic(MusicType type); + +private: + static std::map> soundMap_; +}; + +} // namespace flappybird +``` + +```cpp +// ResLoader.cpp +#include "ResLoader.h" + +namespace flappybird { + +std::map> ResLoader::soundMap_; + +void ResLoader::init() { + auto &resources = extra2d::Application::instance().resources(); + + // 加载所有音效 + soundMap_[MusicType::Click] = resources.loadSound("assets/sound/click.wav"); + soundMap_[MusicType::Hit] = resources.loadSound("assets/sound/hit.wav"); + soundMap_[MusicType::Fly] = resources.loadSound("assets/sound/fly.wav"); + soundMap_[MusicType::Point] = resources.loadSound("assets/sound/point.wav"); + soundMap_[MusicType::Swoosh] = resources.loadSound("assets/sound/swoosh.wav"); +} + +void ResLoader::playMusic(MusicType type) { + auto it = soundMap_.find(type); + if (it != soundMap_.end() && it->second) { + it->second->play(); + } +} + +} // namespace flappybird +``` + +### 在游戏中使用 + +```cpp +// GameScene.cpp - 得分时播放音效 +if (pipeX <= birdX) { + score_++; + scoreNumber_->setNumber(score_); + firstPipe->scored = true; + ResLoader::playMusic(MusicType::Point); // 播放得分音效 +} + +// bird.cpp - 跳跃时播放音效 +void Bird::jump() { + velocity_.y = jumpForce_; + ResLoader::playMusic(MusicType::Fly); // 播放飞翔音效 +} + +// GameOverLayer.cpp - 按钮点击时播放音效 +restartBtn_->setOnClick([]() { + ResLoader::playMusic(MusicType::Click); // 播放点击音效 + auto &app = extra2d::Application::instance(); + app.scenes().replaceScene(extra2d::makePtr(), + extra2d::TransitionType::Fade, 0.5f); +}); +``` + +### 推箱子音效管理器 + ```cpp // audio_manager.h #pragma once