docs: 更新多个API教程文档并添加示例代码
为多个API教程文档添加详细示例代码和说明,包括: - 音频系统:添加Flappy Bird音效管理器实现 - 输入处理:补充鼠标输入检测和游戏输入处理示例 - UI系统:增加按钮启用/禁用功能及动画控制示例 - 节点系统:完善动画系统文档和示例 - 资源管理:添加精灵帧和资源加载器实现 - 场景系统:补充视口适配方案和基础场景类实现
This commit is contained in:
parent
3a9b44cbfe
commit
4107e0b12b
|
|
@ -1,14 +1,197 @@
|
||||||
# 02. 场景系统
|
# 02. 场景系统
|
||||||
|
|
||||||
Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果。
|
Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果,以及视口适配功能。
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
|
|
||||||
参考 `examples/push_box/` 中的实现:
|
参考 `examples/flappy_bird/` 中的实现:
|
||||||
|
|
||||||
|
- `BaseScene.h/cpp` - 基础场景类(视口适配)
|
||||||
- `StartScene.h/cpp` - 开始菜单场景
|
- `StartScene.h/cpp` - 开始菜单场景
|
||||||
- `PlayScene.h/cpp` - 游戏主场景
|
- `GameScene.h/cpp` - 游戏主场景
|
||||||
- `SuccessScene.h/cpp` - 通关场景
|
- `GameOverLayer.h/cpp` - 游戏结束层
|
||||||
|
|
||||||
|
## 视口适配(重要)
|
||||||
|
|
||||||
|
### 问题背景
|
||||||
|
|
||||||
|
在游戏开发中,不同设备有不同的屏幕分辨率。如果直接使用窗口坐标,游戏内容可能会变形或显示不完整。
|
||||||
|
|
||||||
|
Extra2D 提供了**居中视口适配**方案,让游戏在任意分辨率下都能正确显示:
|
||||||
|
|
||||||
|
- 游戏使用固定的逻辑分辨率(如 Flappy Bird 的 288×512)
|
||||||
|
- 根据窗口大小自动缩放,保持宽高比
|
||||||
|
- 游戏内容居中显示,四周显示黑边
|
||||||
|
|
||||||
|
### 实现方案
|
||||||
|
|
||||||
|
参考 `examples/flappy_bird/BaseScene.h/cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// BaseScene.h
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
|
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<float>(app.window().getWidth());
|
||||||
|
float windowHeight = static_cast<float>(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<extra2d::Camera>();
|
||||||
|
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<float>(app.window().getWidth());
|
||||||
|
float currentWindowHeight = static_cast<float>(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<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
|
||||||
|
static_cast<int>(scaledGameWidth_), static_cast<int>(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>();
|
||||||
|
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()` 中检测并更新
|
||||||
|
|
||||||
## 场景基础
|
## 场景基础
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<MoveBy>(1.0f, Vec2(0.0f, -100.0f));
|
||||||
|
node->runAction(moveAction);
|
||||||
|
|
||||||
|
// 缩放动画
|
||||||
|
auto scaleAction = makePtr<ScaleTo>(0.5f, 2.0f);
|
||||||
|
node->runAction(scaleAction);
|
||||||
|
|
||||||
|
// 淡入淡出
|
||||||
|
auto fadeOut = makePtr<FadeOut>(0.3f);
|
||||||
|
auto fadeIn = makePtr<FadeIn>(0.3f);
|
||||||
|
node->runAction(fadeOut);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动画完成回调
|
||||||
|
|
||||||
|
使用 `setCompletionCallback` 在动画完成时执行回调:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 参考 examples/flappy_bird/GameOverLayer.cpp
|
||||||
|
void GameOverLayer::onEnter() {
|
||||||
|
Node::onEnter();
|
||||||
|
|
||||||
|
// 创建向上移动的动画
|
||||||
|
auto moveAction = extra2d::makePtr<extra2d::MoveBy>(
|
||||||
|
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<Sequence>({
|
||||||
|
new MoveTo(1.0f, Vec2(100, 100)),
|
||||||
|
new ScaleTo(0.5f, 2.0f),
|
||||||
|
new FadeOut(0.3f)
|
||||||
|
});
|
||||||
|
node->runAction(sequence);
|
||||||
|
|
||||||
|
// 并行执行:同时移动和旋转
|
||||||
|
auto spawn = makePtr<Spawn>({
|
||||||
|
new MoveTo(1.0f, Vec2(100, 100)),
|
||||||
|
new RotateBy(1.0f, 360.0f)
|
||||||
|
});
|
||||||
|
node->runAction(spawn);
|
||||||
|
|
||||||
|
// 循环执行
|
||||||
|
auto loop = makePtr<Loop(new RotateBy(1.0f, 360.0f), 5); // 旋转5次
|
||||||
|
node->runAction(loop);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动画进度回调
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto action = makePtr<MoveTo>(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) - 深入了解资源加载
|
- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,194 @@ atlas->addTexture("icon", iconTexture);
|
||||||
atlas->pack(); // 执行打包
|
atlas->pack(); // 执行打包
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 精灵帧(SpriteFrame)
|
||||||
|
|
||||||
|
### 什么是精灵帧?
|
||||||
|
|
||||||
|
精灵帧是纹理图集中的一个矩形区域,用于从单个大纹理中提取小图像。使用精灵帧可以:
|
||||||
|
|
||||||
|
- 减少纹理切换,提高渲染性能
|
||||||
|
- 方便管理动画帧和 UI 元素
|
||||||
|
- 支持从 JSON 文件加载精灵帧数据
|
||||||
|
|
||||||
|
### 完整示例:资源加载器
|
||||||
|
|
||||||
|
参考 `examples/flappy_bird/ResLoader.h/cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ResLoader.h
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace flappybird {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音频类型枚举
|
||||||
|
*/
|
||||||
|
enum class MusicType {
|
||||||
|
Click, // 按键声音
|
||||||
|
Hit, // 小鸟死亡声音
|
||||||
|
Fly, // 小鸟飞翔声音
|
||||||
|
Point, // 得分声音
|
||||||
|
Swoosh // 转场声音
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源加载器类
|
||||||
|
* 管理纹理图集、精灵帧和音频资源的加载
|
||||||
|
*/
|
||||||
|
class ResLoader {
|
||||||
|
public:
|
||||||
|
static void init();
|
||||||
|
|
||||||
|
static extra2d::Ptr<extra2d::SpriteFrame> getKeyFrame(const std::string& name);
|
||||||
|
|
||||||
|
static void playMusic(MusicType type);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ImageInfo {
|
||||||
|
float width, height, x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
static extra2d::Ptr<extra2d::Texture> atlasTexture_;
|
||||||
|
static std::map<std::string, ImageInfo> imageMap_;
|
||||||
|
static std::map<MusicType, extra2d::Ptr<extra2d::Sound>> soundMap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flappybird
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ResLoader.cpp
|
||||||
|
#include "ResLoader.h"
|
||||||
|
#include <json/json.hpp>
|
||||||
|
|
||||||
|
namespace flappybird {
|
||||||
|
|
||||||
|
extra2d::Ptr<extra2d::Texture> ResLoader::atlasTexture_;
|
||||||
|
std::map<std::string, ResLoader::ImageInfo> ResLoader::imageMap_;
|
||||||
|
std::map<MusicType, extra2d::Ptr<extra2d::Sound>> 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<extra2d::SpriteFrame>
|
||||||
|
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<extra2d::SpriteFrame>(
|
||||||
|
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 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 字体加载
|
## 字体加载
|
||||||
|
|
||||||
### 基本用法
|
### 基本用法
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,109 @@ player->setPosition(player->getPosition() + leftStick * speed * dt);
|
||||||
input.setStickDeadZone(0.2f);
|
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<GameScene>(),
|
||||||
|
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<StartScene>(),
|
||||||
|
extra2d::TransitionType::Fade, 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
|
|
||||||
### 菜单导航
|
### 菜单导航
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,103 @@ menuBtn->setPosition(centerX, centerY);
|
||||||
addChild(menuBtn);
|
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<extra2d::Button> restartBtn_;
|
||||||
|
extra2d::Ptr<extra2d::Button> menuBtn_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// GameOverLayer.cpp
|
||||||
|
void GameOverLayer::onEnter() {
|
||||||
|
Node::onEnter();
|
||||||
|
|
||||||
|
// 初始化按钮(初始禁用)
|
||||||
|
initButtons();
|
||||||
|
|
||||||
|
// 创建动画
|
||||||
|
auto moveAction = extra2d::makePtr<extra2d::MoveBy>(
|
||||||
|
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)
|
## 文本(Text)
|
||||||
|
|
||||||
### 创建文本
|
### 创建文本
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,96 @@ audio.unloadAllSounds();
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
|
|
||||||
|
### Flappy Bird 音效管理器
|
||||||
|
|
||||||
|
参考 `examples/flappy_bird/ResLoader.h/cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ResLoader.h
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
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<MusicType, extra2d::Ptr<extra2d::Sound>> soundMap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace flappybird
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ResLoader.cpp
|
||||||
|
#include "ResLoader.h"
|
||||||
|
|
||||||
|
namespace flappybird {
|
||||||
|
|
||||||
|
std::map<MusicType, extra2d::Ptr<extra2d::Sound>> 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<GameScene>(),
|
||||||
|
extra2d::TransitionType::Fade, 0.5f);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 推箱子音效管理器
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// audio_manager.h
|
// audio_manager.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue