refactor(input): 统一使用 GamepadButton 枚举替代 SDL_CONTROLLER_BUTTON 常量
docs: 添加 API 教程文档和构建系统文档 新增完整的 API 教程文档,涵盖快速开始、场景系统、节点系统、资源管理、输入处理、碰撞检测、UI 系统和音频系统。同时添加详细的构建系统文档,说明 MinGW 和 Switch 平台的构建配置。 feat(build): 支持 MinGW 平台构建 新增 MinGW 平台支持,更新 README 添加 MinGW 构建说明,完善 xmake 构建脚本以支持多平台构建。 chore: 更新项目结构和文档链接 调整项目目录结构,更新 README 中的文档链接,添加构建系统文档和 API 教程的快速访问链接
This commit is contained in:
parent
0f89262498
commit
23647b6458
|
|
@ -0,0 +1,327 @@
|
||||||
|
# Extra2D 构建系统文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Extra2D 使用 **Xmake** 作为构建系统,支持 **MinGW (Windows)** 和 **Nintendo Switch** 两个平台。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Extra2D/
|
||||||
|
├── xmake.lua # 主构建脚本
|
||||||
|
├── xmake/
|
||||||
|
│ ├── engine.lua # 引擎库定义
|
||||||
|
│ └── toolchains/
|
||||||
|
│ └── switch.lua # Switch 工具链定义
|
||||||
|
├── Extra2D/
|
||||||
|
│ ├── src/ # 引擎源码
|
||||||
|
│ └── include/ # 引擎头文件
|
||||||
|
├── squirrel/ # Squirrel 脚本引擎
|
||||||
|
└── examples/ # 示例程序
|
||||||
|
├── hello_world/
|
||||||
|
├── collision_demo/
|
||||||
|
├── push_box/
|
||||||
|
└── spatial_index_demo/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境准备
|
||||||
|
|
||||||
|
### MinGW (Windows) 平台
|
||||||
|
|
||||||
|
1. **安装 MinGW-w64**
|
||||||
|
- 下载地址: https://www.mingw-w64.org/downloads/
|
||||||
|
- 或使用 MSYS2: `pacman -S mingw-w64-x86_64-toolchain`
|
||||||
|
|
||||||
|
2. **安装 Xmake**
|
||||||
|
- 下载地址: https://xmake.io/#/zh-cn/guide/installation
|
||||||
|
|
||||||
|
3. **安装依赖包**
|
||||||
|
```bash
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nintendo Switch 平台
|
||||||
|
|
||||||
|
1. **安装 devkitPro**
|
||||||
|
- 下载地址: https://devkitpro.org/wiki/Getting_Started
|
||||||
|
- Windows 安装程序会自动设置环境变量
|
||||||
|
|
||||||
|
2. **设置环境变量**
|
||||||
|
```powershell
|
||||||
|
# PowerShell
|
||||||
|
$env:DEVKITPRO="C:/devkitPro"
|
||||||
|
|
||||||
|
# 或永久设置(系统属性 -> 环境变量)
|
||||||
|
[Environment]::SetEnvironmentVariable("DEVKITPRO", "C:/devkitPro", "User")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **在 MSYS2 中安装 Switch 库**
|
||||||
|
```bash
|
||||||
|
# 打开 MSYS2 (devkitPro 提供的)
|
||||||
|
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
|
||||||
|
|
||||||
|
# 或安装所有 Switch 开发库
|
||||||
|
pacman -S $(pacman -Slq dkp-libs | grep switch-)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **验证安装**
|
||||||
|
```bash
|
||||||
|
ls $DEVKITPRO/portlibs/switch/include/SDL2
|
||||||
|
ls $DEVKITPRO/portlibs/switch/include/glm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主构建脚本 (xmake.lua)
|
||||||
|
|
||||||
|
### 项目元信息
|
||||||
|
- **项目名称**: Extra2D
|
||||||
|
- **版本**: 3.1.0
|
||||||
|
- **许可证**: MIT
|
||||||
|
- **语言标准**: C++17
|
||||||
|
- **编码**: UTF-8
|
||||||
|
|
||||||
|
### 构建选项
|
||||||
|
|
||||||
|
| 选项 | 默认值 | 描述 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `examples` | true | 构建示例程序 |
|
||||||
|
| `debug_logs` | false | 启用调试日志 |
|
||||||
|
|
||||||
|
### 平台检测逻辑
|
||||||
|
|
||||||
|
1. 获取主机平台: `os.host()`
|
||||||
|
2. 获取目标平台: `get_config("plat")` 或主机平台
|
||||||
|
3. 平台回退: 如果不支持,Windows 回退到 `mingw`
|
||||||
|
4. 设置平台: `set_plat(target_plat)`
|
||||||
|
5. 设置架构:
|
||||||
|
- Switch: `arm64`
|
||||||
|
- MinGW: `x86_64`
|
||||||
|
|
||||||
|
### 依赖包 (MinGW 平台)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 引擎库定义 (xmake/engine.lua)
|
||||||
|
|
||||||
|
### 目标: extra2d
|
||||||
|
- **类型**: 静态库 (`static`)
|
||||||
|
- **源文件**:
|
||||||
|
- `Extra2D/src/**.cpp`
|
||||||
|
- `Extra2D/src/glad/glad.c`
|
||||||
|
- `squirrel/squirrel/*.cpp`
|
||||||
|
- `squirrel/sqstdlib/*.cpp`
|
||||||
|
|
||||||
|
### 头文件路径
|
||||||
|
- `Extra2D/include` (public)
|
||||||
|
- `squirrel/include` (public)
|
||||||
|
- `Extra2D/include/extra2d/platform` (public)
|
||||||
|
|
||||||
|
### 平台配置
|
||||||
|
|
||||||
|
#### Switch 平台
|
||||||
|
```lua
|
||||||
|
add_includedirs(devkitPro .. "/portlibs/switch/include")
|
||||||
|
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
|
||||||
|
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MinGW 平台
|
||||||
|
```lua
|
||||||
|
add_packages("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编译器标志
|
||||||
|
- `-Wall`, `-Wextra`
|
||||||
|
- `-Wno-unused-variable`, `-Wno-unused-function`
|
||||||
|
- `-Wno-deprecated-copy`, `-Wno-class-memaccess`
|
||||||
|
|
||||||
|
### 构建模式
|
||||||
|
- **Debug**: `-O0`, `-g`, 定义 `E2D_DEBUG`, `_DEBUG`
|
||||||
|
- **Release**: `-O2`, 定义 `NDEBUG`
|
||||||
|
|
||||||
|
## Switch 工具链 (xmake/toolchains/switch.lua)
|
||||||
|
|
||||||
|
### 工具链: switch
|
||||||
|
- **类型**: standalone
|
||||||
|
- **描述**: Nintendo Switch devkitA64 工具链
|
||||||
|
|
||||||
|
### 工具路径
|
||||||
|
- **CC**: `aarch64-none-elf-gcc.exe`
|
||||||
|
- **CXX**: `aarch64-none-elf-g++.exe`
|
||||||
|
- **LD**: `aarch64-none-elf-g++.exe`
|
||||||
|
- **AR**: `aarch64-none-elf-gcc-ar.exe`
|
||||||
|
|
||||||
|
### 架构标志
|
||||||
|
```
|
||||||
|
-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 链接标志
|
||||||
|
```
|
||||||
|
-specs=switch.specs -g
|
||||||
|
```
|
||||||
|
|
||||||
|
### 预定义宏
|
||||||
|
- `__SWITCH__`
|
||||||
|
- `__NX__`
|
||||||
|
- `MA_SWITCH`
|
||||||
|
- `PFD_SWITCH`
|
||||||
|
|
||||||
|
### 系统库
|
||||||
|
- `nx` (libnx)
|
||||||
|
- `m` (math)
|
||||||
|
|
||||||
|
## 示例程序构建脚本
|
||||||
|
|
||||||
|
### 通用结构
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- 使用与主项目相同的平台配置
|
||||||
|
if is_plat("switch") then
|
||||||
|
-- Switch 平台配置
|
||||||
|
elseif is_plat("mingw") then
|
||||||
|
-- MinGW 平台配置
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch 平台配置
|
||||||
|
- 设置平台: `set_plat("switch")`
|
||||||
|
- 设置架构: `set_arch("arm64")`
|
||||||
|
- 设置工具链: `set_toolchains("switch")`
|
||||||
|
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
|
||||||
|
- 构建后生成 NRO 文件
|
||||||
|
|
||||||
|
### MinGW 平台配置
|
||||||
|
- 设置平台: `set_plat("mingw")`
|
||||||
|
- 设置架构: `set_arch("x86_64")`
|
||||||
|
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
|
||||||
|
- 链接标志: `-mwindows`
|
||||||
|
- 构建后复制资源文件
|
||||||
|
|
||||||
|
## 构建命令
|
||||||
|
|
||||||
|
### 配置项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认配置 (MinGW)
|
||||||
|
xmake f -c
|
||||||
|
|
||||||
|
# 指定平台 (使用 -p 参数)
|
||||||
|
xmake f -c -p mingw
|
||||||
|
xmake f -c -p switch
|
||||||
|
|
||||||
|
# 指定 MinGW 路径(如果不在默认位置)
|
||||||
|
xmake f -c -p mingw --mingw=C:\mingw
|
||||||
|
|
||||||
|
# 禁用示例
|
||||||
|
xmake f --examples=n
|
||||||
|
|
||||||
|
# 启用调试日志
|
||||||
|
xmake f --debug_logs=y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安装依赖 (MinGW)
|
||||||
|
```bash
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建项目
|
||||||
|
```bash
|
||||||
|
# 构建所有目标
|
||||||
|
xmake
|
||||||
|
|
||||||
|
# 构建特定目标
|
||||||
|
xmake -r extra2d
|
||||||
|
xmake -r push_box
|
||||||
|
|
||||||
|
# 并行构建
|
||||||
|
xmake -j4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行程序
|
||||||
|
```bash
|
||||||
|
# 运行示例
|
||||||
|
xmake run push_box
|
||||||
|
xmake run hello_world
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理构建
|
||||||
|
```bash
|
||||||
|
xmake clean
|
||||||
|
xmake f -c # 重新配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
build/
|
||||||
|
├── examples/
|
||||||
|
│ ├── hello_world/
|
||||||
|
│ │ ├── hello_world.exe # MinGW
|
||||||
|
│ │ ├── hello_world.nro # Switch
|
||||||
|
│ │ └── assets/ # 资源文件
|
||||||
|
│ ├── push_box/
|
||||||
|
│ ├── collision_demo/
|
||||||
|
│ └── spatial_index_demo/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键设计决策
|
||||||
|
|
||||||
|
### 1. 平台检测
|
||||||
|
- 使用 `is_plat()` 而不是手动检测,确保与主项目一致
|
||||||
|
- 示例脚本继承主项目的平台配置
|
||||||
|
|
||||||
|
### 2. 资源处理
|
||||||
|
- **Switch**: 使用 romfs 嵌入 NRO 文件
|
||||||
|
- **MinGW**: 构建后复制到输出目录
|
||||||
|
|
||||||
|
### 3. 依赖管理
|
||||||
|
- **MinGW**: 使用 Xmake 包管理器 (`add_requires`)
|
||||||
|
- **Switch**: 使用 devkitPro 提供的库
|
||||||
|
|
||||||
|
### 4. 工具链隔离
|
||||||
|
- Switch 工具链定义在单独文件中
|
||||||
|
- 通过 `set_toolchains("switch")` 切换
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 依赖包找不到
|
||||||
|
```bash
|
||||||
|
xmake repo -u
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Switch 工具链找不到
|
||||||
|
- 确保 DEVKITPRO 环境变量设置正确
|
||||||
|
- 默认路径: `C:/devkitPro`
|
||||||
|
|
||||||
|
### 3. 平台配置不匹配
|
||||||
|
- 使用 `xmake show` 查看当前配置
|
||||||
|
- 使用 `xmake f -c` 重新配置
|
||||||
|
|
||||||
|
### 4. MinGW 路径问题
|
||||||
|
如果 MinGW 安装在非默认位置,使用 `--mingw` 参数指定:
|
||||||
|
```bash
|
||||||
|
xmake f -c -p mingw --mingw=D:\Tools\mingw64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Switch 库找不到
|
||||||
|
确保在 MSYS2 中安装了必要的库:
|
||||||
|
```bash
|
||||||
|
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展指南
|
||||||
|
|
||||||
|
### 添加新示例
|
||||||
|
1. 在 `examples/` 下创建新目录
|
||||||
|
2. 创建 `xmake.lua` 构建脚本
|
||||||
|
3. 在 `xmake.lua` 中添加 `includes("examples/new_example")`
|
||||||
|
|
||||||
|
### 添加新平台
|
||||||
|
1. 在 `xmake/toolchains/` 下创建工具链定义
|
||||||
|
2. 在 `xmake.lua` 中添加平台检测逻辑
|
||||||
|
3. 在 `xmake/engine.lua` 中添加平台配置
|
||||||
112
README.md
112
README.md
|
|
@ -111,10 +111,10 @@ mindmap
|
||||||
|
|
||||||
| 组件 | 要求 |
|
| 组件 | 要求 |
|
||||||
|:----:|:-----|
|
|:----:|:-----|
|
||||||
| 开发环境 | devkitPro + devkitA64 |
|
| 开发环境 | devkitPro + devkitA64 (Switch) / MinGW-w64 (Windows) |
|
||||||
| C++ 标准 | C++17 |
|
| C++ 标准 | C++17 |
|
||||||
| 构建工具 | xmake |
|
| 构建工具 | xmake |
|
||||||
| 目标平台 | Nintendo Switch |
|
| 目标平台 | Nintendo Switch / Windows (MinGW) |
|
||||||
|
|
||||||
### 安装 devkitPro
|
### 安装 devkitPro
|
||||||
|
|
||||||
|
|
@ -129,6 +129,8 @@ pacman -S switch-dev switch-portlibs
|
||||||
|
|
||||||
### 构建项目
|
### 构建项目
|
||||||
|
|
||||||
|
#### Switch 平台
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆仓库
|
# 克隆仓库
|
||||||
git clone https://github.com/ChestnutYueyue/extra2d.git
|
git clone https://github.com/ChestnutYueyue/extra2d.git
|
||||||
|
|
@ -144,6 +146,26 @@ xmake
|
||||||
xmake -g examples
|
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 可运行文件
|
### 生成 NSP 可运行文件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -226,31 +248,57 @@ int main()
|
||||||
Extra2D/
|
Extra2D/
|
||||||
├── 📁 Extra2D/ # 引擎核心代码
|
├── 📁 Extra2D/ # 引擎核心代码
|
||||||
│ ├── 📁 include/ # 头文件
|
│ ├── 📁 include/ # 头文件
|
||||||
│ │ └── 📁 extra2d/ # 引擎头文件
|
│ │ ├── 📁 extra2d/ # 引擎头文件
|
||||||
│ │ ├── extra2d.h # 主头文件
|
│ │ │ ├── extra2d.h # 主头文件
|
||||||
│ │ ├── app/ # 应用管理
|
│ │ │ ├── app/ # 应用管理
|
||||||
│ │ ├── action/ # 动作系统
|
│ │ │ ├── action/ # 动作系统
|
||||||
│ │ ├── animation/ # 动画系统
|
│ │ │ ├── animation/ # 动画系统
|
||||||
│ │ ├── audio/ # 音频系统
|
│ │ │ ├── audio/ # 音频系统
|
||||||
│ │ ├── core/ # 核心类型
|
│ │ │ ├── core/ # 核心类型
|
||||||
│ │ ├── effects/ # 特效系统
|
│ │ │ ├── effects/ # 特效系统
|
||||||
│ │ ├── event/ # 事件系统
|
│ │ │ ├── event/ # 事件系统
|
||||||
│ │ ├── graphics/ # 图形渲染
|
│ │ │ ├── graphics/ # 图形渲染
|
||||||
│ │ ├── platform/ # 平台抽象
|
│ │ │ ├── platform/ # 平台抽象
|
||||||
│ │ ├── resource/ # 资源管理
|
│ │ │ ├── resource/ # 资源管理
|
||||||
│ │ ├── scene/ # 场景系统
|
│ │ │ ├── scene/ # 场景系统
|
||||||
│ │ ├── script/ # 脚本系统
|
│ │ │ ├── script/ # 脚本系统
|
||||||
│ │ ├── spatial/ # 空间索引
|
│ │ │ ├── spatial/ # 空间索引
|
||||||
│ │ ├── ui/ # UI 系统
|
│ │ │ ├── ui/ # UI 系统
|
||||||
│ │ └── utils/ # 工具库
|
│ │ │ └── utils/ # 工具库
|
||||||
│ ├── 📁 src/ # 源文件
|
│ │ ├── 📁 glad/ # OpenGL Loader
|
||||||
│ └── 📁 examples/ # 示例程序
|
│ │ ├── 📁 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/ # 推箱子游戏
|
│ ├── push_box/ # 推箱子游戏
|
||||||
│ └── switch_simple_test/ # 简单测试
|
│ └── spatial_index_demo/ # 空间索引示例
|
||||||
├── 📁 squirrel/ # Squirrel 脚本引擎
|
|
||||||
├── 📁 logo/ # Logo 资源
|
├── 📁 logo/ # Logo 资源
|
||||||
├── 📄 xmake.lua # xmake 构建配置
|
├── 📁 squirrel/ # Squirrel 脚本引擎
|
||||||
├── 📄 SWITCH_BUILD_GUIDE.md # Switch 构建详细指南
|
├── <20> xmake/ # Xmake 构建配置
|
||||||
|
│ └── toolchains/ # 工具链定义
|
||||||
|
├── 📄 xmake.lua # 主构建配置
|
||||||
├── 📄 LICENSE # MIT 许可证
|
├── 📄 LICENSE # MIT 许可证
|
||||||
└── 📄 README.md # 本文件
|
└── 📄 README.md # 本文件
|
||||||
```
|
```
|
||||||
|
|
@ -380,8 +428,18 @@ sound->setVolume(0.8f);
|
||||||
|
|
||||||
## 📖 相关文档
|
## 📖 相关文档
|
||||||
|
|
||||||
- [Switch 构建指南](./SWITCH_BUILD_GUIDE.md) - 详细的 Switch 平台构建教程
|
- [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
|
||||||
- [迁移完成记录](./SWITCH_MIGRATION_COMPLETE.md) - 项目迁移历史记录
|
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
|
||||||
|
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
|
||||||
|
- [03. 节点系统](./docs/API_Tutorial/03_Node_System.md)
|
||||||
|
- [04. 资源管理](./docs/API_Tutorial/04_Resource_Management.md)
|
||||||
|
- [05. 输入处理](./docs/API_Tutorial/05_Input_Handling.md)
|
||||||
|
- [06. 碰撞检测](./docs/API_Tutorial/06_Collision_Detection.md)
|
||||||
|
- [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) - 项目迁移历史记录
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Extra2D API 教程 - 01. 快速开始
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
Extra2D 是一个跨平台的 2D 游戏引擎,支持 Windows (MinGW) 和 Nintendo Switch 平台。
|
||||||
|
|
||||||
|
## 最小示例
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
|
using namespace extra2d;
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
// 1. 初始化日志系统
|
||||||
|
Logger::init();
|
||||||
|
Logger::setLevel(LogLevel::Debug);
|
||||||
|
|
||||||
|
// 2. 获取应用实例
|
||||||
|
auto &app = Application::instance();
|
||||||
|
|
||||||
|
// 3. 配置应用
|
||||||
|
AppConfig config;
|
||||||
|
config.title = "My Game";
|
||||||
|
config.width = 1280;
|
||||||
|
config.height = 720;
|
||||||
|
config.vsync = true;
|
||||||
|
config.fpsLimit = 60;
|
||||||
|
|
||||||
|
// 4. 初始化应用
|
||||||
|
if (!app.init(config)) {
|
||||||
|
E2D_LOG_ERROR("应用初始化失败!");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 进入场景
|
||||||
|
app.enterScene(makePtr<MyScene>());
|
||||||
|
|
||||||
|
// 6. 运行应用
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 应用生命周期
|
||||||
|
|
||||||
|
```
|
||||||
|
Logger::init() → Application::init() → enterScene() → run() → 退出
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景生命周期
|
||||||
|
|
||||||
|
```
|
||||||
|
onEnter() → onUpdate(dt) → onRender() → onExit()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [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)
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
# Extra2D API 教程 - 02. 场景系统
|
||||||
|
|
||||||
|
## 场景基础
|
||||||
|
|
||||||
|
场景(Scene)是游戏的基本组织单位,负责管理节点和渲染。
|
||||||
|
|
||||||
|
### 创建场景
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
|
using namespace extra2d;
|
||||||
|
|
||||||
|
class MyScene : public Scene {
|
||||||
|
public:
|
||||||
|
// 场景进入时调用
|
||||||
|
void onEnter() override {
|
||||||
|
// 必须先调用父类的 onEnter()
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 设置背景颜色
|
||||||
|
setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f));
|
||||||
|
|
||||||
|
E2D_LOG_INFO("场景已进入");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每帧更新时调用
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
// dt 是时间间隔(秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染时调用
|
||||||
|
void onRender(RenderBackend &renderer) override {
|
||||||
|
Scene::onRender(renderer);
|
||||||
|
// 绘制自定义内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// 场景退出时调用
|
||||||
|
void onExit() override {
|
||||||
|
// 清理资源
|
||||||
|
Scene::onExit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重要提示
|
||||||
|
|
||||||
|
**必须调用 `Scene::onEnter()`**:
|
||||||
|
```cpp
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter(); // 必须调用!
|
||||||
|
// 你的初始化代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果不调用,会导致:
|
||||||
|
- `running_` 状态未设置
|
||||||
|
- 子节点无法正确注册到空间索引
|
||||||
|
- 碰撞检测失效
|
||||||
|
|
||||||
|
## 场景管理
|
||||||
|
|
||||||
|
### 进入场景
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 进入新场景
|
||||||
|
app.enterScene(makePtr<MyScene>());
|
||||||
|
|
||||||
|
// 替换当前场景(带过渡效果)
|
||||||
|
app.scenes().replaceScene(
|
||||||
|
makePtr<PlayScene>(),
|
||||||
|
TransitionType::Fade, // 淡入淡出
|
||||||
|
0.25f // 过渡时间(秒)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景过渡类型
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class TransitionType {
|
||||||
|
None, // 无过渡
|
||||||
|
Fade, // 淡入淡出
|
||||||
|
SlideLeft, // 向左滑动
|
||||||
|
SlideRight, // 向右滑动
|
||||||
|
SlideUp, // 向上滑动
|
||||||
|
SlideDown // 向下滑动
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 场景配置
|
||||||
|
|
||||||
|
### 视口设置
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 设置视口大小(影响坐标系)
|
||||||
|
setViewportSize(1280.0f, 720.0f);
|
||||||
|
|
||||||
|
// 设置背景颜色
|
||||||
|
setBackgroundColor(Colors::Black);
|
||||||
|
|
||||||
|
// 启用/禁用空间索引
|
||||||
|
setSpatialIndexingEnabled(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 空间索引
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 获取空间管理器
|
||||||
|
auto &spatialManager = getSpatialManager();
|
||||||
|
|
||||||
|
// 切换空间索引策略
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
|
||||||
|
|
||||||
|
// 查询所有碰撞
|
||||||
|
auto collisions = queryCollisions();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class GameScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 设置视口和背景
|
||||||
|
setViewportSize(1280.0f, 720.0f);
|
||||||
|
setBackgroundColor(Color(0.1f, 0.2f, 0.3f, 1.0f));
|
||||||
|
|
||||||
|
// 启用空间索引
|
||||||
|
setSpatialIndexingEnabled(true);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("游戏场景已加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
// 检查退出按键
|
||||||
|
auto &input = Application::instance().input();
|
||||||
|
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
||||||
|
Application::instance().quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRender(RenderBackend &renderer) override {
|
||||||
|
Scene::onRender(renderer);
|
||||||
|
|
||||||
|
// 绘制 FPS
|
||||||
|
auto &app = Application::instance();
|
||||||
|
std::string fpsText = "FPS: " + std::to_string(app.fps());
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void onExit() override {
|
||||||
|
E2D_LOG_INFO("游戏场景退出");
|
||||||
|
Scene::onExit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [03. 节点系统](03_Node_System.md)
|
||||||
|
- [04. 资源管理](04_Resource_Management.md)
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
# Extra2D API 教程 - 03. 节点系统
|
||||||
|
|
||||||
|
## 节点基础
|
||||||
|
|
||||||
|
节点(Node)是游戏对象的基本单位,可以包含子节点,形成树形结构。
|
||||||
|
|
||||||
|
### 创建节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
// 自定义渲染
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 节点层级
|
||||||
|
|
||||||
|
### 添加子节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 创建子节点
|
||||||
|
auto child = makePtr<MyNode>();
|
||||||
|
|
||||||
|
// 添加到场景
|
||||||
|
addChild(child);
|
||||||
|
|
||||||
|
// 在指定位置添加
|
||||||
|
addChild(child, 0); // z-order = 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 移除子节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 移除指定子节点
|
||||||
|
removeChild(child);
|
||||||
|
|
||||||
|
// 移除所有子节点
|
||||||
|
removeAllChildren();
|
||||||
|
|
||||||
|
// 通过名称移除
|
||||||
|
removeChildByName("myNode");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取子节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 获取子节点数量
|
||||||
|
size_t count = getChildren().size();
|
||||||
|
|
||||||
|
// 通过名称查找
|
||||||
|
auto node = getChildByName("myNode");
|
||||||
|
|
||||||
|
// 遍历子节点
|
||||||
|
for (auto &child : getChildren()) {
|
||||||
|
// 处理子节点
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 空间索引
|
||||||
|
|
||||||
|
### 启用空间索引
|
||||||
|
|
||||||
|
```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)) {
|
||||||
|
// 边界框相交
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 精灵节点
|
||||||
|
|
||||||
|
### 创建精灵
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 加载纹理
|
||||||
|
auto texture = resources.loadTexture("assets/player.png");
|
||||||
|
|
||||||
|
// 创建精灵
|
||||||
|
auto sprite = Sprite::create(texture);
|
||||||
|
|
||||||
|
// 设置位置
|
||||||
|
sprite->setPosition(Vec2(640.0f, 360.0f));
|
||||||
|
|
||||||
|
// 设置锚点(中心)
|
||||||
|
sprite->setAnchor(0.5f, 0.5f);
|
||||||
|
|
||||||
|
// 添加到场景
|
||||||
|
addChild(sprite);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 精灵动画
|
||||||
|
|
||||||
|
```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"));
|
||||||
|
|
||||||
|
// 播放动画
|
||||||
|
sprite->playAnimation(animation, true); // true = 循环播放
|
||||||
|
|
||||||
|
// 停止动画
|
||||||
|
sprite->stopAnimation();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Player : public Node {
|
||||||
|
public:
|
||||||
|
Player() {
|
||||||
|
setSpatialIndexed(true);
|
||||||
|
|
||||||
|
// 加载精灵
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
auto texture = resources.loadTexture("assets/player.png");
|
||||||
|
sprite_ = Sprite::create(texture);
|
||||||
|
sprite_->setAnchor(0.5f, 0.5f);
|
||||||
|
addChild(sprite_);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<float>(app.getConfig().width);
|
||||||
|
float height = static_cast<float>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override {
|
||||||
|
Vec2 pos = getPosition();
|
||||||
|
return Rect(pos.x - 25.0f, pos.y - 25.0f, 50.0f, 50.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<Sprite> sprite_;
|
||||||
|
Vec2 velocity_{100.0f, 100.0f};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [04. 资源管理](04_Resource_Management.md)
|
||||||
|
- [05. 输入处理](05_Input_Handling.md)
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
# Extra2D API 教程 - 04. 资源管理
|
||||||
|
|
||||||
|
## 资源管理器
|
||||||
|
|
||||||
|
Extra2D 使用资源管理器来统一加载和管理资源。
|
||||||
|
|
||||||
|
### 获取资源管理器
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字体资源
|
||||||
|
|
||||||
|
### 加载字体
|
||||||
|
|
||||||
|
```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");
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
E2D_LOG_ERROR("纹理加载失败!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建精灵
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto sprite = Sprite::create(texture);
|
||||||
|
sprite->setPosition(Vec2(640.0f, 360.0f));
|
||||||
|
addChild(sprite);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 音效资源
|
||||||
|
|
||||||
|
### 加载音效
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 加载音效
|
||||||
|
auto sound = resources.loadSound("assets/jump.wav");
|
||||||
|
|
||||||
|
// 播放音效
|
||||||
|
sound->play();
|
||||||
|
|
||||||
|
// 循环播放
|
||||||
|
sound->play(true);
|
||||||
|
|
||||||
|
// 停止播放
|
||||||
|
sound->stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 资源路径解析
|
||||||
|
|
||||||
|
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");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<FontAtlas> titleFont_;
|
||||||
|
Ptr<FontAtlas> infoFont_;
|
||||||
|
Ptr<Texture> playerTexture_;
|
||||||
|
Ptr<Texture> enemyTexture_;
|
||||||
|
Ptr<Sprite> player_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [05. 输入处理](05_Input_Handling.md)
|
||||||
|
- [06. 碰撞检测](06_Collision_Detection.md)
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
# Extra2D API 教程 - 05. 输入处理
|
||||||
|
|
||||||
|
## 输入系统
|
||||||
|
|
||||||
|
Extra2D 提供统一的输入处理接口,支持键盘和游戏手柄。
|
||||||
|
|
||||||
|
### 获取输入管理器
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检测按键按住
|
||||||
|
|
||||||
|
```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 |
|
||||||
|
|
||||||
|
## 摇杆输入
|
||||||
|
|
||||||
|
### 获取摇杆值
|
||||||
|
|
||||||
|
```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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 摇杆轴映射表
|
||||||
|
|
||||||
|
| 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float speed_ = 200.0f;
|
||||||
|
|
||||||
|
void jump() {
|
||||||
|
// 跳跃逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
void attack() {
|
||||||
|
// 攻击逻辑
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [06. 碰撞检测](06_Collision_Detection.md)
|
||||||
|
- [07. UI 系统](07_UI_System.md)
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
# Extra2D API 教程 - 06. 碰撞检测
|
||||||
|
|
||||||
|
## 空间索引系统
|
||||||
|
|
||||||
|
Extra2D 内置了空间索引系统,用于高效地进行碰撞检测。
|
||||||
|
|
||||||
|
### 启用空间索引
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 启用空间索引
|
||||||
|
setSpatialIndexingEnabled(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 碰撞节点
|
||||||
|
|
||||||
|
### 创建可碰撞节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class PhysicsNode : public Node {
|
||||||
|
public:
|
||||||
|
PhysicsNode(float size, const Color &color)
|
||||||
|
: size_(size), color_(color), isColliding_(false) {
|
||||||
|
// 启用空间索引(关键!)
|
||||||
|
setSpatialIndexed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 必须实现 getBoundingBox()
|
||||||
|
Rect getBoundingBox() const override {
|
||||||
|
Vec2 pos = getPosition();
|
||||||
|
return Rect(pos.x - size_ / 2, pos.y - size_ / 2, size_, size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setColliding(bool colliding) { isColliding_ = colliding; }
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float size_;
|
||||||
|
Color color_;
|
||||||
|
bool isColliding_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 碰撞检测
|
||||||
|
|
||||||
|
### 查询所有碰撞
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void performCollisionDetection() {
|
||||||
|
// 清除之前的碰撞状态
|
||||||
|
for (auto &node : nodes_) {
|
||||||
|
node->setColliding(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有碰撞(使用空间索引)
|
||||||
|
auto collisions = queryCollisions();
|
||||||
|
|
||||||
|
// 标记碰撞的节点
|
||||||
|
for (const auto &[nodeA, nodeB] : collisions) {
|
||||||
|
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
|
||||||
|
boxA->setColliding(true);
|
||||||
|
}
|
||||||
|
if (auto boxB = dynamic_cast<PhysicsNode *>(nodeB)) {
|
||||||
|
boxB->setColliding(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 空间索引策略
|
||||||
|
|
||||||
|
### 切换策略
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 启用空间索引
|
||||||
|
setSpatialIndexingEnabled(true);
|
||||||
|
|
||||||
|
// 设置空间索引策略
|
||||||
|
auto &spatialManager = getSpatialManager();
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::QuadTree); // 四叉树
|
||||||
|
// 或
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::SpatialHash); // 空间哈希
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换策略
|
||||||
|
void toggleStrategy() {
|
||||||
|
auto &spatialManager = getSpatialManager();
|
||||||
|
SpatialStrategy current = spatialManager.getCurrentStrategy();
|
||||||
|
|
||||||
|
if (current == SpatialStrategy::QuadTree) {
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::SpatialHash);
|
||||||
|
} else {
|
||||||
|
spatialManager.setStrategy(SpatialStrategy::QuadTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 策略对比
|
||||||
|
|
||||||
|
| 策略 | 适用场景 | 特点 |
|
||||||
|
|------|----------|------|
|
||||||
|
| QuadTree | 节点分布不均匀 | 适合稀疏场景 |
|
||||||
|
| SpatialHash | 节点分布均匀 | 适合密集场景 |
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class CollisionScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 启用空间索引
|
||||||
|
setSpatialIndexingEnabled(true);
|
||||||
|
|
||||||
|
// 创建碰撞节点
|
||||||
|
createNodes(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
// 更新节点位置
|
||||||
|
for (auto &node : nodes_) {
|
||||||
|
node->update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行碰撞检测
|
||||||
|
performCollisionDetection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRender(RenderBackend &renderer) override {
|
||||||
|
Scene::onRender(renderer);
|
||||||
|
|
||||||
|
// 绘制碰撞统计
|
||||||
|
std::string text = "Collisions: " + std::to_string(collisionCount_);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<PhysicsNode>> nodes_;
|
||||||
|
size_t collisionCount_ = 0;
|
||||||
|
|
||||||
|
void createNodes(size_t count) {
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
auto node = makePtr<PhysicsNode>(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_) {
|
||||||
|
node->setColliding(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询碰撞
|
||||||
|
auto collisions = queryCollisions();
|
||||||
|
collisionCount_ = collisions.size();
|
||||||
|
|
||||||
|
// 标记碰撞节点
|
||||||
|
for (const auto &[nodeA, nodeB] : collisions) {
|
||||||
|
if (auto boxA = dynamic_cast<PhysicsNode *>(nodeA)) {
|
||||||
|
boxA->setColliding(true);
|
||||||
|
}
|
||||||
|
if (auto boxB = dynamic_cast<PhysicsNode *>(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_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [07. UI 系统](07_UI_System.md)
|
||||||
|
- [08. 音频系统](08_Audio_System.md)
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
# Extra2D API 教程 - 07. UI 系统
|
||||||
|
|
||||||
|
## 按钮 (Button)
|
||||||
|
|
||||||
|
Extra2D 提供 Button 组件用于创建交互式按钮。
|
||||||
|
|
||||||
|
### 创建按钮
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 创建按钮
|
||||||
|
auto button = Button::create();
|
||||||
|
|
||||||
|
// 设置位置
|
||||||
|
button->setPosition(Vec2(640.0f, 360.0f));
|
||||||
|
|
||||||
|
// 设置锚点(中心)
|
||||||
|
button->setAnchor(0.5f, 0.5f);
|
||||||
|
|
||||||
|
// 添加到场景
|
||||||
|
addChild(button);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置按钮文本
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 加载字体
|
||||||
|
auto font = resources.loadFont("assets/font.ttf", 28, true);
|
||||||
|
|
||||||
|
// 设置按钮字体和文本
|
||||||
|
button->setFont(font);
|
||||||
|
button->setText("点击我");
|
||||||
|
button->setTextColor(Colors::Black);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置按钮样式
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 设置背景颜色(普通、悬停、按下)
|
||||||
|
button->setBackgroundColor(
|
||||||
|
Colors::White, // 普通状态
|
||||||
|
Colors::LightGray, // 悬停状态
|
||||||
|
Colors::Gray // 按下状态
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置边框
|
||||||
|
button->setBorder(Colors::Black, 2.0f);
|
||||||
|
|
||||||
|
// 设置内边距
|
||||||
|
button->setPadding(Vec2(20.0f, 10.0f));
|
||||||
|
|
||||||
|
// 设置固定大小
|
||||||
|
button->setCustomSize(200.0f, 50.0f);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 透明按钮
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 创建透明按钮(仅文本可点击)
|
||||||
|
auto button = Button::create();
|
||||||
|
button->setFont(font);
|
||||||
|
button->setText("菜单项");
|
||||||
|
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));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 菜单系统
|
||||||
|
|
||||||
|
### 创建菜单
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MenuScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
font_ = resources.loadFont("assets/font.ttf", 28, true);
|
||||||
|
|
||||||
|
float centerX = 640.0f;
|
||||||
|
float startY = 300.0f;
|
||||||
|
|
||||||
|
// 创建菜单按钮
|
||||||
|
createMenuButton("开始游戏", centerX, startY, 0);
|
||||||
|
createMenuButton("继续游戏", centerX, startY + 50.0f, 1);
|
||||||
|
createMenuButton("退出", centerX, startY + 100.0f, 2);
|
||||||
|
|
||||||
|
menuCount_ = 3;
|
||||||
|
selectedIndex_ = 0;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A键确认
|
||||||
|
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
||||||
|
executeMenuItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
std::vector<Ptr<Button>> 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 绘制文字
|
||||||
|
|
||||||
|
### 基本文字绘制
|
||||||
|
|
||||||
|
```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<FontAtlas> font_;
|
||||||
|
Ptr<Button> titleBtn_;
|
||||||
|
std::vector<Ptr<Button>> menuButtons_;
|
||||||
|
int selectedIndex_ = 0;
|
||||||
|
int menuCount_ = 0;
|
||||||
|
|
||||||
|
void createMenuButton(const std::string &text, float x, float y, int index) {
|
||||||
|
auto btn = Button::create();
|
||||||
|
btn->setFont(font_);
|
||||||
|
btn->setText(text);
|
||||||
|
btn->setTextColor(Colors::White);
|
||||||
|
btn->setBackgroundColor(
|
||||||
|
Colors::Transparent,
|
||||||
|
Colors::Transparent,
|
||||||
|
Colors::Transparent
|
||||||
|
);
|
||||||
|
btn->setBorder(Colors::Transparent, 0.0f);
|
||||||
|
btn->setAnchor(0.5f, 0.5f);
|
||||||
|
btn->setPosition(x, y);
|
||||||
|
addChild(btn);
|
||||||
|
menuButtons_.push_back(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateMenuColors() {
|
||||||
|
for (int i = 0; i < menuButtons_.size(); ++i) {
|
||||||
|
if (menuButtons_[i]) {
|
||||||
|
menuButtons_[i]->setTextColor(
|
||||||
|
i == selectedIndex_ ? Colors::Red : Colors::White
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeMenuItem() {
|
||||||
|
switch (selectedIndex_) {
|
||||||
|
case 0: /* 新游戏 */ break;
|
||||||
|
case 1: /* 继续 */ break;
|
||||||
|
case 2: Application::instance().quit(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [08. 音频系统](08_Audio_System.md)
|
||||||
|
|
@ -0,0 +1,323 @@
|
||||||
|
# Extra2D API 教程 - 08. 音频系统
|
||||||
|
|
||||||
|
## 音频系统概述
|
||||||
|
|
||||||
|
Extra2D 使用 SDL2_mixer 作为音频后端,支持 WAV、MP3、OGG 等格式。
|
||||||
|
|
||||||
|
## 音效播放
|
||||||
|
|
||||||
|
### 加载和播放音效
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 获取资源管理器
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
|
||||||
|
// 加载音效
|
||||||
|
auto jumpSound = resources.loadSound("assets/jump.wav");
|
||||||
|
auto attackSound = resources::loadSound("assets/attack.ogg");
|
||||||
|
|
||||||
|
// 播放音效(一次)
|
||||||
|
jumpSound->play();
|
||||||
|
|
||||||
|
// 循环播放
|
||||||
|
backgroundMusic->play(true);
|
||||||
|
|
||||||
|
// 停止播放
|
||||||
|
jumpSound->stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 音频控制器
|
||||||
|
|
||||||
|
### 创建音频控制器节点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class AudioController : public Node {
|
||||||
|
public:
|
||||||
|
static Ptr<AudioController> create() {
|
||||||
|
return makePtr<AudioController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnter() override {
|
||||||
|
Node::onEnter();
|
||||||
|
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
|
||||||
|
// 加载音效
|
||||||
|
sounds_["jump"] = resources.loadSound("assets/jump.wav");
|
||||||
|
sounds_["attack"] = resources.loadSound("assets/attack.wav");
|
||||||
|
sounds_["bgm"] = resources.loadSound("assets/bgm.mp3");
|
||||||
|
|
||||||
|
// 播放背景音乐
|
||||||
|
if (sounds_["bgm"]) {
|
||||||
|
sounds_["bgm"]->play(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void playSound(const std::string &name) {
|
||||||
|
auto it = sounds_.find(name);
|
||||||
|
if (it != sounds_.end() && it->second) {
|
||||||
|
it->second->play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
if (!enabled) {
|
||||||
|
// 停止所有音效
|
||||||
|
for (auto &[name, sound] : sounds_) {
|
||||||
|
if (sound) {
|
||||||
|
sound->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, Ptr<Sound>> sounds_;
|
||||||
|
bool enabled_ = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在场景中使用
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class GameScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 创建音频控制器
|
||||||
|
auto audio = AudioController::create();
|
||||||
|
audio->setName("audio_controller");
|
||||||
|
addChild(audio);
|
||||||
|
setAudioController(audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
void playJumpSound() {
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->playSound("jump");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void playAttackSound() {
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->playSound("attack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleSound() {
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->setEnabled(!audio->isEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<AudioController> audioController_;
|
||||||
|
|
||||||
|
void setAudioController(Ptr<AudioController> controller) {
|
||||||
|
audioController_ = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioController* getAudioController() const {
|
||||||
|
return audioController_.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// AudioController.h
|
||||||
|
#pragma once
|
||||||
|
#include <extra2d/extra2d.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
class AudioController : public extra2d::Node {
|
||||||
|
public:
|
||||||
|
static extra2d::Ptr<AudioController> create();
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void playSound(const std::string &name);
|
||||||
|
void setEnabled(bool enabled);
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, extra2d::Ptr<extra2d::Sound>> sounds_;
|
||||||
|
bool enabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
|
|
||||||
|
// AudioController.cpp
|
||||||
|
#include "AudioController.h"
|
||||||
|
|
||||||
|
using namespace extra2d;
|
||||||
|
|
||||||
|
Ptr<AudioController> AudioController::create() {
|
||||||
|
return makePtr<AudioController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::onEnter() {
|
||||||
|
Node::onEnter();
|
||||||
|
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
|
||||||
|
// 加载所有音效
|
||||||
|
sounds_["jump"] = resources.loadSound("assets/audio/jump.wav");
|
||||||
|
sounds_["attack"] = resources.loadSound("assets/audio/attack.wav");
|
||||||
|
sounds_["hit"] = resources.loadSound("assets/audio/hit.wav");
|
||||||
|
sounds_["bgm"] = resources.loadSound("assets/audio/background.mp3");
|
||||||
|
|
||||||
|
// 播放背景音乐
|
||||||
|
if (sounds_["bgm"]) {
|
||||||
|
sounds_["bgm"]->play(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::playSound(const std::string &name) {
|
||||||
|
if (!enabled_) return;
|
||||||
|
|
||||||
|
auto it = sounds_.find(name);
|
||||||
|
if (it != sounds_.end() && it->second) {
|
||||||
|
it->second->play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::setEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
// 停止所有音效
|
||||||
|
for (auto &[name, sound] : sounds_) {
|
||||||
|
if (sound) {
|
||||||
|
sound->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 重新播放背景音乐
|
||||||
|
auto it = sounds_.find("bgm");
|
||||||
|
if (it != sounds_.end() && it->second) {
|
||||||
|
it->second->play(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GameScene.cpp
|
||||||
|
#include "AudioController.h"
|
||||||
|
|
||||||
|
class GameScene : public Scene {
|
||||||
|
public:
|
||||||
|
void onEnter() override {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
// 创建音频控制器
|
||||||
|
auto audio = game::AudioController::create();
|
||||||
|
audio->setName("audio_controller");
|
||||||
|
addChild(audio);
|
||||||
|
audioController_ = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdate(float dt) override {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
auto &input = Application::instance().input();
|
||||||
|
|
||||||
|
// A键跳跃
|
||||||
|
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
||||||
|
jump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// B键攻击
|
||||||
|
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_B)) {
|
||||||
|
attack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// X键切换音效
|
||||||
|
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
|
||||||
|
toggleSound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<game::AudioController> audioController_;
|
||||||
|
|
||||||
|
void jump() {
|
||||||
|
// 播放跳跃音效
|
||||||
|
if (audioController_) {
|
||||||
|
audioController_->playSound("jump");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void attack() {
|
||||||
|
// 播放攻击音效
|
||||||
|
if (audioController_) {
|
||||||
|
audioController_->playSound("attack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleSound() {
|
||||||
|
if (audioController_) {
|
||||||
|
audioController_->setEnabled(!audioController_->isEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 音频格式支持
|
||||||
|
|
||||||
|
| 格式 | 支持 |
|
||||||
|
|------|------|
|
||||||
|
| WAV | ✓ |
|
||||||
|
| MP3 | ✓ |
|
||||||
|
| OGG | ✓ |
|
||||||
|
| FLAC | ✓ |
|
||||||
|
| MOD | ✓ |
|
||||||
|
|
||||||
|
## 音量控制
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 设置音效音量 (0-128)
|
||||||
|
Mix_Volume(-1, MIX_MAX_VOLUME / 2); // 所有音效
|
||||||
|
Mix_Volume(channel, volume); // 指定通道
|
||||||
|
|
||||||
|
// 设置音乐音量 (0-128)
|
||||||
|
Mix_VolumeMusic(volume);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **预加载音效**: 在 `onEnter()` 中加载所有需要的音效
|
||||||
|
2. **使用音频控制器**: 统一管理音效,方便控制开关
|
||||||
|
3. **音效开关**: 提供用户选项控制音效开关
|
||||||
|
4. **资源释放**: 音效资源会自动管理,无需手动释放
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
Extra2D 的音频系统简单易用:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 加载
|
||||||
|
auto sound = resources.loadSound("assets/sound.wav");
|
||||||
|
|
||||||
|
// 播放
|
||||||
|
sound->play(); // 一次
|
||||||
|
sound->play(true); // 循环
|
||||||
|
|
||||||
|
// 停止
|
||||||
|
sound->stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**教程完成!** 您已经学习了 Extra2D 的所有核心功能:
|
||||||
|
|
||||||
|
1. [快速开始](01_Quick_Start.md)
|
||||||
|
2. [场景系统](02_Scene_System.md)
|
||||||
|
3. [节点系统](03_Node_System.md)
|
||||||
|
4. [资源管理](04_Resource_Management.md)
|
||||||
|
5. [输入处理](05_Input_Handling.md)
|
||||||
|
6. [碰撞检测](06_Collision_Detection.md)
|
||||||
|
7. [UI 系统](07_UI_System.md)
|
||||||
|
8. [音频系统](08_Audio_System.md)
|
||||||
|
|
@ -0,0 +1,327 @@
|
||||||
|
# Extra2D 构建系统文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Extra2D 使用 **Xmake** 作为构建系统,支持 **MinGW (Windows)** 和 **Nintendo Switch** 两个平台。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Extra2D/
|
||||||
|
├── xmake.lua # 主构建脚本
|
||||||
|
├── xmake/
|
||||||
|
│ ├── engine.lua # 引擎库定义
|
||||||
|
│ └── toolchains/
|
||||||
|
│ └── switch.lua # Switch 工具链定义
|
||||||
|
├── Extra2D/
|
||||||
|
│ ├── src/ # 引擎源码
|
||||||
|
│ └── include/ # 引擎头文件
|
||||||
|
├── squirrel/ # Squirrel 脚本引擎
|
||||||
|
└── examples/ # 示例程序
|
||||||
|
├── hello_world/
|
||||||
|
├── collision_demo/
|
||||||
|
├── push_box/
|
||||||
|
└── spatial_index_demo/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境准备
|
||||||
|
|
||||||
|
### MinGW (Windows) 平台
|
||||||
|
|
||||||
|
1. **安装 MinGW-w64**
|
||||||
|
- 下载地址: https://www.mingw-w64.org/downloads/
|
||||||
|
- 或使用 MSYS2: `pacman -S mingw-w64-x86_64-toolchain`
|
||||||
|
|
||||||
|
2. **安装 Xmake**
|
||||||
|
- 下载地址: https://xmake.io/#/zh-cn/guide/installation
|
||||||
|
|
||||||
|
3. **安装依赖包**
|
||||||
|
```bash
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nintendo Switch 平台
|
||||||
|
|
||||||
|
1. **安装 devkitPro**
|
||||||
|
- 下载地址: https://devkitpro.org/wiki/Getting_Started
|
||||||
|
- Windows 安装程序会自动设置环境变量
|
||||||
|
|
||||||
|
2. **设置环境变量**
|
||||||
|
```powershell
|
||||||
|
# PowerShell
|
||||||
|
$env:DEVKITPRO="C:/devkitPro"
|
||||||
|
|
||||||
|
# 或永久设置(系统属性 -> 环境变量)
|
||||||
|
[Environment]::SetEnvironmentVariable("DEVKITPRO", "C:/devkitPro", "User")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **在 MSYS2 中安装 Switch 库**
|
||||||
|
```bash
|
||||||
|
# 打开 MSYS2 (devkitPro 提供的)
|
||||||
|
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
|
||||||
|
|
||||||
|
# 或安装所有 Switch 开发库
|
||||||
|
pacman -S $(pacman -Slq dkp-libs | grep switch-)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **验证安装**
|
||||||
|
```bash
|
||||||
|
ls $DEVKITPRO/portlibs/switch/include/SDL2
|
||||||
|
ls $DEVKITPRO/portlibs/switch/include/glm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主构建脚本 (xmake.lua)
|
||||||
|
|
||||||
|
### 项目元信息
|
||||||
|
- **项目名称**: Extra2D
|
||||||
|
- **版本**: 3.1.0
|
||||||
|
- **许可证**: MIT
|
||||||
|
- **语言标准**: C++17
|
||||||
|
- **编码**: UTF-8
|
||||||
|
|
||||||
|
### 构建选项
|
||||||
|
|
||||||
|
| 选项 | 默认值 | 描述 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `examples` | true | 构建示例程序 |
|
||||||
|
| `debug_logs` | false | 启用调试日志 |
|
||||||
|
|
||||||
|
### 平台检测逻辑
|
||||||
|
|
||||||
|
1. 获取主机平台: `os.host()`
|
||||||
|
2. 获取目标平台: `get_config("plat")` 或主机平台
|
||||||
|
3. 平台回退: 如果不支持,Windows 回退到 `mingw`
|
||||||
|
4. 设置平台: `set_plat(target_plat)`
|
||||||
|
5. 设置架构:
|
||||||
|
- Switch: `arm64`
|
||||||
|
- MinGW: `x86_64`
|
||||||
|
|
||||||
|
### 依赖包 (MinGW 平台)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 引擎库定义 (xmake/engine.lua)
|
||||||
|
|
||||||
|
### 目标: extra2d
|
||||||
|
- **类型**: 静态库 (`static`)
|
||||||
|
- **源文件**:
|
||||||
|
- `Extra2D/src/**.cpp`
|
||||||
|
- `Extra2D/src/glad/glad.c`
|
||||||
|
- `squirrel/squirrel/*.cpp`
|
||||||
|
- `squirrel/sqstdlib/*.cpp`
|
||||||
|
|
||||||
|
### 头文件路径
|
||||||
|
- `Extra2D/include` (public)
|
||||||
|
- `squirrel/include` (public)
|
||||||
|
- `Extra2D/include/extra2d/platform` (public)
|
||||||
|
|
||||||
|
### 平台配置
|
||||||
|
|
||||||
|
#### Switch 平台
|
||||||
|
```lua
|
||||||
|
add_includedirs(devkitPro .. "/portlibs/switch/include")
|
||||||
|
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
|
||||||
|
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### MinGW 平台
|
||||||
|
```lua
|
||||||
|
add_packages("glm", "libsdl2", "libsdl2_mixer")
|
||||||
|
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编译器标志
|
||||||
|
- `-Wall`, `-Wextra`
|
||||||
|
- `-Wno-unused-variable`, `-Wno-unused-function`
|
||||||
|
- `-Wno-deprecated-copy`, `-Wno-class-memaccess`
|
||||||
|
|
||||||
|
### 构建模式
|
||||||
|
- **Debug**: `-O0`, `-g`, 定义 `E2D_DEBUG`, `_DEBUG`
|
||||||
|
- **Release**: `-O2`, 定义 `NDEBUG`
|
||||||
|
|
||||||
|
## Switch 工具链 (xmake/toolchains/switch.lua)
|
||||||
|
|
||||||
|
### 工具链: switch
|
||||||
|
- **类型**: standalone
|
||||||
|
- **描述**: Nintendo Switch devkitA64 工具链
|
||||||
|
|
||||||
|
### 工具路径
|
||||||
|
- **CC**: `aarch64-none-elf-gcc.exe`
|
||||||
|
- **CXX**: `aarch64-none-elf-g++.exe`
|
||||||
|
- **LD**: `aarch64-none-elf-g++.exe`
|
||||||
|
- **AR**: `aarch64-none-elf-gcc-ar.exe`
|
||||||
|
|
||||||
|
### 架构标志
|
||||||
|
```
|
||||||
|
-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 链接标志
|
||||||
|
```
|
||||||
|
-specs=switch.specs -g
|
||||||
|
```
|
||||||
|
|
||||||
|
### 预定义宏
|
||||||
|
- `__SWITCH__`
|
||||||
|
- `__NX__`
|
||||||
|
- `MA_SWITCH`
|
||||||
|
- `PFD_SWITCH`
|
||||||
|
|
||||||
|
### 系统库
|
||||||
|
- `nx` (libnx)
|
||||||
|
- `m` (math)
|
||||||
|
|
||||||
|
## 示例程序构建脚本
|
||||||
|
|
||||||
|
### 通用结构
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- 使用与主项目相同的平台配置
|
||||||
|
if is_plat("switch") then
|
||||||
|
-- Switch 平台配置
|
||||||
|
elseif is_plat("mingw") then
|
||||||
|
-- MinGW 平台配置
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Switch 平台配置
|
||||||
|
- 设置平台: `set_plat("switch")`
|
||||||
|
- 设置架构: `set_arch("arm64")`
|
||||||
|
- 设置工具链: `set_toolchains("switch")`
|
||||||
|
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
|
||||||
|
- 构建后生成 NRO 文件
|
||||||
|
|
||||||
|
### MinGW 平台配置
|
||||||
|
- 设置平台: `set_plat("mingw")`
|
||||||
|
- 设置架构: `set_arch("x86_64")`
|
||||||
|
- 设置输出目录: `set_targetdir("../../build/examples/xxx")`
|
||||||
|
- 链接标志: `-mwindows`
|
||||||
|
- 构建后复制资源文件
|
||||||
|
|
||||||
|
## 构建命令
|
||||||
|
|
||||||
|
### 配置项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 默认配置 (MinGW)
|
||||||
|
xmake f -c
|
||||||
|
|
||||||
|
# 指定平台 (使用 -p 参数)
|
||||||
|
xmake f -c -p mingw
|
||||||
|
xmake f -c -p switch
|
||||||
|
|
||||||
|
# 指定 MinGW 路径(如果不在默认位置)
|
||||||
|
xmake f -c -p mingw --mingw=C:\mingw
|
||||||
|
|
||||||
|
# 禁用示例
|
||||||
|
xmake f --examples=n
|
||||||
|
|
||||||
|
# 启用调试日志
|
||||||
|
xmake f --debug_logs=y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安装依赖 (MinGW)
|
||||||
|
```bash
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建项目
|
||||||
|
```bash
|
||||||
|
# 构建所有目标
|
||||||
|
xmake
|
||||||
|
|
||||||
|
# 构建特定目标
|
||||||
|
xmake -r extra2d
|
||||||
|
xmake -r push_box
|
||||||
|
|
||||||
|
# 并行构建
|
||||||
|
xmake -j4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行程序
|
||||||
|
```bash
|
||||||
|
# 运行示例
|
||||||
|
xmake run push_box
|
||||||
|
xmake run hello_world
|
||||||
|
```
|
||||||
|
|
||||||
|
### 清理构建
|
||||||
|
```bash
|
||||||
|
xmake clean
|
||||||
|
xmake f -c # 重新配置
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
build/
|
||||||
|
├── examples/
|
||||||
|
│ ├── hello_world/
|
||||||
|
│ │ ├── hello_world.exe # MinGW
|
||||||
|
│ │ ├── hello_world.nro # Switch
|
||||||
|
│ │ └── assets/ # 资源文件
|
||||||
|
│ ├── push_box/
|
||||||
|
│ ├── collision_demo/
|
||||||
|
│ └── spatial_index_demo/
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键设计决策
|
||||||
|
|
||||||
|
### 1. 平台检测
|
||||||
|
- 使用 `is_plat()` 而不是手动检测,确保与主项目一致
|
||||||
|
- 示例脚本继承主项目的平台配置
|
||||||
|
|
||||||
|
### 2. 资源处理
|
||||||
|
- **Switch**: 使用 romfs 嵌入 NRO 文件
|
||||||
|
- **MinGW**: 构建后复制到输出目录
|
||||||
|
|
||||||
|
### 3. 依赖管理
|
||||||
|
- **MinGW**: 使用 Xmake 包管理器 (`add_requires`)
|
||||||
|
- **Switch**: 使用 devkitPro 提供的库
|
||||||
|
|
||||||
|
### 4. 工具链隔离
|
||||||
|
- Switch 工具链定义在单独文件中
|
||||||
|
- 通过 `set_toolchains("switch")` 切换
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 依赖包找不到
|
||||||
|
```bash
|
||||||
|
xmake repo -u
|
||||||
|
xmake require -y
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Switch 工具链找不到
|
||||||
|
- 确保 DEVKITPRO 环境变量设置正确
|
||||||
|
- 默认路径: `C:/devkitPro`
|
||||||
|
|
||||||
|
### 3. 平台配置不匹配
|
||||||
|
- 使用 `xmake show` 查看当前配置
|
||||||
|
- 使用 `xmake f -c` 重新配置
|
||||||
|
|
||||||
|
### 4. MinGW 路径问题
|
||||||
|
如果 MinGW 安装在非默认位置,使用 `--mingw` 参数指定:
|
||||||
|
```bash
|
||||||
|
xmake f -c -p mingw --mingw=D:\Tools\mingw64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Switch 库找不到
|
||||||
|
确保在 MSYS2 中安装了必要的库:
|
||||||
|
```bash
|
||||||
|
pacman -S switch-sdl2 switch-sdl2_mixer switch-glm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扩展指南
|
||||||
|
|
||||||
|
### 添加新示例
|
||||||
|
1. 在 `examples/` 下创建新目录
|
||||||
|
2. 创建 `xmake.lua` 构建脚本
|
||||||
|
3. 在 `xmake.lua` 中添加 `includes("examples/new_example")`
|
||||||
|
|
||||||
|
### 添加新平台
|
||||||
|
1. 在 `xmake/toolchains/` 下创建工具链定义
|
||||||
|
2. 在 `xmake.lua` 中添加平台检测逻辑
|
||||||
|
3. 在 `xmake/engine.lua` 中添加平台配置
|
||||||
|
|
@ -104,7 +104,7 @@ public:
|
||||||
|
|
||||||
// 检查退出按键
|
// 检查退出按键
|
||||||
auto &input = Application::instance().input();
|
auto &input = Application::instance().input();
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||||
E2D_LOG_INFO("退出应用");
|
E2D_LOG_INFO("退出应用");
|
||||||
Application::instance().quit();
|
Application::instance().quit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ public:
|
||||||
// 检查退出按键
|
// 检查退出按键
|
||||||
auto &input = Application::instance().input();
|
auto &input = Application::instance().input();
|
||||||
|
|
||||||
// Switch: 使用手柄 START 按钮
|
// 使用手柄 START 按钮退出 (GamepadButton::Start)
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||||
E2D_LOG_INFO("退出应用 (START 按钮)");
|
E2D_LOG_INFO("退出应用 (START 按钮)");
|
||||||
Application::instance().quit();
|
Application::instance().quit();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,21 +122,21 @@ void StartScene::onUpdate(float dt) {
|
||||||
auto& input = app.input();
|
auto& input = app.input();
|
||||||
|
|
||||||
// 方向键上下切换选择
|
// 方向键上下切换选择
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_UP)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
||||||
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
|
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
|
||||||
updateMenuColors();
|
updateMenuColors();
|
||||||
} else if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_DPAD_DOWN)) {
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
||||||
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
|
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
|
||||||
updateMenuColors();
|
updateMenuColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A键确认
|
// A键确认
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
executeMenuItem();
|
executeMenuItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y键切换音效
|
// X键切换音效
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||||
g_SoundOpen = !g_SoundOpen;
|
g_SoundOpen = !g_SoundOpen;
|
||||||
if (auto audio = getAudioController()) {
|
if (auto audio = getAudioController()) {
|
||||||
audio->setEnabled(g_SoundOpen);
|
audio->setEnabled(g_SoundOpen);
|
||||||
|
|
|
||||||
|
|
@ -156,23 +156,23 @@ public:
|
||||||
|
|
||||||
// 检查退出按键
|
// 检查退出按键
|
||||||
auto &input = Application::instance().input();
|
auto &input = Application::instance().input();
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
if (input.isButtonPressed(GamepadButton::Start)) {
|
||||||
E2D_LOG_INFO("退出应用");
|
E2D_LOG_INFO("退出应用");
|
||||||
Application::instance().quit();
|
Application::instance().quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 A 键添加节点
|
// 按 A 键添加节点
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
if (input.isButtonPressed(GamepadButton::A)) {
|
||||||
addNodes(100);
|
addNodes(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 B 键减少节点
|
// 按 B 键减少节点
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_B)) {
|
if (input.isButtonPressed(GamepadButton::B)) {
|
||||||
removeNodes(100);
|
removeNodes(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 X 键切换空间索引策略
|
// 按 X 键切换空间索引策略
|
||||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_X)) {
|
if (input.isButtonPressed(GamepadButton::X)) {
|
||||||
toggleSpatialStrategy();
|
toggleSpatialStrategy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue