diff --git a/README.md b/README.md
index c3285b0..5c0b393 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
高性能、易用、原生支持 Switch 平台
-[📖 构建指南](./SWITCH_BUILD_GUIDE.md) | [🚀 快速开始](#快速开始) | [📦 项目结构](#项目结构) | [💬 问题反馈](https://github.com/ChestnutYueyue/extra2d/issues)
+[📖 构建指南](./docs/Extra2D%20构建系统文档.md) | [🚀 快速开始](#快速开始) | [📦 示例程序](#示例程序) | [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md)
@@ -40,68 +40,12 @@
### ✨ 核心特性
- **🎯 Switch 原生支持**:专为 Nintendo Switch 硬件优化,支持掌机/主机双模式
-- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画,提供 ALS 动画格式支持
-- **📜 脚本系统**:集成 Squirrel 脚本引擎,支持热更新和快速迭代开发
-- **🎵 音频系统**:基于 SDL2 的高质量音频播放,支持 BGM 和音效
-- **🎨 特效系统**:粒子系统、后处理效果、自定义着色器支持
+- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画
+- **🎵 音频系统**:基于 SDL2_mixer 的高质量音频播放,支持 BGM 和音效
+- **🎨 渲染系统**:基于 OpenGL ES 的 2D 渲染,支持自定义着色器
- **💾 数据持久化**:游戏存档、配置文件的便捷读写
-
----
-
-## 🗺️ 架构概览
-
-```mermaid
-mindmap
- root((Extra2D 引擎架构))
- 核心系统
- 应用管理 Application
- 渲染后端 RenderBackend
- 窗口管理 Window
- 输入处理 Input
- 音频引擎 AudioEngine
- 资源管理 ResourceManager
- 事件系统 EventDispatcher
- 场景管理
- 场景 Scene
- 场景管理器 SceneManager
- 过渡动画 Transition
- 空间索引 SpatialManager
- 节点系统
- 基础节点 Node
- 精灵 Sprite
- 文本 Text
- 形状 ShapeNode
- 摄像机 Camera
- 动画节点 AnimationNode
- 动画系统
- 动作系统 Action
- 精灵动画 AnimatedSprite
- 骨骼动画支持
- 动画缓存 AnimationCache
- 动画事件 AnimationEvent
- 脚本系统
- Squirrel 脚本引擎
- 脚本节点 ScriptNode
- 完整 API 绑定
- 特效系统
- 粒子系统 ParticleSystem
- 后处理 PostProcess
- 自定义效果管理器
- UI 系统
- 基础控件 Widget
- 按钮 Button
- 工具库
- 音频播放 Sound
- 数据持久化 Data
- 随机数 Random
- 定时器 Timer
- 字体 FontAtlas
- 数学库
- 向量 Vec2/Vec3
- 矩形 Rect
- 大小 Size
- 颜色 Color
-```
+- **🔧 空间索引**:内置四叉树和空间哈希碰撞检测系统
+- **🖱️ UI 系统**:完整的 UI 控件支持(按钮、文本、滑块等)
---
@@ -116,319 +60,24 @@ mindmap
| 构建工具 | xmake |
| 目标平台 | Nintendo Switch / Windows (MinGW) |
-### 安装 devkitPro
+### 安装 xmake
```bash
-# Windows (以管理员身份运行 PowerShell)
-Invoke-WebRequest -Uri "https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.exe" -OutFile "devkitpro-pacman.exe"
-.\devkitpro-pacman.exe
+# Windows (PowerShell)
+Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
-# 安装 Switch 开发工具链
-pacman -S switch-dev switch-portlibs
+# macOS
+brew install xmake
+
+# Linux
+sudo add-apt-repository ppa:xmake-io/xmake
+sudo apt update
+sudo apt install xmake
```
-### 构建项目
+## 📚 文档
-#### Switch 平台
-
-```bash
-# 克隆仓库
-git clone https://github.com/ChestnutYueyue/extra2d.git
-cd extra2d
-
-# 配置 Switch 平台构建
-xmake f -p switch --mode=release
-
-# 构建引擎
-xmake
-
-# 构建示例游戏
-xmake -g examples
-```
-
-#### Windows (MinGW) 平台
-
-```bash
-# 克隆仓库
-git clone https://github.com/ChestnutYueyue/extra2d.git
-cd extra2d
-
-# 配置 MinGW 平台构建
-xmake f -p mingw --mode=release
-
-# 安装依赖
-xmake require -y
-
-# 构建引擎和示例
-xmake
-
-# 运行示例
-xmake run hello_world
-```
-
-### 生成 NSP 可运行文件
-
-```bash
-# 打包推箱子游戏示例
-xmake package push_box
-
-# 生成的文件位于
-# build/switch/release/push_box/push_box.nsp
-```
-
----
-
-## 📝 Hello World 示例
-
-```cpp
-#include
-
-using namespace extra2d;
-
-int main()
-{
- // 初始化日志
- Logger::init();
- Logger::setLevel(LogLevel::Info);
-
- // 配置应用
- AppConfig config;
- config.title = "Hello Extra2D";
- config.width = 1280;
- config.height = 720;
- config.vsync = true;
-
- // 初始化应用
- auto& app = Application::instance();
- if (!app.init(config)) {
- Logger::shutdown();
- return -1;
- }
-
- // 创建场景
- auto scene = makePtr();
- scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
-
- // 创建文本节点
- auto text = Text::create("Hello, Extra2D on Switch!");
- text->setPosition(Vec2(640, 360));
- text->setAnchor(Vec2(0.5f, 0.5f));
- text->setTextColor(Color(1.0f, 0.5f, 0.2f, 1.0f));
- text->setFontSize(48);
-
- // 添加动画效果
- text->runAction(makePtr(
- makePtr(std::vector>{
- makePtr(1.0f, Vec2(1.5f, 1.5f)),
- makePtr(1.0f, Vec2(1.0f, 1.0f))
- })
- ));
-
- // 添加到场景
- scene->addChild(text);
-
- // 进入场景
- app.enterScene(scene);
-
- // 运行主循环
- app.run();
-
- // 清理
- app.shutdown();
- Logger::shutdown();
- return 0;
-}
-```
-
----
-
-## 🏗️ 项目结构
-
-```
-Extra2D/
-├── 📁 Extra2D/ # 引擎核心代码
-│ ├── 📁 include/ # 头文件
-│ │ ├── 📁 extra2d/ # 引擎头文件
-│ │ │ ├── extra2d.h # 主头文件
-│ │ │ ├── app/ # 应用管理
-│ │ │ ├── action/ # 动作系统
-│ │ │ ├── animation/ # 动画系统
-│ │ │ ├── audio/ # 音频系统
-│ │ │ ├── core/ # 核心类型
-│ │ │ ├── effects/ # 特效系统
-│ │ │ ├── event/ # 事件系统
-│ │ │ ├── graphics/ # 图形渲染
-│ │ │ ├── platform/ # 平台抽象
-│ │ │ ├── resource/ # 资源管理
-│ │ │ ├── scene/ # 场景系统
-│ │ │ ├── script/ # 脚本系统
-│ │ │ ├── spatial/ # 空间索引
-│ │ │ ├── ui/ # UI 系统
-│ │ │ └── utils/ # 工具库
-│ │ ├── 📁 glad/ # OpenGL Loader
-│ │ ├── 📁 json/ # JSON 库
-│ │ ├── 📁 simpleini/ # INI 配置文件库
-│ │ └── 📁 stb/ # STB 图像库
-│ └── 📁 src/ # 源文件
-│ ├── action/ # 动作系统实现
-│ ├── animation/ # 动画系统实现
-│ ├── app/ # 应用管理实现
-│ ├── audio/ # 音频系统实现
-│ ├── core/ # 核心类型实现
-│ ├── effects/ # 特效系统实现
-│ ├── event/ # 事件系统实现
-│ ├── glad/ # GLAD 实现
-│ ├── graphics/ # 图形渲染实现
-│ ├── platform/ # 平台抽象实现
-│ ├── resource/ # 资源管理实现
-│ ├── scene/ # 场景系统实现
-│ ├── script/ # 脚本系统实现
-│ ├── spatial/ # 空间索引实现
-│ ├── ui/ # UI 系统实现
-│ └── utils/ # 工具库实现
-├── 📁 docs/ # 文档
-│ ├── 📁 API_Tutorial/ # API 教程
-│ └── Extra2D 构建系统文档.md # 构建系统文档
-├── 📁 examples/ # 示例程序
-│ ├── hello_world/ # Hello World 示例
-│ ├── collision_demo/ # 碰撞检测示例
-│ ├── push_box/ # 推箱子游戏
-│ └── spatial_index_demo/ # 空间索引示例
-├── 📁 logo/ # Logo 资源
-├── 📁 squirrel/ # Squirrel 脚本引擎
-├── � xmake/ # Xmake 构建配置
-│ └── toolchains/ # 工具链定义
-├── 📄 xmake.lua # 主构建配置
-├── 📄 LICENSE # MIT 许可证
-└── 📄 README.md # 本文件
-```
-
----
-
-## 🎮 Switch 特定功能
-
-### 双模式支持
-
-```cpp
-// 检测当前模式
-if (app.isDocked()) {
- // 主机模式:可使用更高分辨率
- config.width = 1920;
- config.height = 1080;
-} else {
- // 掌机模式
- config.width = 1280;
- config.height = 720;
-}
-```
-
-### 控制器输入
-
-```cpp
-auto& input = app.input();
-
-// Joy-Con 支持
-if (input.isKeyDown(KeyCode::ButtonA)) {
- // A 键按下
-}
-
-if (input.isKeyDown(KeyCode::ButtonLeft)) {
- // 左摇杆向左
-}
-```
-
-### ROMFS 资源加载
-
-```cpp
-// 自动从 ROMFS 加载资源
-auto texture = resources.loadTexture("romfs:/images/player.png");
-auto sound = audio.loadSound("romfs:/audio/jump.wav");
-```
-
----
-
-## 📋 API 速查
-
-### 应用控制
-
-```cpp
-auto& app = Application::instance();
-app.init(config);
-app.run();
-app.quit();
-```
-
-### 场景管理
-
-```cpp
-auto scene = makePtr();
-app.enterScene(scene);
-app.enterScene(scene, makePtr(1.0f));
-```
-
-### 节点操作
-
-```cpp
-auto sprite = Sprite::create(texture);
-sprite->setPosition(Vec2(100, 200));
-sprite->setRotation(45.0f);
-sprite->runAction(makePtr(1.0f, Vec2(200, 300)));
-```
-
-### 动画系统
-
-```cpp
-// 精灵动画
-auto anim = AnimatedSprite::createFromGrid(
- "player.png", 96, 96, 125.0f, 16);
-anim->setFrameRange(0, 3);
-anim->play();
-
-// 动作动画
-node->runAction(makePtr(
- makePtr(1.0f, Vec2(100, 200)),
- makePtr(0.5f, Vec2(2.0f, 2.0f))
-));
-```
-
-### 输入处理
-
-```cpp
-auto& input = app.input();
-if (input.isKeyDown(KeyCode::ButtonA)) {}
-if (input.isKeyPressed(KeyCode::ButtonB)) {}
-auto pos = input.getMousePosition();
-```
-
-### 音频播放
-
-```cpp
-auto& audio = app.audio();
-auto sound = audio.loadSound("jump.wav");
-sound->play();
-sound->setVolume(0.8f);
-```
-
----
-
-## 🛠️ 技术栈
-
-| 技术 | 用途 | 版本 |
-|:----:|:-----|:----:|
-| OpenGL | 2D 图形渲染 | ES 3.0+ |
-| GLFW | 窗口和输入管理 | 3.3+ |
-| GLM | 数学库 | 0.9.9+ |
-| miniaudio | 音频播放 | 最新版 |
-| spdlog | 日志系统 | 最新版 |
-| stb_image | 图像加载 | 最新版 |
-| Squirrel | 脚本引擎 | 3.2+ |
-| xmake | 构建系统 | 2.5+ |
-
----
-
-## 📖 相关文档
-
-- [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
+- [📖 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
- [03. 节点系统](./docs/API_Tutorial/03_Node_System.md)
@@ -438,8 +87,21 @@ sound->setVolume(0.8f);
- [07. UI 系统](./docs/API_Tutorial/07_UI_System.md)
- [08. 音频系统](./docs/API_Tutorial/08_Audio_System.md)
- [🔧 构建系统文档](./docs/Extra2D%20构建系统文档.md) - 详细的构建系统说明
-- [🎮 Switch 构建指南](./SWITCH_BUILD_GUIDE.md) - Switch 平台构建教程
-- [📝 迁移完成记录](./SWITCH_MIGRATION_COMPLETE.md) - 项目迁移历史记录
+
+---
+
+## 🛠️ 技术栈
+
+| 技术 | 用途 | 版本 |
+|:----:|:-----|:----:|
+| OpenGL ES | 2D 图形渲染 | 3.0+ |
+| GLFW | 窗口和输入管理 | 3.3+ |
+| GLM | 数学库 | 0.9.9+ |
+| SDL2_mixer | 音频播放 | 2.0+ |
+| spdlog | 日志系统 | 最新版 |
+| stb_image | 图像加载 | 最新版 |
+| freetype | 字体渲染 | 最新版 |
+| xmake | 构建系统 | 2.5+ |
---
diff --git a/docs/API_Tutorial/01_Quick_Start.md b/docs/API_Tutorial/01_Quick_Start.md
index 9490a63..071b9a7 100644
--- a/docs/API_Tutorial/01_Quick_Start.md
+++ b/docs/API_Tutorial/01_Quick_Start.md
@@ -1,68 +1,215 @@
-# Extra2D API 教程 - 01. 快速开始
+# 01. 快速开始
-## 简介
+本教程将带你快速上手 Extra2D 引擎,通过一个简单的 Hello World 示例了解引擎的基本使用方法。
-Extra2D 是一个跨平台的 2D 游戏引擎,支持 Windows (MinGW) 和 Nintendo Switch 平台。
+## 示例代码
-## 最小示例
+完整示例位于 `examples/hello_world/main.cpp`:
```cpp
#include
using namespace extra2d;
-int main(int argc, char **argv) {
- // 1. 初始化日志系统
- Logger::init();
- Logger::setLevel(LogLevel::Debug);
+// ============================================================================
+// Hello World 场景
+// ============================================================================
- // 2. 获取应用实例
- auto &app = Application::instance();
+/**
+ * @brief Hello World 场景类
+ * 显示简单的 "Hello World" 文字
+ */
+class HelloWorldScene : public Scene {
+public:
+ /**
+ * @brief 场景进入时调用
+ */
+ void onEnter() override {
+ E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景");
- // 3. 配置应用
- AppConfig config;
- config.title = "My Game";
- config.width = 1280;
- config.height = 720;
- config.vsync = true;
- config.fpsLimit = 60;
+ // 设置背景颜色为深蓝色
+ setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
- // 4. 初始化应用
- if (!app.init(config)) {
- E2D_LOG_ERROR("应用初始化失败!");
- return -1;
+ // 加载字体(支持多种字体后备)
+ auto &resources = Application::instance().resources();
+ font_ = resources.loadFont("assets/font.ttf", 48, true);
+
+ if (!font_) {
+ E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!");
+ return;
}
- // 5. 进入场景
- app.enterScene(makePtr());
+ // 创建 "你好世界" 文本组件 - 使用屏幕空间(固定位置,不随相机移动)
+ auto text1 = Text::create("你好世界", font_);
+ text1->withCoordinateSpace(CoordinateSpace::Screen)
+ ->withScreenPosition(640.0f, 360.0f) // 屏幕中心
+ ->withAnchor(0.5f, 0.5f) // 中心锚点,让文字中心对准位置
+ ->withTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
+ addChild(text1);
- // 6. 运行应用
- app.run();
+ // 创建提示文本组件 - 使用屏幕空间,固定在屏幕底部
+ auto text2 = Text::create("退出按键(START 按钮)", font_);
+ text2->withCoordinateSpace(CoordinateSpace::Screen)
+ ->withScreenPosition(640.0f, 650.0f) // 屏幕底部
+ ->withAnchor(0.5f, 0.5f)
+ ->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f));
+ addChild(text2);
+ }
- return 0;
+ /**
+ * @brief 每帧更新时调用
+ * @param dt 时间间隔(秒)
+ */
+ void onUpdate(float dt) override {
+ Scene::onUpdate(dt);
+
+ // 检查退出按键
+ auto &input = Application::instance().input();
+
+ // 使用手柄 START 按钮退出 (GamepadButton::Start)
+ if (input.isButtonPressed(GamepadButton::Start)) {
+ E2D_LOG_INFO("退出应用 (START 按钮)");
+ Application::instance().quit();
+ }
+ }
+
+private:
+ Ptr font_; // 字体图集
+};
+
+// ============================================================================
+// 程序入口
+// ============================================================================
+
+int main(int argc, char **argv)
+{
+ // 初始化日志系统
+ Logger::init();
+ Logger::setLevel(LogLevel::Debug);
+
+ // 获取应用实例
+ auto &app = Application::instance();
+
+ // 配置应用
+ AppConfig config;
+ config.title = "Easy2D - Hello World";
+ config.width = 1280;
+ config.height = 720;
+ config.vsync = true;
+ config.fpsLimit = 60;
+
+ // 初始化应用
+ if (!app.init(config)) {
+ E2D_LOG_ERROR("应用初始化失败!");
+ return -1;
+ }
+
+ // 进入 Hello World 场景
+ app.enterScene(makePtr());
+
+ // 运行应用
+ app.run();
+
+ return 0;
}
```
## 核心概念
-### 应用生命周期
+### 1. 应用生命周期
+
+Extra2D 应用遵循以下生命周期:
```
-Logger::init() → Application::init() → enterScene() → run() → 退出
+初始化 (Application::init)
+ ↓
+进入场景 (enterScene)
+ ↓
+主循环 (run) → 更新 (onUpdate) → 渲染 (onRender)
+ ↓
+退出 (quit)
```
-### 场景生命周期
+### 2. 场景系统
+场景是游戏内容的容器,通过继承 `Scene` 类并重写以下方法:
+
+| 方法 | 说明 |
+|------|------|
+| `onEnter()` | 场景进入时调用,用于初始化资源 |
+| `onExit()` | 场景退出时调用,用于清理资源 |
+| `onUpdate(dt)` | 每帧更新时调用,用于处理游戏逻辑 |
+| `onRender(renderer)` | 渲染时调用,用于自定义绘制 |
+
+### 3. 坐标空间
+
+Extra2D 支持三种坐标空间:
+
+```cpp
+// 屏幕空间 - 固定位置,不随相机移动
+text->withCoordinateSpace(CoordinateSpace::Screen)
+ ->withScreenPosition(640.0f, 360.0f);
+
+// 相机空间 - 跟随相机但保持相对偏移
+text->withCoordinateSpace(CoordinateSpace::Camera)
+ ->withCameraOffset(50.0f, 50.0f);
+
+// 世界空间 - 随相机移动(默认行为)
+text->withCoordinateSpace(CoordinateSpace::World)
+ ->withPosition(100.0f, 100.0f);
```
-onEnter() → onUpdate(dt) → onRender() → onExit()
+
+### 4. 输入处理
+
+支持手柄输入检测:
+
+```cpp
+auto &input = Application::instance().input();
+
+// 检测按键按下(持续触发)
+if (input.isButtonDown(GamepadButton::A)) { }
+
+// 检测按键按下(单次触发)
+if (input.isButtonPressed(GamepadButton::A)) { }
+
+// 检测按键释放
+if (input.isButtonReleased(GamepadButton::A)) { }
+```
+
+常用按键:
+- `GamepadButton::A` - A 键
+- `GamepadButton::B` - B 键
+- `GamepadButton::X` - X 键
+- `GamepadButton::Y` - Y 键
+- `GamepadButton::Start` - + 键 (Switch)
+- `GamepadButton::DPadUp/Down/Left/Right` - 方向键
+
+### 5. 资源加载
+
+通过资源管理器加载字体、纹理等资源:
+
+```cpp
+auto &resources = Application::instance().resources();
+
+// 加载字体
+auto font = resources.loadFont("assets/font.ttf", 48, true);
+
+// 加载纹理
+auto texture = resources.loadTexture("assets/image.png");
+```
+
+### 6. 日志系统
+
+使用宏进行日志输出:
+
+```cpp
+E2D_LOG_DEBUG("调试信息");
+E2D_LOG_INFO("普通信息");
+E2D_LOG_WARN("警告信息");
+E2D_LOG_ERROR("错误信息");
```
## 下一步
-- [02. 场景系统](02_Scene_System.md)
-- [03. 节点系统](03_Node_System.md)
-- [04. 资源管理](04_Resource_Management.md)
-- [05. 输入处理](05_Input_Handling.md)
-- [06. 碰撞检测](06_Collision_Detection.md)
-- [07. UI 系统](07_UI_System.md)
-- [08. 音频系统](08_Audio_System.md)
+- [02. 场景系统](./02_Scene_System.md) - 深入了解场景管理
+- [03. 节点系统](./03_Node_System.md) - 学习节点和精灵的使用
diff --git a/docs/API_Tutorial/02_Scene_System.md b/docs/API_Tutorial/02_Scene_System.md
index d885e14..1c68a68 100644
--- a/docs/API_Tutorial/02_Scene_System.md
+++ b/docs/API_Tutorial/02_Scene_System.md
@@ -1,9 +1,17 @@
-# Extra2D API 教程 - 02. 场景系统
+# 02. 场景系统
+
+Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本教程将详细介绍场景的生命周期、切换和过渡效果。
+
+## 完整示例
+
+参考 `examples/push_box/` 中的实现:
+
+- `StartScene.h/cpp` - 开始菜单场景
+- `PlayScene.h/cpp` - 游戏主场景
+- `SuccessScene.h/cpp` - 通关场景
## 场景基础
-场景(Scene)是游戏的基本组织单位,负责管理节点和渲染。
-
### 创建场景
```cpp
@@ -11,71 +19,54 @@
using namespace extra2d;
-class MyScene : public Scene {
+class GameScene : public Scene {
public:
- // 场景进入时调用
void onEnter() override {
- // 必须先调用父类的 onEnter()
+ // 必须先调用父类方法
Scene::onEnter();
- // 设置背景颜色
+ // 设置背景色
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
- E2D_LOG_INFO("场景已进入");
+ // 设置视口大小(用于UI布局)
+ setViewportSize(1280.0f, 720.0f);
}
- // 每帧更新时调用
- void onUpdate(float dt) override {
- Scene::onUpdate(dt);
- // dt 是时间间隔(秒)
- }
-
- // 渲染时调用
- void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
- // 绘制自定义内容
- }
-
- // 场景退出时调用
void onExit() override {
// 清理资源
+ removeAllChildren();
+
Scene::onExit();
}
+
+ void onUpdate(float dt) override {
+ Scene::onUpdate(dt);
+
+ // 游戏逻辑更新
+ }
};
```
-### 重要提示
-
-**必须调用 `Scene::onEnter()`**:
-```cpp
-void onEnter() override {
- Scene::onEnter(); // 必须调用!
- // 你的初始化代码
-}
-```
-
-如果不调用,会导致:
-- `running_` 状态未设置
-- 子节点无法正确注册到空间索引
-- 碰撞检测失效
-
-## 场景管理
-
-### 进入场景
+### 场景切换
```cpp
-// 进入新场景
-app.enterScene(makePtr());
+// 进入场景(无过渡)
+app.enterScene(makePtr());
-// 替换当前场景(带过渡效果)
-app.scenes().replaceScene(
- makePtr(),
- TransitionType::Fade, // 淡入淡出
- 0.25f // 过渡时间(秒)
-);
+// 进入场景(有过渡效果)
+app.enterScene(makePtr(), TransitionType::Fade, 0.5f);
+
+// 替换当前场景
+app.scenes().replaceScene(makePtr());
+
+// 推入场景(保留当前场景)
+app.scenes().pushScene(makePtr());
+
+// 弹出场景(返回上一个场景)
+app.scenes().popScene();
```
-### 场景过渡类型
+### 过渡效果类型
```cpp
enum class TransitionType {
@@ -88,84 +79,184 @@ enum class TransitionType {
};
```
-## 场景配置
+## 场景管理器
-### 视口设置
+通过 `app.scenes()` 访问场景管理器:
```cpp
-void onEnter() override {
- Scene::onEnter();
-
- // 设置视口大小(影响坐标系)
- setViewportSize(1280.0f, 720.0f);
-
- // 设置背景颜色
- setBackgroundColor(Colors::Black);
-
- // 启用/禁用空间索引
- setSpatialIndexingEnabled(true);
-}
+auto& scenes = app.scenes();
+
+// 获取当前场景
+auto current = scenes.currentScene();
+
+// 获取场景栈深度
+size_t depth = scenes.stackDepth();
+
+// 清空场景栈
+scenes.clearStack();
```
-### 空间索引
+## 场景生命周期
-```cpp
-// 获取空间管理器
-auto &spatialManager = getSpatialManager();
-
-// 切换空间索引策略
-spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
-spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
-
-// 查询所有碰撞
-auto collisions = queryCollisions();
+```
+创建场景 (makePtr)
+ ↓
+进入场景 (enterScene)
+ ↓
+onEnter() - 初始化资源
+ ↓
+主循环
+ ├── onUpdate(dt) - 每帧更新
+ └── onRender(renderer) - 每帧渲染
+ ↓
+退出场景
+ ↓
+onExit() - 清理资源
+ ↓
+场景销毁
```
-## 完整示例
+## 推箱子示例场景结构
+
+```
+┌─────────────────────────────────────┐
+│ StartScene │
+│ ┌─────────────────────────────┐ │
+│ │ 开始菜单界面 │ │
+│ │ - 新游戏 │ │
+│ │ - 继续游戏 │ │
+│ │ - 退出 │ │
+│ └─────────────────────────────┘ │
+└──────────────┬──────────────────────┘
+ │ 选择"新游戏"
+ ↓
+┌─────────────────────────────────────┐
+│ PlayScene │
+│ ┌─────────────────────────────┐ │
+│ │ 游戏主界面 │ │
+│ │ - 地图渲染 │ │
+│ │ - 玩家控制 │ │
+│ │ - 关卡信息 │ │
+│ └─────────────────────────────┘ │
+└──────────────┬──────────────────────┘
+ │ 通关
+ ↓
+┌─────────────────────────────────────┐
+│ SuccessScene │
+│ ┌─────────────────────────────┐ │
+│ │ 通关界面 │ │
+│ │ - 显示成绩 │ │
+│ │ - 下一关/返回菜单 │ │
+│ └─────────────────────────────┘ │
+└─────────────────────────────────────┘
+```
+
+## 代码示例:菜单场景
```cpp
-class GameScene : public Scene {
+class MenuScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
- // 设置视口和背景
- setViewportSize(1280.0f, 720.0f);
- setBackgroundColor(Color(0.1f, 0.2f, 0.3f, 1.0f));
+ auto& app = Application::instance();
+ auto& resources = app.resources();
- // 启用空间索引
- setSpatialIndexingEnabled(true);
+ // 加载背景
+ auto bgTex = resources.loadTexture("assets/bg.jpg");
+ if (bgTex) {
+ auto bg = Sprite::create(bgTex);
+ bg->setAnchor(0.0f, 0.0f);
+ addChild(bg);
+ }
- E2D_LOG_INFO("游戏场景已加载");
+ // 加载字体
+ font_ = resources.loadFont("assets/font.ttf", 28, true);
+
+ // 创建菜单按钮
+ createMenuButtons();
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
- // 检查退出按键
- auto &input = Application::instance().input();
- if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
- Application::instance().quit();
+ auto& input = Application::instance().input();
+
+ // 方向键导航
+ if (input.isButtonPressed(GamepadButton::DPadUp)) {
+ selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
+ updateMenuColors();
+ }
+ else if (input.isButtonPressed(GamepadButton::DPadDown)) {
+ selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
+ updateMenuColors();
+ }
+
+ // A键确认
+ if (input.isButtonPressed(GamepadButton::A)) {
+ executeMenuItem();
}
}
- void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
+private:
+ void createMenuButtons() {
+ float centerX = 640.0f;
+ float startY = 300.0f;
+ float spacing = 50.0f;
- // 绘制 FPS
- auto &app = Application::instance();
- std::string fpsText = "FPS: " + std::to_string(app.fps());
- // ...
+ for (int i = 0; i < menuCount_; ++i) {
+ auto btn = Button::create();
+ btn->setFont(font_);
+ btn->setAnchor(0.5f, 0.5f);
+ btn->setPosition(centerX, startY + i * spacing);
+ addChild(btn);
+ buttons_.push_back(btn);
+ }
+
+ buttons_[0]->setText("开始游戏");
+ buttons_[1]->setText("设置");
+ buttons_[2]->setText("退出");
+
+ updateMenuColors();
}
- void onExit() override {
- E2D_LOG_INFO("游戏场景退出");
- Scene::onExit();
+ void updateMenuColors() {
+ for (int i = 0; i < buttons_.size(); ++i) {
+ auto color = (i == selectedIndex_) ? Colors::Red : Colors::White;
+ buttons_[i]->setTextColor(color);
+ }
}
+
+ void executeMenuItem() {
+ switch (selectedIndex_) {
+ case 0:
+ Application::instance().scenes().replaceScene(
+ makePtr(), TransitionType::Fade, 0.25f);
+ break;
+ case 1:
+ // 打开设置
+ break;
+ case 2:
+ Application::instance().quit();
+ break;
+ }
+ }
+
+ Ptr font_;
+ std::vector> buttons_;
+ int selectedIndex_ = 0;
+ int menuCount_ = 3;
};
```
+## 最佳实践
+
+1. **始终在 onEnter 中调用 Scene::onEnter()** - 确保场景正确初始化
+2. **在 onExit 中清理资源** - 避免内存泄漏
+3. **使用过渡效果** - 提升用户体验
+4. **分离场景逻辑** - 每个场景负责自己的功能
+
## 下一步
-- [03. 节点系统](03_Node_System.md)
-- [04. 资源管理](04_Resource_Management.md)
+- [03. 节点系统](./03_Node_System.md) - 学习节点和精灵的使用
+- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
diff --git a/docs/API_Tutorial/03_Node_System.md b/docs/API_Tutorial/03_Node_System.md
index 63a7b08..fa61066 100644
--- a/docs/API_Tutorial/03_Node_System.md
+++ b/docs/API_Tutorial/03_Node_System.md
@@ -1,219 +1,353 @@
-# Extra2D API 教程 - 03. 节点系统
+# 03. 节点系统
-## 节点基础
+Extra2D 的节点系统是构建游戏对象的基础。所有可见的游戏元素都是节点的子类。
-节点(Node)是游戏对象的基本单位,可以包含子节点,形成树形结构。
+## 核心节点类型
-### 创建节点
-
-```cpp
-#include
-
-using namespace extra2d;
-
-class MyNode : public Node {
-public:
- MyNode() {
- // 设置位置
- setPosition(Vec2(100.0f, 200.0f));
-
- // 设置旋转(度)
- setRotation(45.0f);
-
- // 设置缩放
- setScale(Vec2(2.0f, 2.0f));
-
- // 设置锚点(0-1范围,默认0.5是中心)
- setAnchor(0.5f, 0.5f);
-
- // 设置可见性
- setVisible(true);
- }
-
- // 每帧更新
- void onUpdate(float dt) override {
- Node::onUpdate(dt);
- // 自定义更新逻辑
- }
-
- // 渲染
- void onRender(RenderBackend &renderer) override {
- Node::onRender(renderer);
- // 自定义渲染
- }
-};
+```
+Node (基类)
+├── Sprite (精灵)
+├── Text (文本)
+├── Button (按钮)
+├── Widget (UI控件基类)
+│ ├── Label (标签)
+│ ├── CheckBox (复选框)
+│ ├── RadioButton (单选按钮)
+│ ├── Slider (滑块)
+│ └── ProgressBar (进度条)
+└── 自定义节点...
```
-## 节点层级
+## 基础节点操作
-### 添加子节点
+### 创建和添加节点
```cpp
-void onEnter() override {
- Scene::onEnter();
-
- // 创建子节点
- auto child = makePtr();
-
- // 添加到场景
- addChild(child);
-
- // 在指定位置添加
- addChild(child, 0); // z-order = 0
-}
+// 创建精灵
+auto sprite = Sprite::create(texture);
+sprite->setPosition(Vec2(100, 200));
+sprite->setAnchor(Vec2(0.5f, 0.5f)); // 中心锚点
+addChild(sprite);
+
+// 创建文本
+auto text = Text::create("Hello World", font);
+text->setPosition(Vec2(400, 300));
+text->setTextColor(Color(1, 1, 1, 1));
+addChild(text);
```
-### 移除子节点
+### 节点属性
```cpp
-// 移除指定子节点
-removeChild(child);
+// 位置
+node->setPosition(Vec2(x, y));
+Vec2 pos = node->getPosition();
-// 移除所有子节点
-removeAllChildren();
+// 旋转(角度)
+node->setRotation(45.0f);
+float angle = node->getRotation();
-// 通过名称移除
-removeChildByName("myNode");
+// 缩放
+node->setScale(Vec2(2.0f, 2.0f));
+Vec2 scale = node->getScale();
+
+// 锚点(0,0 左上角,0.5,0.5 中心,1,1 右下角)
+node->setAnchor(Vec2(0.5f, 0.5f));
+
+// 可见性
+node->setVisible(true);
+bool visible = node->isVisible();
+
+// Z轴顺序
+node->setZOrder(10);
```
-### 获取子节点
+### 链式调用(Builder 模式)
```cpp
-// 获取子节点数量
-size_t count = getChildren().size();
-
-// 通过名称查找
-auto node = getChildByName("myNode");
-
-// 遍历子节点
-for (auto &child : getChildren()) {
- // 处理子节点
-}
+// 使用链式调用快速配置节点
+auto text = Text::create("标题", font)
+ ->withPosition(640.0f, 100.0f)
+ ->withAnchor(0.5f, 0.5f)
+ ->withTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f))
+ ->withCoordinateSpace(CoordinateSpace::Screen);
+addChild(text);
```
-## 空间索引
-
-### 启用空间索引
-
-```cpp
-class PhysicsNode : public Node {
-public:
- PhysicsNode() {
- // 启用空间索引(用于碰撞检测)
- setSpatialIndexed(true);
- }
-
- // 必须实现 getBoundingBox()
- Rect getBoundingBox() const override {
- Vec2 pos = getPosition();
- return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
- }
-};
-```
-
-### 边界框
-
-```cpp
-// 获取节点边界框
-Rect bounds = node->getBoundingBox();
-
-// 检查点是否在边界框内
-if (bounds.contains(Vec2(x, y))) {
- // 点在边界框内
-}
-
-// 检查两个边界框是否相交
-if (bounds.intersects(otherBounds)) {
- // 边界框相交
-}
-```
-
-## 精灵节点
+## 精灵(Sprite)
### 创建精灵
```cpp
-// 加载纹理
-auto texture = resources.loadTexture("assets/player.png");
+auto& resources = Application::instance().resources();
-// 创建精灵
+// 从纹理创建
+auto texture = resources.loadTexture("assets/player.png");
auto sprite = Sprite::create(texture);
-// 设置位置
-sprite->setPosition(Vec2(640.0f, 360.0f));
+// 设置精灵属性
+sprite->setPosition(Vec2(400, 300));
+sprite->setAnchor(Vec2(0.5f, 0.5f));
-// 设置锚点(中心)
-sprite->setAnchor(0.5f, 0.5f);
-
-// 添加到场景
-addChild(sprite);
+// 切换纹理
+auto newTexture = resources.loadTexture("assets/player2.png");
+sprite->setTexture(newTexture);
```
### 精灵动画
```cpp
-// 创建动画
-auto animation = Animation::create("walk", 0.1f);
-animation->addFrame(resources.loadTexture("assets/walk1.png"));
-animation->addFrame(resources.loadTexture("assets/walk2.png"));
-animation->addFrame(resources.loadTexture("assets/walk3.png"));
+// 创建帧动画
+auto anim = AnimatedSprite::createFromGrid(
+ "player.png", // 纹理
+ 32, 32, // 单帧宽高
+ 100.0f, // 帧间隔(ms)
+ 8 // 总帧数
+);
// 播放动画
-sprite->playAnimation(animation, true); // true = 循环播放
+anim->play();
+
+// 设置帧范围
+anim->setFrameRange(0, 3);
+
+// 循环播放
+anim->setLoop(true);
// 停止动画
-sprite->stopAnimation();
+anim->stop();
```
-## 完整示例
+## 文本(Text)
+
+### 创建文本
+
+```cpp
+// 加载字体
+auto font = resources.loadFont("assets/font.ttf", 24, true);
+
+// 创建文本
+auto text = Text::create("Hello World", font);
+text->setPosition(Vec2(400, 300));
+addChild(text);
+
+// 动态修改文本
+text->setText("新的文本内容");
+
+// 使用格式化文本
+text->setFormat("得分: %d", score);
+```
+
+### 文本样式
+
+```cpp
+text->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); // 颜色
+text->setFontSize(48); // 字体大小
+text->setAnchor(Vec2(0.5f, 0.5f)); // 锚点
+
+// 坐标空间
+text->withCoordinateSpace(CoordinateSpace::Screen) // 屏幕空间
+ ->withScreenPosition(100.0f, 50.0f);
+```
+
+## 按钮(Button)
+
+### 创建按钮
+
+```cpp
+auto button = Button::create();
+button->setFont(font);
+button->setText("点击我");
+button->setPosition(Vec2(400, 300));
+button->setCustomSize(200.0f, 60.0f);
+
+// 设置颜色
+button->setTextColor(Colors::White);
+button->setBackgroundColor(
+ Colors::Blue, // 正常状态
+ Colors::Green, // 悬停状态
+ Colors::Red // 按下状态
+);
+
+// 设置点击回调
+button->onClick([]() {
+ E2D_LOG_INFO("按钮被点击!");
+});
+
+addChild(button);
+```
+
+### 透明按钮(用于菜单)
+
+```cpp
+// 创建纯文本按钮(透明背景)
+auto menuBtn = Button::create();
+menuBtn->setFont(font);
+menuBtn->setText("菜单项");
+menuBtn->setTextColor(Colors::Black);
+menuBtn->setBackgroundColor(
+ Colors::Transparent,
+ Colors::Transparent,
+ Colors::Transparent
+);
+menuBtn->setBorder(Colors::Transparent, 0.0f);
+menuBtn->setAnchor(0.5f, 0.5f);
+menuBtn->setPosition(centerX, centerY);
+addChild(menuBtn);
+```
+
+## 节点层级管理
+
+### 父子关系
+
+```cpp
+// 添加子节点
+parent->addChild(child);
+
+// 移除子节点
+parent->removeChild(child);
+
+// 移除所有子节点
+parent->removeAllChildren();
+
+// 获取父节点
+Node* parent = child->getParent();
+
+// 获取子节点列表
+const auto& children = parent->getChildren();
+```
+
+### Z轴顺序
+
+```cpp
+// 设置Z轴顺序(值越大越在上层)
+node->setZOrder(10);
+
+// 重新排序子节点
+parent->reorderChild(child, newZOrder);
+```
+
+## 自定义节点
+
+### 继承 Node 创建自定义节点
```cpp
class Player : public Node {
public:
- Player() {
- setSpatialIndexed(true);
-
- // 加载精灵
- auto &resources = Application::instance().resources();
- auto texture = resources.loadTexture("assets/player.png");
+ static Ptr create(Ptr texture) {
+ auto player = makePtr();
+ if (player->init(texture)) {
+ return player;
+ }
+ return nullptr;
+ }
+
+ bool init(Ptr texture) {
sprite_ = Sprite::create(texture);
- sprite_->setAnchor(0.5f, 0.5f);
addChild(sprite_);
+ return true;
}
- void onUpdate(float dt) override {
- Node::onUpdate(dt);
-
- // 移动
- Vec2 pos = getPosition();
- pos = pos + velocity_ * dt;
- setPosition(pos);
-
- // 边界检查
- auto &app = Application::instance();
- float width = static_cast(app.getConfig().width);
- float height = static_cast(app.getConfig().height);
-
- if (pos.x < 0 || pos.x > width) {
- velocity_.x = -velocity_.x;
- }
- if (pos.y < 0 || pos.y > height) {
- velocity_.y = -velocity_.y;
- }
+ void update(float dt) override {
+ // 更新逻辑
+ velocity_.y += gravity_ * dt;
+ setPosition(getPosition() + velocity_ * dt);
}
- Rect getBoundingBox() const override {
- Vec2 pos = getPosition();
- return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
+ void jump() {
+ velocity_.y = jumpForce_;
}
private:
Ptr sprite_;
- Vec2 velocity_{100.0f, 100.0f};
+ Vec2 velocity_;
+ float gravity_ = -980.0f;
+ float jumpForce_ = 500.0f;
};
+
+// 使用
+auto player = Player::create(texture);
+scene->addChild(player);
+```
+
+## 空间索引(碰撞检测)
+
+### 启用空间索引
+
+```cpp
+class CollidableNode : public Node {
+public:
+ CollidableNode() {
+ // 启用空间索引
+ setSpatialIndexed(true);
+ }
+
+ // 必须实现 getBoundingBox
+ Rect getBoundingBox() const override {
+ Vec2 pos = getPosition();
+ return Rect(pos.x - 25, pos.y - 25, 50, 50);
+ }
+};
+```
+
+### 查询碰撞
+
+```cpp
+// 在场景中查询所有碰撞
+auto collisions = scene->queryCollisions();
+
+for (const auto& [nodeA, nodeB] : collisions) {
+ // 处理碰撞
+ handleCollision(nodeA, nodeB);
+}
+```
+
+## 完整示例
+
+参考 `examples/push_box/PlayScene.cpp` 中的节点使用:
+
+```cpp
+void PlayScene::onEnter() {
+ Scene::onEnter();
+
+ auto& resources = app.resources();
+
+ // 加载纹理资源
+ texWall_ = resources.loadTexture("assets/images/wall.gif");
+ texBox_ = resources.loadTexture("assets/images/box.gif");
+
+ // 创建地图层
+ mapLayer_ = makePtr();
+ addChild(mapLayer_);
+
+ // 创建地图元素
+ for (int y = 0; y < mapHeight; ++y) {
+ for (int x = 0; x < mapWidth; ++x) {
+ char cell = map_[y][x];
+
+ if (cell == '#') {
+ auto wall = Sprite::create(texWall_);
+ wall->setPosition(x * tileSize, y * tileSize);
+ mapLayer_->addChild(wall);
+ }
+ else if (cell == '$') {
+ auto box = Sprite::create(texBox_);
+ box->setPosition(x * tileSize, y * tileSize);
+ mapLayer_->addChild(box);
+ }
+ }
+ }
+
+ // 创建UI文本
+ font28_ = resources.loadFont("assets/font.ttf", 28, true);
+ levelText_ = Text::create("Level: 1", font28_);
+ levelText_->setPosition(50, 30);
+ addChild(levelText_);
+}
```
## 下一步
-- [04. 资源管理](04_Resource_Management.md)
-- [05. 输入处理](05_Input_Handling.md)
+- [04. 资源管理](./04_Resource_Management.md) - 深入了解资源加载
+- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
+- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
diff --git a/docs/API_Tutorial/04_Resource_Management.md b/docs/API_Tutorial/04_Resource_Management.md
index 18d8108..fdcc3b3 100644
--- a/docs/API_Tutorial/04_Resource_Management.md
+++ b/docs/API_Tutorial/04_Resource_Management.md
@@ -1,148 +1,167 @@
-# Extra2D API 教程 - 04. 资源管理
+# 04. 资源管理
+
+Extra2D 提供了统一的资源管理系统,用于加载和管理游戏中的各种资源。
## 资源管理器
-Extra2D 使用资源管理器来统一加载和管理资源。
-
-### 获取资源管理器
+通过 `Application::instance().resources()` 访问资源管理器:
```cpp
-auto &resources = Application::instance().resources();
+auto& resources = Application::instance().resources();
```
-## 字体资源
+## 支持的资源类型
-### 加载字体
+| 资源类型 | 加载方法 | 说明 |
+|---------|---------|------|
+| 纹理 | `loadTexture()` | 图片文件 (PNG, JPG, etc.) |
+| 字体 | `loadFont()` | TrueType 字体文件 |
+| 音频 | `loadSound()` / `loadMusic()` | 音频文件 |
-```cpp
-// 加载字体(路径,大小,使用后备字体)
-auto font = resources.loadFont("assets/font.ttf", 48, true);
+## 纹理加载
-if (!font) {
- E2D_LOG_ERROR("字体加载失败!");
-}
-```
-
-### 使用字体
-
-```cpp
-void onRender(RenderBackend &renderer) override {
- if (font) {
- renderer.drawText(*font, "Hello World", Vec2(100.0f, 100.0f),
- Colors::White);
- }
-}
-```
-
-## 纹理资源
-
-### 加载纹理
+### 基本用法
```cpp
// 加载纹理
-auto texture = resources.loadTexture("assets/player.png");
+auto texture = resources.loadTexture("assets/images/player.png");
-if (!texture) {
- E2D_LOG_ERROR("纹理加载失败!");
+if (texture) {
+ // 创建精灵
+ auto sprite = Sprite::create(texture);
+ addChild(sprite);
}
```
-### 创建精灵
+### 纹理缓存
+
+资源管理器会自动缓存已加载的纹理,多次加载同一文件会返回缓存的实例:
```cpp
-auto sprite = Sprite::create(texture);
-sprite->setPosition(Vec2(640.0f, 360.0f));
-addChild(sprite);
+// 第一次加载 - 从文件读取
+auto tex1 = resources.loadTexture("assets/image.png");
+
+// 第二次加载 - 返回缓存
+auto tex2 = resources.loadTexture("assets/image.png");
+
+// tex1 和 tex2 指向同一个纹理对象
```
-## 音效资源
+## 字体加载
-### 加载音效
+### 基本用法
```cpp
-// 加载音效
-auto sound = resources.loadSound("assets/jump.wav");
+// 加载字体(指定字号)
+auto font24 = resources.loadFont("assets/font.ttf", 24, true);
-// 播放音效
-sound->play();
-
-// 循环播放
-sound->play(true);
-
-// 停止播放
-sound->stop();
+// 创建文本
+auto text = Text::create("Hello World", font24);
+addChild(text);
```
-## 资源路径解析
+### 字体后备
-Extra2D 的资源管理器支持多平台路径解析:
-
-### 路径优先级
-
-1. **原始路径**: `assets/font.ttf`
-2. **romfs 路径**: `romfs:/assets/font.ttf` (Switch)
-3. **sdmc 路径**: `sdmc:/assets/font.ttf` (Switch SD卡)
-4. **可执行文件相对路径** (Windows)
-
-### 使用示例
+支持设置后备字体,当主字体缺少某些字符时自动使用后备字体:
```cpp
-// 所有平台使用相同的路径
-auto font = resources.loadFont("assets/font.ttf", 48, true);
-auto texture = resources.loadTexture("assets/images/player.png");
-auto sound = resources.loadSound("assets/audio/jump.wav");
+// 加载主字体和后备字体
+auto mainFont = resources.loadFont("assets/main.ttf", 24, true);
+auto fallbackFont = resources.loadFont("assets/fallback.ttf", 24, true);
+
+// 设置后备字体
+mainFont->setFallback(fallbackFont);
+```
+
+## 资源路径
+
+### 路径格式
+
+```cpp
+// 相对路径(相对于工作目录)
+auto tex = resources.loadTexture("assets/images/player.png");
+
+// Switch 平台使用 romfs
+auto tex = resources.loadTexture("romfs:/images/player.png");
+
+// SD 卡路径
+auto tex = resources.loadTexture("sdmc:/switch/game/images/player.png");
+```
+
+### 路径辅助函数
+
+```cpp
+// 获取平台特定的资源路径
+std::string path = ResourceManager::getPlatformPath("images/player.png");
+// Windows: "assets/images/player.png"
+// Switch: "romfs:/images/player.png"
+```
+
+## 资源释放
+
+### 自动释放
+
+资源使用智能指针管理,当没有引用时会自动释放:
+
+```cpp
+{
+ auto tex = resources.loadTexture("assets/temp.png");
+ // 使用纹理...
+} // 超出作用域,如果没有其他引用,纹理自动释放
+```
+
+### 手动清理缓存
+
+```cpp
+// 清理未使用的资源
+resources.cleanupUnused();
+
+// 清空所有缓存(谨慎使用)
+resources.clearCache();
```
## 完整示例
+参考 `examples/push_box/StartScene.cpp`:
+
```cpp
-class GameScene : public Scene {
-public:
- void onEnter() override {
- Scene::onEnter();
-
- auto &resources = Application::instance().resources();
-
- // 加载字体
- titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
- infoFont_ = resources.loadFont("assets/font.ttf", 24, true);
-
- // 加载纹理
- playerTexture_ = resources.loadTexture("assets/player.png");
- enemyTexture_ = resources.loadTexture("assets/enemy.png");
-
- // 创建精灵
- player_ = Sprite::create(playerTexture_);
- player_->setPosition(Vec2(640.0f, 360.0f));
- addChild(player_);
+void StartScene::onEnter() {
+ Scene::onEnter();
+
+ auto& app = Application::instance();
+ auto& resources = app.resources();
+
+ // 加载背景纹理
+ auto bgTex = resources.loadTexture("assets/images/start.jpg");
+ if (bgTex) {
+ auto background = Sprite::create(bgTex);
+ background->setAnchor(0.0f, 0.0f);
+ addChild(background);
}
- void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
-
- // 绘制文字
- if (titleFont_) {
- renderer.drawText(*titleFont_, "Game Title",
- Vec2(50.0f, 50.0f), Colors::White);
- }
-
- if (infoFont_) {
- std::string fps = "FPS: " + std::to_string(Application::instance().fps());
- renderer.drawText(*infoFont_, fps,
- Vec2(50.0f, 100.0f), Colors::Yellow);
- }
+ // 加载音效图标纹理
+ auto soundOn = resources.loadTexture("assets/images/soundon.png");
+ auto soundOff = resources.loadTexture("assets/images/soundoff.png");
+ if (soundOn && soundOff) {
+ soundIcon_ = Sprite::create(g_SoundOpen ? soundOn : soundOff);
+ addChild(soundIcon_);
}
-private:
- Ptr titleFont_;
- Ptr infoFont_;
- Ptr playerTexture_;
- Ptr enemyTexture_;
- Ptr player_;
-};
+ // 加载字体
+ font_ = resources.loadFont("assets/font.ttf", 28, true);
+
+ // 创建按钮...
+}
```
+## 最佳实践
+
+1. **预加载资源** - 在场景 `onEnter()` 中加载所需资源
+2. **检查资源有效性** - 始终检查加载结果是否为 nullptr
+3. **复用资源** - 多次使用同一资源时保存指针,避免重复加载
+4. **合理设置字号** - 字体加载时会生成对应字号的图集
+
## 下一步
-- [05. 输入处理](05_Input_Handling.md)
-- [06. 碰撞检测](06_Collision_Detection.md)
+- [05. 输入处理](./05_Input_Handling.md) - 学习输入处理
+- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
diff --git a/docs/API_Tutorial/05_Input_Handling.md b/docs/API_Tutorial/05_Input_Handling.md
index 24ed607..d280294 100644
--- a/docs/API_Tutorial/05_Input_Handling.md
+++ b/docs/API_Tutorial/05_Input_Handling.md
@@ -1,216 +1,189 @@
-# Extra2D API 教程 - 05. 输入处理
+# 05. 输入处理
-## 输入系统
+Extra2D 提供了统一的输入处理系统,支持手柄、键盘等多种输入设备。
-Extra2D 提供统一的输入处理接口,支持键盘和游戏手柄。
+## 输入管理器
-### 获取输入管理器
+通过 `Application::instance().input()` 访问输入管理器:
```cpp
-auto &input = Application::instance().input();
+auto& input = Application::instance().input();
```
-## 游戏手柄输入
+## 按键检测
-Extra2D 提供了 `GamepadButton` 和 `GamepadAxis` 命名空间来映射 SDL 按键。
-
-### 检测按键按下
+### 检测方法
```cpp
-void onUpdate(float dt) override {
- auto &input = Application::instance().input();
-
- // 检测按键按下(每帧只触发一次)
- if (input.isButtonPressed(GamepadButton::A)) {
- // A 键被按下
- jump();
- }
-
- if (input.isButtonPressed(GamepadButton::B)) {
- // B 键被按下
- attack();
- }
+// 按键是否按下(持续触发)
+if (input.isButtonDown(GamepadButton::A)) {
+ // 每帧都会触发,只要按键保持按下
+}
+
+// 按键是否刚按下(单次触发)
+if (input.isButtonPressed(GamepadButton::A)) {
+ // 只在按下瞬间触发一次
+}
+
+// 按键是否刚释放
+if (input.isButtonReleased(GamepadButton::A)) {
+ // 只在释放瞬间触发一次
}
```
-### 检测按键按住
+### 常用按键
-```cpp
-void onUpdate(float dt) override {
- auto &input = Application::instance().input();
-
- // 检测按键按住(每帧都触发)
- if (input.isButtonDown(GamepadButton::DPadLeft)) {
- // 左方向键按住
- moveLeft();
- }
-
- if (input.isButtonDown(GamepadButton::DPadRight)) {
- // 右方向键按住
- moveRight();
- }
-}
-```
-
-### 按键映射表
-
-| Extra2D 枚举 | 对应按键 |
-|-------------|----------|
-| `GamepadButton::A` | A 键 (Xbox) / × 键 (PlayStation) |
-| `GamepadButton::B` | B 键 (Xbox) / ○ 键 (PlayStation) |
-| `GamepadButton::X` | X 键 (Xbox) / □ 键 (PlayStation) |
-| `GamepadButton::Y` | Y 键 (Xbox) / △ 键 (PlayStation) |
-| `GamepadButton::LeftBumper` | 左肩键 (LB/L1) |
-| `GamepadButton::RightBumper` | 右肩键 (RB/R1) |
-| `GamepadButton::Back` | 返回键 (View/Share) |
-| `GamepadButton::Start` | 开始键 (Menu/Options) |
-| `GamepadButton::Guide` | 主页键 (Xbox/PS) |
-| `GamepadButton::LeftThumb` | 左摇杆按下 (L3) |
-| `GamepadButton::RightThumb` | 右摇杆按下 (R3) |
-| `GamepadButton::DPadUp` | 方向键上 |
-| `GamepadButton::DPadDown` | 方向键下 |
-| `GamepadButton::DPadLeft` | 方向键左 |
-| `GamepadButton::DPadRight` | 方向键右 |
-
-### PlayStation 风格别名
-
-| Extra2D 枚举 | 对应按键 |
-|-------------|----------|
-| `GamepadButton::Cross` | A |
-| `GamepadButton::Circle` | B |
-| `GamepadButton::Square` | X |
-| `GamepadButton::Triangle` | Y |
+| 按键 | 说明 | Switch 对应 |
+|------|------|------------|
+| `GamepadButton::A` | A 键 | A 键 |
+| `GamepadButton::B` | B 键 | B 键 |
+| `GamepadButton::X` | X 键 | X 键 |
+| `GamepadButton::Y` | Y 键 | Y 键 |
+| `GamepadButton::Start` | 开始键 | + 键 |
+| `GamepadButton::Select` | 选择键 | - 键 |
+| `GamepadButton::DPadUp` | 方向上 | 方向键上 |
+| `GamepadButton::DPadDown` | 方向下 | 方向键下 |
+| `GamepadButton::DPadLeft` | 方向左 | 方向键左 |
+| `GamepadButton::DPadRight` | 方向右 | 方向键右 |
+| `GamepadButton::LeftStick` | 左摇杆按下 | L3 |
+| `GamepadButton::RightStick` | 右摇杆按下 | R3 |
+| `GamepadButton::LeftShoulder` | 左肩键 | L |
+| `GamepadButton::RightShoulder` | 右肩键 | R |
## 摇杆输入
### 获取摇杆值
```cpp
-void onUpdate(float dt) override {
- auto &input = Application::instance().input();
-
- // 左摇杆(范围 -1.0 到 1.0)
- float leftX = input.getAxis(GamepadAxis::LeftX);
- float leftY = input.getAxis(GamepadAxis::LeftY);
-
- // 右摇杆
- float rightX = input.getAxis(GamepadAxis::RightX);
- float rightY = input.getAxis(GamepadAxis::RightY);
-
- // 使用摇杆值移动
- if (std::abs(leftX) > 0.1f || std::abs(leftY) > 0.1f) {
- Vec2 velocity(leftX * speed, leftY * speed);
- player->setPosition(player->getPosition() + velocity * dt);
- }
-}
+// 获取左摇杆位置(范围 -1.0 到 1.0)
+Vec2 leftStick = input.getLeftStick();
+
+// 获取右摇杆位置
+Vec2 rightStick = input.getRightStick();
+
+// 应用摇杆输入
+float speed = 200.0f;
+player->setPosition(player->getPosition() + leftStick * speed * dt);
```
-### 摇杆轴映射表
-
-| Extra2D 枚举 | 说明 |
-|-------------|------|
-| `GamepadAxis::LeftX` | 左摇杆 X 轴 |
-| `GamepadAxis::LeftY` | 左摇杆 Y 轴 |
-| `GamepadAxis::RightX` | 右摇杆 X 轴 |
-| `GamepadAxis::RightY` | 右摇杆 Y 轴 |
-| `GamepadAxis::LeftTrigger` | 左扳机 (LT/L2) |
-| `GamepadAxis::RightTrigger` | 右扳机 (RT/R2) |
-
-## 键盘输入
-
-### 检测键盘按键
+### 摇杆死区
```cpp
-void onUpdate(float dt) override {
- auto &input = Application::instance().input();
-
- // 检测按键按下
- if (input.isKeyPressed(SDLK_SPACE)) {
- jump();
- }
-
- // 检测按键按住
- if (input.isKeyDown(SDLK_LEFT)) {
- moveLeft();
- }
-
- if (input.isKeyDown(SDLK_RIGHT)) {
- moveRight();
- }
-}
+// 设置摇杆死区(默认 0.15)
+input.setStickDeadZone(0.2f);
```
## 完整示例
+### 菜单导航
+
+参考 `examples/push_box/StartScene.cpp`:
+
```cpp
-class Player : public Node {
-public:
- void onUpdate(float dt) override {
- Node::onUpdate(dt);
-
- auto &input = Application::instance().input();
- Vec2 velocity(0.0f, 0.0f);
-
- // 方向键移动
- if (input.isButtonDown(GamepadButton::DPadLeft)) {
- velocity.x = -speed_;
- } else if (input.isButtonDown(GamepadButton::DPadRight)) {
- velocity.x = speed_;
- }
-
- if (input.isButtonDown(GamepadButton::DPadUp)) {
- velocity.y = -speed_;
- } else if (input.isButtonDown(GamepadButton::DPadDown)) {
- velocity.y = speed_;
- }
-
- // 摇杆移动(如果方向键没有按下)
- if (velocity.x == 0.0f && velocity.y == 0.0f) {
- float axisX = input.getAxis(GamepadAxis::LeftX);
- float axisY = input.getAxis(GamepadAxis::LeftY);
-
- if (std::abs(axisX) > 0.1f) {
- velocity.x = axisX * speed_;
- }
- if (std::abs(axisY) > 0.1f) {
- velocity.y = axisY * speed_;
- }
- }
-
- // 应用移动
- Vec2 pos = getPosition();
- pos = pos + velocity * dt;
- setPosition(pos);
-
- // 动作键
- if (input.isButtonPressed(GamepadButton::A)) {
- jump();
- }
-
- if (input.isButtonPressed(GamepadButton::B)) {
- attack();
- }
-
- // 退出游戏
- if (input.isButtonPressed(GamepadButton::Start)) {
- Application::instance().quit();
- }
+void StartScene::onUpdate(float dt) {
+ Scene::onUpdate(dt);
+
+ auto& input = Application::instance().input();
+
+ // 方向键上下切换选择
+ if (input.isButtonPressed(GamepadButton::DPadUp)) {
+ selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
+ updateMenuColors();
+ }
+ else if (input.isButtonPressed(GamepadButton::DPadDown)) {
+ selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
+ updateMenuColors();
}
-
-private:
- float speed_ = 200.0f;
-
- void jump() {
- // 跳跃逻辑
+
+ // A键确认
+ if (input.isButtonPressed(GamepadButton::A)) {
+ executeMenuItem();
}
-
- void attack() {
- // 攻击逻辑
+
+ // X键切换音效
+ if (input.isButtonPressed(GamepadButton::X)) {
+ g_SoundOpen = !g_SoundOpen;
+ AudioManager::instance().setEnabled(g_SoundOpen);
+ updateSoundIcon();
}
-};
+}
```
+### 玩家移动
+
+```cpp
+void Player::update(float dt) {
+ auto& input = Application::instance().input();
+
+ Vec2 moveDir;
+
+ // 方向键移动
+ if (input.isButtonDown(GamepadButton::DPadLeft)) {
+ moveDir.x -= 1;
+ }
+ if (input.isButtonDown(GamepadButton::DPadRight)) {
+ moveDir.x += 1;
+ }
+ if (input.isButtonDown(GamepadButton::DPadUp)) {
+ moveDir.y -= 1;
+ }
+ if (input.isButtonDown(GamepadButton::DPadDown)) {
+ moveDir.y += 1;
+ }
+
+ // 摇杆移动
+ Vec2 stick = input.getLeftStick();
+ if (stick.length() > 0.1f) {
+ moveDir = stick;
+ }
+
+ // 应用移动
+ if (moveDir.length() > 0) {
+ moveDir.normalize();
+ setPosition(getPosition() + moveDir * speed_ * dt);
+ }
+
+ // 跳跃
+ if (input.isButtonPressed(GamepadButton::A)) {
+ jump();
+ }
+}
+```
+
+## 输入映射
+
+### 自定义按键映射
+
+```cpp
+// 定义动作
+enum class Action {
+ Jump,
+ Attack,
+ Pause
+};
+
+// 映射按键到动作
+std::unordered_map actionMap = {
+ {Action::Jump, GamepadButton::A},
+ {Action::Attack, GamepadButton::B},
+ {Action::Pause, GamepadButton::Start}
+};
+
+// 检查动作
+bool isActionPressed(Action action) {
+ auto& input = Application::instance().input();
+ return input.isButtonPressed(actionMap[action]);
+}
+```
+
+## 最佳实践
+
+1. **使用 isButtonPressed 进行菜单操作** - 避免持续触发
+2. **使用 isButtonDown 进行移动控制** - 实现流畅移动
+3. **支持多种输入方式** - 同时支持方向键和摇杆
+4. **添加输入缓冲** - 提升操作手感
+
## 下一步
-- [06. 碰撞检测](06_Collision_Detection.md)
-- [07. UI 系统](07_UI_System.md)
+- [06. 碰撞检测](./06_Collision_Detection.md) - 学习碰撞检测系统
+- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用
diff --git a/docs/API_Tutorial/06_Collision_Detection.md b/docs/API_Tutorial/06_Collision_Detection.md
index 8c213bd..1fd85bf 100644
--- a/docs/API_Tutorial/06_Collision_Detection.md
+++ b/docs/API_Tutorial/06_Collision_Detection.md
@@ -1,81 +1,90 @@
-# Extra2D API 教程 - 06. 碰撞检测
+# 06. 碰撞检测
-## 空间索引系统
+Extra2D 提供了基于空间索引的高效碰撞检测系统,支持四叉树和空间哈希两种策略。
-Extra2D 内置了空间索引系统,用于高效地进行碰撞检测。
+## 完整示例
-### 启用空间索引
+参考示例代码:
+- `examples/collision_demo/main.cpp` - 基础碰撞检测演示
+- `examples/spatial_index_demo/main.cpp` - 空间索引性能演示
+
+## 启用碰撞检测
+
+### 1. 创建可碰撞节点
```cpp
-void onEnter() override {
- Scene::onEnter();
-
- // 启用空间索引
- setSpatialIndexingEnabled(true);
-}
-```
-
-## 碰撞节点
-
-### 创建可碰撞节点
-
-```cpp
-class PhysicsNode : public Node {
+class CollidableBox : public Node {
public:
- PhysicsNode(float size, const Color &color)
- : size_(size), color_(color), isColliding_(false) {
- // 启用空间索引(关键!)
+ CollidableBox(float width, float height, const Color& color)
+ : width_(width), height_(height), color_(color), isColliding_(false) {
+ // 启用空间索引 - 这是关键!
setSpatialIndexed(true);
}
- // 必须实现 getBoundingBox()
+ // 必须实现 getBoundingBox 方法
Rect getBoundingBox() const override {
Vec2 pos = getPosition();
- return Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_);
+ return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
}
void setColliding(bool colliding) { isColliding_ = colliding; }
- void onRender(RenderBackend &renderer) override {
+ void onRender(RenderBackend& renderer) override {
Vec2 pos = getPosition();
// 碰撞时变红色
- Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_;
- renderer.fillRect(Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_),
- fillColor);
+ Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
+ renderer.fillRect(
+ Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
+ fillColor
+ );
+
+ // 绘制边框
+ Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
+ : Color(1.0f, 1.0f, 1.0f, 0.5f);
+ renderer.drawRect(
+ Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
+ borderColor, 2.0f
+ );
}
private:
- float size_;
+ float width_, height_;
Color color_;
bool isColliding_;
};
```
-## 碰撞检测
-
-### 查询所有碰撞
+### 2. 执行碰撞检测
```cpp
-void performCollisionDetection() {
- // 清除之前的碰撞状态
- for (auto &node : nodes_) {
- node->setColliding(false);
- }
-
- // 查询所有碰撞(使用空间索引)
- auto collisions = queryCollisions();
-
- // 标记碰撞的节点
- for (const auto &[nodeA, nodeB] : collisions) {
- if (auto boxA = dynamic_cast(nodeA)) {
- boxA->setColliding(true);
+class GameScene : public Scene {
+public:
+ void onUpdate(float dt) override {
+ Scene::onUpdate(dt);
+
+ // 清除之前的碰撞状态
+ for (auto& box : boxes_) {
+ box->setColliding(false);
}
- if (auto boxB = dynamic_cast(nodeB)) {
- boxB->setColliding(true);
+
+ // 使用场景的空间索引查询所有碰撞
+ auto collisions = queryCollisions();
+
+ // 处理碰撞
+ for (const auto& [nodeA, nodeB] : collisions) {
+ if (auto boxA = dynamic_cast(nodeA)) {
+ boxA->setColliding(true);
+ }
+ if (auto boxB = dynamic_cast(nodeB)) {
+ boxB->setColliding(true);
+ }
}
}
-}
+
+private:
+ std::vector> boxes_;
+};
```
## 空间索引策略
@@ -83,141 +92,105 @@ void performCollisionDetection() {
### 切换策略
```cpp
-void onEnter() override {
- Scene::onEnter();
-
- // 启用空间索引
- setSpatialIndexingEnabled(true);
-
- // 设置空间索引策略
- auto &spatialManager = getSpatialManager();
- spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
- // 或
- spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
-}
+// 获取空间管理器
+auto& spatialManager = getSpatialManager();
-// 切换策略
-void toggleStrategy() {
- auto &spatialManager = getSpatialManager();
- SpatialStrategy current = spatialManager.getCurrentStrategy();
-
- if (current == SpatialStrategy::QuadTree) {
- spatialManager.setStrategy(SpatialStrategy::SpatialHash);
- } else {
- spatialManager.setStrategy(SpatialStrategy::QuadTree);
- }
-}
+// 切换到四叉树
+spatialManager.setStrategy(SpatialStrategy::QuadTree);
+
+// 切换到空间哈希
+spatialManager.setStrategy(SpatialStrategy::SpatialHash);
+
+// 获取当前策略名称
+const char* name = spatialManager.getStrategyName();
```
### 策略对比
| 策略 | 适用场景 | 特点 |
-|------|----------|------|
-| QuadTree | 节点分布不均匀 | 适合稀疏场景 |
-| SpatialHash | 节点分布均匀 | 适合密集场景 |
+|------|---------|------|
+| QuadTree | 节点分布不均匀 | 分层划分,适合稀疏分布 |
+| SpatialHash | 节点分布均匀 | 均匀网格,适合密集分布 |
-## 完整示例
+## 性能演示
+
+`examples/spatial_index_demo/main.cpp` 展示了空间索引的性能优势:
```cpp
-class CollisionScene : public Scene {
+class SpatialIndexDemoScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
- // 启用空间索引
- setSpatialIndexingEnabled(true);
-
- // 创建碰撞节点
+ // 创建100个碰撞节点
createNodes(100);
+
+ E2D_LOG_INFO("创建了 {} 个碰撞节点", nodes_.size());
+ E2D_LOG_INFO("空间索引已启用: {}", isSpatialIndexingEnabled());
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
- // 更新节点位置
- for (auto &node : nodes_) {
- node->update(dt);
+ // 更新所有节点位置
+ for (auto& node : nodes_) {
+ node->update(dt, screenWidth_, screenHeight_);
}
- // 执行碰撞检测
+ // 使用空间索引进行碰撞检测
performCollisionDetection();
- }
-
- void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
- // 绘制碰撞统计
- std::string text = "Collisions: " + std::to_string(collisionCount_);
- // ...
+ // 按 X 键切换索引策略
+ auto& input = Application::instance().input();
+ if (input.isButtonPressed(GamepadButton::X)) {
+ toggleSpatialStrategy();
+ }
}
private:
- std::vector> nodes_;
- size_t collisionCount_ = 0;
-
- void createNodes(size_t count) {
- for (size_t i = 0; i < count; ++i) {
- auto node = makePtr(20.0f, Color(0.5f, 0.5f, 0.9f, 0.7f));
- node->setPosition(randomPosition());
- addChild(node);
- nodes_.push_back(node);
- }
- }
-
void performCollisionDetection() {
- // 清除碰撞状态
- for (auto &node : nodes_) {
+ // 清除之前的碰撞状态
+ for (auto& node : nodes_) {
node->setColliding(false);
}
- // 查询碰撞
+ // 使用引擎自带的空间索引进行碰撞检测
auto collisions = queryCollisions();
- collisionCount_ = collisions.size();
- // 标记碰撞节点
- for (const auto &[nodeA, nodeB] : collisions) {
- if (auto boxA = dynamic_cast(nodeA)) {
+ // 标记碰撞的节点
+ for (const auto& [nodeA, nodeB] : collisions) {
+ if (auto boxA = dynamic_cast(nodeA)) {
boxA->setColliding(true);
}
- if (auto boxB = dynamic_cast(nodeB)) {
+ if (auto boxB = dynamic_cast(nodeB)) {
boxB->setColliding(true);
}
}
}
-};
-```
-
-## 注意事项
-
-### 必须调用 Scene::onEnter()
-
-```cpp
-void onEnter() override {
- Scene::onEnter(); // 必须调用!
- // 否则子节点无法注册到空间索引
- setSpatialIndexingEnabled(true);
-}
-```
-
-### 必须实现 getBoundingBox()
-
-```cpp
-class MyNode : public Node {
-public:
- MyNode() {
- setSpatialIndexed(true);
- }
-
- // 必须实现!
- Rect getBoundingBox() const override {
- Vec2 pos = getPosition();
- return Rect(pos.x - width_/2, pos.y - height_/2, width_, height_);
+ void toggleSpatialStrategy() {
+ auto& spatialManager = getSpatialManager();
+ SpatialStrategy currentStrategy = spatialManager.getCurrentStrategy();
+
+ if (currentStrategy == SpatialStrategy::QuadTree) {
+ spatialManager.setStrategy(SpatialStrategy::SpatialHash);
+ E2D_LOG_INFO("切换到空间哈希策略");
+ } else {
+ spatialManager.setStrategy(SpatialStrategy::QuadTree);
+ E2D_LOG_INFO("切换到四叉树策略");
+ }
}
};
```
+## 关键要点
+
+1. **必须调用 `setSpatialIndexed(true)`** - 启用节点的空间索引
+2. **必须实现 `getBoundingBox()`** - 返回准确的边界框
+3. **在 `onEnter()` 中调用 `Scene::onEnter()`** - 确保节点正确注册到空间索引
+4. **使用 `queryCollisions()`** - 自动利用空间索引优化检测
+
## 下一步
-- [07. UI 系统](07_UI_System.md)
-- [08. 音频系统](08_Audio_System.md)
+- [07. UI 系统](./07_UI_System.md) - 学习 UI 控件使用
+- [08. 音频系统](./08_Audio_System.md) - 学习音频播放
diff --git a/docs/API_Tutorial/07_UI_System.md b/docs/API_Tutorial/07_UI_System.md
index 1b82ad2..848a1f6 100644
--- a/docs/API_Tutorial/07_UI_System.md
+++ b/docs/API_Tutorial/07_UI_System.md
@@ -1,337 +1,606 @@
-# Extra2D API 教程 - 07. UI 系统
+# 07. UI 系统
-## 按钮 (Button)
+Extra2D 提供了一套完整的 UI 系统,支持按钮、文本、标签、复选框、单选按钮、滑块、进度条等常用控件。
-Extra2D 提供 Button 组件用于创建交互式按钮。
+## UI 控件类型
+
+```
+Widget (UI控件基类)
+├── Button (按钮)
+├── Text (文本)
+├── Label (标签)
+├── CheckBox (复选框)
+├── RadioButton (单选按钮)
+├── Slider (滑块)
+└── ProgressBar (进度条)
+```
+
+## 坐标空间
+
+UI 控件支持三种坐标空间:
+
+```cpp
+enum class CoordinateSpace {
+ World, // 世界空间 - 随相机移动(默认)
+ Screen, // 屏幕空间 - 固定位置,不随相机移动
+ Camera, // 相机空间 - 相对于相机位置的偏移
+};
+```
+
+设置坐标空间:
+
+```cpp
+// 屏幕空间(UI 常用)
+button->setCoordinateSpace(CoordinateSpace::Screen);
+button->setScreenPosition(100.0f, 50.0f);
+
+// 或使用链式调用
+button->withCoordinateSpace(CoordinateSpace::Screen)
+ ->withScreenPosition(100.0f, 50.0f);
+```
+
+## 通用链式调用方法
+
+所有 UI 组件都支持以下链式调用方法:
+
+```cpp
+widget->withPosition(x, y) // 设置位置
+ ->withAnchor(x, y) // 设置锚点 (0-1)
+ ->withCoordinateSpace(space) // 设置坐标空间
+ ->withScreenPosition(x, y) // 设置屏幕位置
+ ->withCameraOffset(x, y); // 设置相机偏移
+```
+
+## 按钮(Button)
### 创建按钮
```cpp
-// 创建按钮
+auto& resources = Application::instance().resources();
+auto font = resources.loadFont("assets/font.ttf", 24);
+
+// 方式1:简单创建
auto button = Button::create();
+button->setText("点击我");
+button->setFont(font);
+button->setPosition(Vec2(400, 300));
-// 设置位置
-button->setPosition(Vec2(640.0f, 360.0f));
+// 方式2:链式调用创建
+auto button = Button::create()
+ ->withText("点击我")
+ ->withFont(font)
+ ->withPosition(400, 300)
+ ->withSize(200, 60)
+ ->withTextColor(Colors::White)
+ ->withBackgroundColor(Colors::Blue, Colors::Green, Colors::Red)
+ ->withBorder(Colors::White, 2.0f);
-// 设置锚点(中心)
-button->setAnchor(0.5f, 0.5f);
+// 设置点击回调
+button->setOnClick([]() {
+ E2D_LOG_INFO("按钮被点击!");
+});
-// 添加到场景
addChild(button);
```
-### 设置按钮文本
+### 按钮属性设置
```cpp
-// 加载字体
-auto font = resources.loadFont("assets/font.ttf", 28, true);
-
-// 设置按钮字体和文本
+// 文本和字体
+button->setText("新文本");
button->setFont(font);
-button->setText("点击我");
-button->setTextColor(Colors::Black);
-```
+button->setTextColor(Colors::White);
-### 设置按钮样式
+// 尺寸和内边距
+button->setCustomSize(200.0f, 60.0f);
+button->setPadding(Vec2(10.0f, 5.0f));
-```cpp
-// 设置背景颜色(普通、悬停、按下)
+// 背景颜色(正常、悬停、按下三种状态)
button->setBackgroundColor(
- Colors::White, // 普通状态
- Colors::LightGray, // 悬停状态
- Colors::Gray // 按下状态
+ Colors::Blue, // 正常状态
+ Colors::Green, // 悬停状态
+ Colors::Red // 按下状态
);
-// 设置边框
-button->setBorder(Colors::Black, 2.0f);
+// 边框
+button->setBorder(Colors::White, 2.0f);
-// 设置内边距
-button->setPadding(Vec2(20.0f, 10.0f));
+// 圆角
+button->setRoundedCornersEnabled(true);
+button->setCornerRadius(8.0f);
-// 设置固定大小
-button->setCustomSize(200.0f, 50.0f);
+// 图片背景
+button->setBackgroundImage(normalTex, hoverTex, pressedTex);
+button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
+
+// 悬停光标
+button->setHoverCursor(CursorShape::Hand);
```
-### 透明按钮
+### 图片缩放模式
```cpp
-// 创建透明按钮(仅文本可点击)
-auto button = Button::create();
-button->setFont(font);
-button->setText("菜单项");
-button->setTextColor(Colors::Black);
+enum class ImageScaleMode {
+ Original, // 使用原图大小
+ Stretch, // 拉伸填充
+ ScaleFit, // 等比缩放,保持完整显示
+ ScaleFill // 等比缩放,填充整个区域(可能裁剪)
+};
+```
-// 透明背景
-button->setBackgroundColor(
- Colors::Transparent,
- Colors::Transparent,
- Colors::Transparent
+### 透明按钮(菜单项)
+
+```cpp
+// 创建纯文本按钮(透明背景,用于菜单)
+auto menuBtn = Button::create();
+menuBtn->setFont(font);
+menuBtn->setText("新游戏");
+menuBtn->setTextColor(Colors::Black);
+menuBtn->setBackgroundColor(
+ Colors::Transparent, // 正常
+ Colors::Transparent, // 悬停
+ Colors::Transparent // 按下
);
-
-// 无边框
-button->setBorder(Colors::Transparent, 0.0f);
-
-// 无内边距
-button->setPadding(Vec2(0.0f, 0.0f));
+menuBtn->setBorder(Colors::Transparent, 0.0f);
+menuBtn->setPadding(Vec2(0.0f, 0.0f));
+menuBtn->setCustomSize(200.0f, 40.0f);
+menuBtn->setAnchor(0.5f, 0.5f); // 中心锚点
+menuBtn->setPosition(centerX, centerY);
+addChild(menuBtn);
```
-## 菜单系统
+## 文本(Text)
-### 创建菜单
+### 创建文本
```cpp
-class MenuScene : public Scene {
+// 方式1:简单创建
+auto text = Text::create("Hello World", font);
+text->setPosition(Vec2(100, 50));
+
+// 方式2:链式调用
+auto text = Text::create("Hello World")
+ ->withFont(font)
+ ->withPosition(100, 50)
+ ->withTextColor(Colors::White)
+ ->withFontSize(24)
+ ->withAlignment(Alignment::Center);
+
+addChild(text);
+```
+
+### 文本属性设置
+
+```cpp
+// 设置文本
+text->setText("新文本");
+text->setFormat("Score: %d", score); // 格式化文本
+
+// 字体和颜色
+text->setFont(font);
+text->setTextColor(Colors::White);
+text->setFontSize(24);
+
+// 对齐方式
+text->setAlignment(Alignment::Left); // 水平:左/中/右
+text->setVerticalAlignment(VerticalAlignment::Middle); // 垂直:上/中/下
+
+// 获取文本尺寸
+Size size = text->getTextSize();
+float lineHeight = text->getLineHeight();
+```
+
+### 对齐方式枚举
+
+```cpp
+enum class Alignment { Left, Center, Right };
+enum class VerticalAlignment { Top, Middle, Bottom };
+```
+
+## 标签(Label)
+
+Label 是功能更丰富的文本组件,支持阴影、描边、多行文本。
+
+### 创建标签
+
+```cpp
+// 创建标签
+auto label = Label::create("玩家名称", font);
+label->setPosition(Vec2(100, 50));
+label->setTextColor(Colors::White);
+
+// 链式调用
+auto label = Label::create("玩家名称")
+ ->withFont(font)
+ ->withPosition(100, 50)
+ ->withTextColor(Colors::White)
+ ->withFontSize(24);
+
+addChild(label);
+```
+
+### 标签特效
+
+```cpp
+// 阴影
+label->setShadowEnabled(true);
+label->setShadowColor(Colors::Black);
+label->setShadowOffset(Vec2(2.0f, 2.0f));
+
+// 描边
+label->setOutlineEnabled(true);
+label->setOutlineColor(Colors::Black);
+label->setOutlineWidth(1.0f);
+
+// 多行文本
+label->setMultiLine(true);
+label->setLineSpacing(1.2f);
+label->setMaxWidth(300.0f); // 自动换行宽度
+
+// 对齐方式
+label->setHorizontalAlign(HorizontalAlign::Center);
+label->setVerticalAlign(VerticalAlign::Middle);
+```
+
+## 复选框(CheckBox)
+
+### 创建复选框
+
+```cpp
+// 方式1:简单创建
+auto checkBox = CheckBox::create();
+checkBox->setPosition(Vec2(100, 200));
+checkBox->setChecked(true);
+
+// 方式2:带标签
+auto checkBox = CheckBox::create("启用音效");
+checkBox->setPosition(Vec2(100, 200));
+
+// 方式3:链式调用
+auto checkBox = CheckBox::create("启用音效")
+ ->withPosition(100, 200)
+ ->withFont(font)
+ ->withTextColor(Colors::White);
+
+// 状态改变回调
+checkBox->setOnStateChange([](bool checked) {
+ E2D_LOG_INFO("复选框状态: {}", checked);
+});
+
+addChild(checkBox);
+```
+
+### 复选框属性
+
+```cpp
+// 状态
+checkBox->setChecked(true);
+checkBox->toggle();
+bool isChecked = checkBox->isChecked();
+
+// 标签
+checkBox->setLabel("新标签");
+checkBox->setFont(font);
+checkBox->setTextColor(Colors::White);
+
+// 外观
+checkBox->setBoxSize(20.0f); // 复选框大小
+checkBox->setSpacing(10.0f); // 复选框与标签间距
+checkBox->setCheckedColor(Colors::Green);
+checkBox->setUncheckedColor(Colors::Gray);
+checkBox->setCheckMarkColor(Colors::White);
+```
+
+## 单选按钮(RadioButton)
+
+### 创建单选按钮
+
+```cpp
+// 创建单选按钮
+auto radio1 = RadioButton::create("选项 A");
+radio1->setPosition(Vec2(100, 300));
+radio1->setSelected(true);
+
+auto radio2 = RadioButton::create("选项 B");
+radio2->setPosition(Vec2(100, 340));
+
+auto radio3 = RadioButton::create("选项 C");
+radio3->setPosition(Vec2(100, 380));
+
+// 添加到组(互斥选择)
+radio1->setGroupId(1);
+radio2->setGroupId(1);
+radio3->setGroupId(1);
+
+// 或使用 RadioButtonGroup
+auto group = std::make_shared();
+group->addButton(radio1.get());
+group->addButton(radio2.get());
+group->addButton(radio3.get());
+
+// 选择改变回调
+group->setOnSelectionChange([](RadioButton* selected) {
+ if (selected) {
+ E2D_LOG_INFO("选中: {}", selected->getLabel());
+ }
+});
+
+addChild(radio1);
+addChild(radio2);
+addChild(radio3);
+```
+
+### 单选按钮属性
+
+```cpp
+// 状态
+radio->setSelected(true);
+bool isSelected = radio->isSelected();
+
+// 标签
+radio->setLabel("新标签");
+radio->setFont(font);
+radio->setTextColor(Colors::White);
+
+// 外观
+radio->setCircleSize(16.0f); // 圆形大小
+radio->setSpacing(10.0f); // 圆形与标签间距
+radio->setSelectedColor(Colors::Green);
+radio->setUnselectedColor(Colors::Gray);
+radio->setDotColor(Colors::White);
+
+// 分组
+radio->setGroupId(1); // 相同 groupId 的按钮互斥
+```
+
+## 滑块(Slider)
+
+### 创建滑块
+
+```cpp
+// 方式1:简单创建
+auto slider = Slider::create();
+slider->setPosition(Vec2(200, 400));
+slider->setRange(0.0f, 100.0f);
+slider->setValue(50.0f);
+
+// 方式2:带初始值创建
+auto slider = Slider::create(0.0f, 100.0f, 50.0f);
+
+// 方式3:链式调用
+auto slider = Slider::create()
+ ->withPosition(200, 400)
+ ->withSize(200, 20)
+ ->withMinValue(0.0f)
+ ->withMaxValue(100.0f)
+ ->withValue(50.0f);
+
+// 值改变回调
+slider->setOnValueChange([](float value) {
+ E2D_LOG_INFO("滑块值: {}", value);
+});
+
+// 拖动开始/结束回调
+slider->setOnDragStart([]() {
+ E2D_LOG_INFO("开始拖动");
+});
+slider->setOnDragEnd([]() {
+ E2D_LOG_INFO("结束拖动");
+});
+
+addChild(slider);
+```
+
+### 滑块属性
+
+```cpp
+// 值和范围
+slider->setRange(0.0f, 100.0f);
+slider->setValue(50.0f);
+slider->setStep(5.0f); // 步进值,0表示无步进
+float value = slider->getValue();
+float min = slider->getMin();
+float max = slider->getMax();
+
+// 方向
+slider->setVertical(false); // false=水平, true=垂直
+
+// 外观
+slider->setTrackSize(4.0f); // 轨道粗细
+slider->setThumbSize(16.0f); // 滑块大小
+
+// 颜色
+slider->setTrackColor(Colors::Gray);
+slider->setFillColor(Colors::Green);
+slider->setThumbColor(Colors::White);
+slider->setThumbHoverColor(Colors::Yellow);
+slider->setThumbPressedColor(Colors::Orange);
+
+// 显示选项
+slider->setShowThumb(true); // 显示滑块
+slider->setShowFill(true); // 显示填充
+slider->setTextEnabled(true); // 显示数值文本
+slider->setTextFormat("{:.0f}%"); // 数值格式
+slider->setFont(font);
+slider->setTextColor(Colors::White);
+```
+
+## 进度条(ProgressBar)
+
+### 创建进度条
+
+```cpp
+// 方式1:简单创建
+auto progressBar = ProgressBar::create();
+progressBar->setPosition(Vec2(200, 500));
+progressBar->setSize(300.0f, 30.0f);
+progressBar->setValue(75.0f); // 75%
+
+// 方式2:带范围创建
+auto progressBar = ProgressBar::create(0.0f, 100.0f, 75.0f);
+
+// 方式3:链式调用
+auto progressBar = ProgressBar::create()
+ ->withPosition(200, 500)
+ ->withSize(300, 30)
+ ->withProgress(0.75f); // 0-1 的进度值
+
+addChild(progressBar);
+```
+
+### 进度条属性
+
+```cpp
+// 值和范围
+progressBar->setRange(0.0f, 100.0f);
+progressBar->setValue(75.0f);
+float value = progressBar->getValue();
+float percent = progressBar->getPercent(); // 0.0-1.0
+
+// 方向
+progressBar->setDirection(Direction::LeftToRight);
+// Direction::LeftToRight, RightToLeft, BottomToTop, TopToBottom
+
+// 颜色
+progressBar->setBackgroundColor(Colors::DarkGray);
+progressBar->setFillColor(Colors::Green);
+
+// 渐变填充
+progressBar->setGradientFillEnabled(true);
+progressBar->setFillColorEnd(Colors::LightGreen);
+
+// 分段颜色(根据进度显示不同颜色)
+progressBar->setSegmentedColorsEnabled(true);
+progressBar->addColorSegment(0.3f, Colors::Red); // <30% 红色
+progressBar->addColorSegment(0.7f, Colors::Yellow); // 30-70% 黄色
+// >70% 使用默认填充色(绿色)
+
+// 圆角
+progressBar->setRoundedCornersEnabled(true);
+progressBar->setCornerRadius(8.0f);
+
+// 边框
+progressBar->setBorderEnabled(true);
+progressBar->setBorderColor(Colors::White);
+progressBar->setBorderWidth(2.0f);
+progressBar->setPadding(2.0f);
+
+// 文本
+progressBar->setTextEnabled(true);
+progressBar->setTextFormat("{:.0f}%");
+progressBar->setFont(font);
+progressBar->setTextColor(Colors::White);
+
+// 动画效果
+progressBar->setAnimatedChangeEnabled(true);
+progressBar->setAnimationSpeed(5.0f);
+
+// 延迟显示效果
+progressBar->setDelayedDisplayEnabled(true);
+progressBar->setDelayTime(0.5f);
+progressBar->setDelayedFillColor(Colors::Yellow);
+
+// 条纹效果
+progressBar->setStripedEnabled(true);
+progressBar->setStripeColor(Colors::White);
+progressBar->setStripeSpeed(1.0f);
+```
+
+## 完整示例:设置场景
+
+```cpp
+class SettingsScene : public Scene {
public:
void onEnter() override {
Scene::onEnter();
- auto &resources = Application::instance().resources();
- font_ = resources.loadFont("assets/font.ttf", 28, true);
+ auto& resources = Application::instance().resources();
+ font_ = resources.loadFont("assets/font.ttf", 24);
- float centerX = 640.0f;
- float startY = 300.0f;
+ // 标题
+ auto title = Text::create("设置", font_);
+ title->setPosition(Vec2(400, 100));
+ title->setAlignment(Alignment::Center);
+ addChild(title);
- // 创建菜单按钮
- createMenuButton("开始游戏", centerX, startY, 0);
- createMenuButton("继续游戏", centerX, startY + 50.0f, 1);
- createMenuButton("退出", centerX, startY + 100.0f, 2);
+ // 音效开关
+ auto soundLabel = Label::create("音效", font_);
+ soundLabel->setPosition(Vec2(200, 200));
+ addChild(soundLabel);
- menuCount_ = 3;
- selectedIndex_ = 0;
- updateMenuColors();
- }
-
- void onUpdate(float dt) override {
- Scene::onUpdate(dt);
+ soundCheck_ = CheckBox::create();
+ soundCheck_->setPosition(Vec2(350, 200));
+ soundCheck_->setChecked(true);
+ soundCheck_->setOnStateChange([this](bool checked) {
+ E2D_LOG_INFO("音效: {}", checked ? "开启" : "关闭");
+ });
+ addChild(soundCheck_);
- auto &input = Application::instance().input();
+ // 音量滑块
+ auto volumeLabel = Label::create("音量", font_);
+ volumeLabel->setPosition(Vec2(200, 280));
+ addChild(volumeLabel);
- // 方向键切换选择
- if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
- selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
- updateMenuColors();
- } else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
- selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
- updateMenuColors();
- }
+ volumeSlider_ = Slider::create(0.0f, 1.0f, 0.8f);
+ volumeSlider_->setPosition(Vec2(350, 280));
+ volumeSlider_->setSize(200, 20);
+ volumeSlider_->setOnValueChange([](float value) {
+ E2D_LOG_INFO("音量: {:.0f}%", value * 100);
+ });
+ addChild(volumeSlider_);
- // A键确认
- if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
- executeMenuItem();
- }
+ // 难度选择
+ auto difficultyLabel = Label::create("难度", font_);
+ difficultyLabel->setPosition(Vec2(200, 360));
+ addChild(difficultyLabel);
+
+ auto easyRadio = RadioButton::create("简单");
+ easyRadio->setPosition(Vec2(350, 360));
+ easyRadio->setSelected(true);
+ easyRadio->setGroupId(1);
+
+ auto normalRadio = RadioButton::create("普通");
+ normalRadio->setPosition(Vec2(450, 360));
+ normalRadio->setGroupId(1);
+
+ auto hardRadio = RadioButton::create("困难");
+ hardRadio->setPosition(Vec2(550, 360));
+ hardRadio->setGroupId(1);
+
+ addChild(easyRadio);
+ addChild(normalRadio);
+ addChild(hardRadio);
+
+ // 返回按钮
+ auto backBtn = Button::create("返回", font_);
+ backBtn->setPosition(Vec2(400, 500));
+ backBtn->setCustomSize(150.0f, 50.0f);
+ backBtn->setBackgroundColor(
+ Colors::Blue,
+ Colors::LightBlue,
+ Colors::DarkBlue
+ );
+ backBtn->setOnClick([]() {
+ Application::instance().scenes().popScene();
+ });
+ addChild(backBtn);
}
private:
Ptr font_;
- std::vector> buttons_;
- int selectedIndex_ = 0;
- int menuCount_ = 0;
-
- void createMenuButton(const std::string &text, float x, float y, int index) {
- auto button = Button::create();
- button->setFont(font_);
- button->setText(text);
- button->setTextColor(Colors::Black);
- button->setBackgroundColor(
- Colors::Transparent,
- Colors::Transparent,
- Colors::Transparent
- );
- button->setBorder(Colors::Transparent, 0.0f);
- button->setPadding(Vec2(0.0f, 0.0f));
- button->setCustomSize(200.0f, 40.0f);
- button->setAnchor(0.5f, 0.5f);
- button->setPosition(x, y);
- addChild(button);
- buttons_.push_back(button);
- }
-
- void updateMenuColors() {
- for (int i = 0; i < buttons_.size(); ++i) {
- if (buttons_[i]) {
- buttons_[i]->setTextColor(
- i == selectedIndex_ ? Colors::Red : Colors::Black
- );
- }
- }
- }
-
- void executeMenuItem() {
- switch (selectedIndex_) {
- case 0: startGame(); break;
- case 1: continueGame(); break;
- case 2: exitGame(); break;
- }
- }
-
- void startGame() {
- // 切换到游戏场景
- }
-
- void continueGame() {
- // 继续游戏
- }
-
- void exitGame() {
- Application::instance().quit();
- }
+ Ptr soundCheck_;
+ Ptr volumeSlider_;
};
```
-## 绘制文字
+## 最佳实践
-### 基本文字绘制
-
-```cpp
-void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
-
- if (font_) {
- // 绘制文字
- renderer.drawText(*font_, "Hello World",
- Vec2(100.0f, 100.0f), Colors::White);
-
- // 绘制带颜色的文字
- renderer.drawText(*font_, "红色文字",
- Vec2(100.0f, 150.0f), Colors::Red);
- }
-}
-```
-
-### 格式化文字
-
-```cpp
-void onRender(RenderBackend &renderer) override {
- Scene::onRender(renderer);
-
- if (infoFont_) {
- auto &app = Application::instance();
-
- // 绘制 FPS
- std::stringstream ss;
- ss << "FPS: " << app.fps();
- renderer.drawText(*infoFont_, ss.str(),
- Vec2(50.0f, 50.0f), Colors::Yellow);
-
- // 绘制节点数量
- ss.str("");
- ss << "Nodes: " << nodes_.size();
- renderer.drawText(*infoFont_, ss.str(),
- Vec2(50.0f, 80.0f), Colors::White);
- }
-}
-```
-
-## 完整示例
-
-```cpp
-class StartScene : public Scene {
-public:
- void onEnter() override {
- Scene::onEnter();
-
- auto &app = Application::instance();
- auto &resources = app.resources();
-
- // 加载背景
- auto bgTex = resources.loadTexture("assets/background.jpg");
- if (bgTex) {
- auto bg = Sprite::create(bgTex);
- bg->setAnchor(0.0f, 0.0f);
- addChild(bg);
- }
-
- // 加载字体
- font_ = resources.loadFont("assets/font.ttf", 32, true);
-
- float centerX = app.getConfig().width / 2.0f;
-
- // 创建标题
- titleBtn_ = Button::create();
- titleBtn_->setFont(font_);
- titleBtn_->setText("游戏标题");
- titleBtn_->setTextColor(Colors::Gold);
- titleBtn_->setBackgroundColor(
- Colors::Transparent,
- Colors::Transparent,
- Colors::Transparent
- );
- titleBtn_->setBorder(Colors::Transparent, 0.0f);
- titleBtn_->setAnchor(0.5f, 0.5f);
- titleBtn_->setPosition(centerX, 200.0f);
- addChild(titleBtn_);
-
- // 创建菜单按钮
- createMenuButton("新游戏", centerX, 350.0f, 0);
- createMenuButton("继续", centerX, 400.0f, 1);
- createMenuButton("退出", centerX, 450.0f, 2);
-
- menuCount_ = 3;
- updateMenuColors();
- }
-
- void onUpdate(float dt) override {
- Scene::onUpdate(dt);
-
- auto &input = Application::instance().input();
-
- if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
- selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
- updateMenuColors();
- } else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
- selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
- updateMenuColors();
- }
-
- if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
- executeMenuItem();
- }
- }
-
-private:
- Ptr font_;
- Ptr