feat(平台兼容性): 添加跨平台支持并重构代码结构
refactor(构建系统): 重构xmake配置以支持多平台构建 feat(文件系统): 新增FileSystem类处理跨平台路径 refactor(窗口系统): 重构Window类支持PC平台特性 feat(输入系统): 扩展Input类支持PC输入设备 docs: 添加PC构建指南和Switch构建指南文档 style: 统一平台相关代码风格和命名规范 chore: 删除过时的文档和配置文件
This commit is contained in:
parent
5880159991
commit
2e08bff567
|
|
@ -1,80 +0,0 @@
|
|||
## DataStore 改进计划(基于 Switch FS Save 示例)
|
||||
|
||||
### 阶段一:Switch 平台存档支持
|
||||
|
||||
#### 1.1 添加存档挂载管理
|
||||
- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h) 和 [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp)
|
||||
- **内容**:
|
||||
- 添加 `mountSaveData()` 方法挂载存档
|
||||
- 添加 `unmountSaveData()` 方法卸载存档
|
||||
- 添加 `commitSaveData()` 方法提交更改
|
||||
- 支持自动挂载/卸载 RAII 模式
|
||||
- **预期收益**: 支持 Switch 官方存档系统
|
||||
|
||||
#### 1.2 用户账户集成
|
||||
- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h)
|
||||
- **内容**:
|
||||
- 添加 `setUserId()` / `getUserId()` 方法
|
||||
- 自动获取当前用户ID(使用 `accountGetPreselectedUser`)
|
||||
- 支持用户特定的存档路径
|
||||
- **预期收益**: 支持多用户存档隔离
|
||||
|
||||
### 阶段二:事务和缓存优化
|
||||
|
||||
#### 2.1 事务支持
|
||||
- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp)
|
||||
- **内容**:
|
||||
- 添加 `beginTransaction()` 开始事务
|
||||
- 添加 `commit()` 提交事务
|
||||
- 添加 `rollback()` 回滚事务
|
||||
- 批量写入优化,减少文件IO
|
||||
- **预期收益**: 数据一致性和性能提升
|
||||
|
||||
#### 2.2 缓存机制
|
||||
- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp)
|
||||
- **内容**:
|
||||
- 添加内存缓存层
|
||||
- 延迟写入(Lazy Write)
|
||||
- 缓存失效策略
|
||||
- **预期收益**: 减少文件IO,提升读写性能
|
||||
|
||||
### 阶段三:数据格式扩展
|
||||
|
||||
#### 3.1 JSON 支持
|
||||
- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h)
|
||||
- **内容**:
|
||||
- 添加 JSON 格式支持(保留 INI 兼容)
|
||||
- 嵌套数据结构支持
|
||||
- 数组类型支持
|
||||
- **预期收益**: 更灵活的数据结构
|
||||
|
||||
#### 3.2 二进制格式支持
|
||||
- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp)
|
||||
- **内容**:
|
||||
- 添加二进制序列化格式
|
||||
- 更小的文件体积
|
||||
- 更快的读写速度
|
||||
- **预期收益**: 性能和存储优化
|
||||
|
||||
### 阶段四:安全和备份
|
||||
|
||||
#### 4.1 数据校验
|
||||
- **目标**: [data.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/utils/data.cpp)
|
||||
- **内容**:
|
||||
- 添加 CRC32/MD5 校验
|
||||
- 数据完整性检查
|
||||
- 损坏数据恢复机制
|
||||
- **预期收益**: 数据可靠性
|
||||
|
||||
#### 4.2 自动备份
|
||||
- **目标**: [data.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/utils/data.h)
|
||||
- **内容**:
|
||||
- 自动备份机制
|
||||
- 多版本存档
|
||||
- 备份恢复接口
|
||||
- **预期收益**: 数据安全
|
||||
|
||||
---
|
||||
|
||||
**预计总工作量**: 6-8 小时
|
||||
**优先级**: 高(1.1, 1.2)> 中(2.1, 3.1)> 低(2.2, 3.2, 4.1, 4.2)
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# Extra2D 数据结构与算法优化计划
|
||||
|
||||
## 概述
|
||||
针对分析发现的潜在问题,制定以下分阶段优化计划。
|
||||
|
||||
---
|
||||
|
||||
## 阶段一:高优先级问题修复(预计 3-4 天)
|
||||
|
||||
### 1.1 四叉树碰撞检测优化
|
||||
**目标**: 解决 O(n²) 碰撞检测性能问题
|
||||
**文件**: `src/spatial/quadtree.cpp`
|
||||
**方案**:
|
||||
- 实现扫描线算法或 AABB 树加速碰撞检测
|
||||
- 优化节点分裂策略,避免对象过度集中
|
||||
- 添加单元测试验证性能改进
|
||||
|
||||
### 1.2 空间哈希内存布局重构
|
||||
**目标**: 减少内存碎片,提高缓存友好性
|
||||
**文件**: `include/extra2d/spatial/spatial_hash.h`, `src/spatial/spatial_hash.cpp`
|
||||
**方案**:
|
||||
- 使用单一连续内存结构替代嵌套哈希表
|
||||
- 实现对象池复用 Cell 内存
|
||||
- 查询时使用线程本地静态缓冲区避免临时分配
|
||||
|
||||
### 1.3 动画帧数据布局优化
|
||||
**目标**: 提高动画系统缓存命中率
|
||||
**文件**: `include/extra2d/animation/animation_frame.h`, `include/extra2d/animation/frame_property.h`
|
||||
**方案**:
|
||||
- 采用结构体数组(SoA)布局存储帧数据
|
||||
- 热数据(delay, offset)和冷数据(碰撞盒)分离
|
||||
- 使用紧凑位域替代 std::variant 存储属性
|
||||
|
||||
### 1.4 Node 循环引用风险修复
|
||||
**目标**: 消除内存泄漏风险
|
||||
**文件**: `include/extra2d/scene/node.h`, `src/scene/node.cpp`
|
||||
**方案**:
|
||||
- 审查所有 shared_ptr 使用场景
|
||||
- 添加对象所有权文档说明
|
||||
- 考虑使用 intrusive_ptr 替代 shared_ptr
|
||||
|
||||
---
|
||||
|
||||
## 阶段二:中优先级优化(预计 2-3 天)
|
||||
|
||||
### 2.1 变换矩阵脏标记传播
|
||||
**目标**: 优化深层场景树性能
|
||||
**文件**: `src/scene/node.cpp`
|
||||
**方案**:
|
||||
- 实现脏标记传播机制替代递归计算
|
||||
- 延迟计算世界变换直到实际需要
|
||||
|
||||
### 2.2 纹理池 LRU 优化
|
||||
**目标**: 提高缓存局部性
|
||||
**文件**: `include/extra2d/graphics/texture_pool.h`
|
||||
**方案**:
|
||||
- 使用侵入式链表替代 std::list
|
||||
- 使用数组索引代替指针
|
||||
|
||||
### 2.3 子节点排序优化
|
||||
**目标**: 减少排序开销
|
||||
**文件**: `src/scene/node.cpp`
|
||||
**方案**:
|
||||
- 使用插入排序(如果大部分已有序)
|
||||
- 或使用 std::multiset 维护有序性
|
||||
|
||||
---
|
||||
|
||||
## 阶段三:低优先级改进(预计 1-2 天)
|
||||
|
||||
### 3.1 渲染命令内存优化
|
||||
**目标**: 减少 RenderCommand 内存占用
|
||||
**文件**: `include/extra2d/graphics/render_command.h`
|
||||
**方案**:
|
||||
- 类型分离的命令队列
|
||||
- 或自定义联合体替代 std::variant
|
||||
|
||||
### 3.2 资源管理软引用缓存
|
||||
**目标**: 减少资源重复加载
|
||||
**文件**: `include/extra2d/resource/resource_manager.h`
|
||||
**方案**:
|
||||
- 实现 LRU 策略管理缓存
|
||||
- 软引用保留最近使用资源
|
||||
|
||||
---
|
||||
|
||||
## 实施顺序建议
|
||||
|
||||
1. **首先修复内存安全问题** (Node 循环引用)
|
||||
2. **然后优化性能瓶颈** (四叉树、空间哈希)
|
||||
3. **最后进行内存布局优化** (动画帧、渲染命令)
|
||||
|
||||
## 预期收益
|
||||
|
||||
| 优化项 | 预期性能提升 | 内存节省 |
|
||||
|--------|-------------|----------|
|
||||
| 四叉树碰撞检测 | 50-80% (大量对象时) | - |
|
||||
| 空间哈希重构 | 20-30% | 30-40% |
|
||||
| 动画帧布局 | 15-25% | 40-50% |
|
||||
| 变换矩阵优化 | 10-20% (深层场景) | - |
|
||||
| 纹理池 LRU | - | 20-30% |
|
||||
|
||||
---
|
||||
|
||||
请确认此计划后,我将开始实施具体的代码修改。
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
## Node 节点优化计划
|
||||
|
||||
### 阶段一:内存布局与数据结构优化
|
||||
|
||||
#### 1.1 成员变量重排(减少内存占用)
|
||||
- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**: 按类型大小降序排列成员变量,减少内存对齐填充
|
||||
- **预期收益**: 减少约 16-32 字节内存占用
|
||||
|
||||
#### 1.2 子节点查找优化(哈希索引)
|
||||
- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 添加 `std::unordered_map<std::string, WeakPtr<Node>> nameIndex_`
|
||||
- 添加 `std::unordered_map<int, WeakPtr<Node>> tagIndex_`
|
||||
- 修改 `addChild`/`removeChild` 维护索引
|
||||
- **预期收益**: `getChildByName`/`getChildByTag` 从 O(n) 优化到 O(1)
|
||||
|
||||
### 阶段二:Action 系统优化
|
||||
|
||||
#### 2.1 Action 存储优化
|
||||
- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 使用 `std::unordered_map<int, Ptr<Action>>` 替代 `std::vector` 存储带 tag 的 Action
|
||||
- 使用侵入式链表管理 Action 更新顺序
|
||||
- **预期收益**: Action 查找和删除从 O(n) 优化到 O(1)
|
||||
|
||||
### 阶段三:变换计算优化
|
||||
|
||||
#### 3.1 世界变换迭代计算
|
||||
- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 将 `getWorldTransform()` 的递归改为迭代实现
|
||||
- 使用栈结构收集父节点链
|
||||
- **预期收益**: 避免深层级节点的栈溢出风险,减少函数调用开销
|
||||
|
||||
#### 3.2 矩阵计算优化
|
||||
- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 优化 `getLocalTransform()` 中的矩阵构造顺序
|
||||
- 延迟计算 skew 矩阵(仅在需要时)
|
||||
- **预期收益**: 减少不必要的矩阵乘法
|
||||
|
||||
### 阶段四:渲染与批量操作优化
|
||||
|
||||
#### 4.1 渲染命令收集完善
|
||||
- **目标**: [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 完善 `collectRenderCommands` 实现
|
||||
- 添加 Z 序累积和递归收集
|
||||
- **预期收益**: 支持多线程渲染命令收集
|
||||
|
||||
#### 4.2 批量操作接口
|
||||
- **目标**: [node.h](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/include/extra2d/scene/node.h) 和 [node.cpp](file:///c:/Users/soulcoco/Desktop/Easy2D/Extra2D/Extra2D/src/scene/node.cpp)
|
||||
- **内容**:
|
||||
- 添加 `addChildren(std::vector<Ptr<Node>>&& children)` 批量添加
|
||||
- 优化 `removeAllChildren` 使用批量处理
|
||||
- **预期收益**: 减少多次操作的开销
|
||||
|
||||
---
|
||||
|
||||
**预计总工作量**: 4-6 小时
|
||||
**优先级**: 高(1.1, 1.2)> 中(2.1, 3.1)> 低(3.2, 4.1, 4.2)
|
||||
|
|
@ -124,9 +124,13 @@ private:
|
|||
auto &resources = Application::instance().resources();
|
||||
|
||||
// 使用后备字体加载功能
|
||||
#ifdef PLATFORM_SWITCH
|
||||
std::vector<std::string> fontPaths = {
|
||||
"romfs:/assets/font.ttf" // 备选字体
|
||||
};
|
||||
#else
|
||||
std::vector<std::string> fontPaths = {FileSystem::resolvePath("font.ttf")};
|
||||
#endif
|
||||
|
||||
titleFont_ = resources.loadFontWithFallbacks(fontPaths, 60, true);
|
||||
infoFont_ = resources.loadFontWithFallbacks(fontPaths, 28, true);
|
||||
|
|
@ -240,7 +244,12 @@ private:
|
|||
// 程序入口
|
||||
// ============================================================================
|
||||
|
||||
extern "C" int main(int argc, char *argv[]) {
|
||||
#ifdef _WIN32
|
||||
int main(int argc, char *argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char *argv[])
|
||||
#endif
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#include <extra2d/extra2d.h>
|
||||
#include <extra2d/platform/platform_compat.h>
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
|
|
@ -13,9 +17,9 @@ using namespace extra2d;
|
|||
*/
|
||||
static std::vector<std::string> getFontCandidates() {
|
||||
return {
|
||||
"romfs:/assets/font.ttf", // 微软雅黑(中文支持)
|
||||
"romfs:/assets/Gasinamu.ttf", // 备选字体
|
||||
"romfs:/assets/default.ttf", // 默认字体
|
||||
FileSystem::resolvePath("font.ttf"), // 微软雅黑(中文支持)
|
||||
FileSystem::resolvePath("Gasinamu.ttf"), // 备选字体
|
||||
FileSystem::resolvePath("default.ttf"), // 默认字体
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -78,12 +82,23 @@ public:
|
|||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
// 检查退出按键(START 按钮)
|
||||
// 检查退出按键
|
||||
auto &input = Application::instance().input();
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch: 使用手柄 START 按钮
|
||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
||||
E2D_LOG_INFO("退出应用");
|
||||
E2D_LOG_INFO("退出应用 (START 按钮)");
|
||||
Application::instance().quit();
|
||||
}
|
||||
#else
|
||||
// PC: 支持 ESC 键或手柄 START 按钮
|
||||
if (input.isKeyPressed(Key::Escape) ||
|
||||
input.isButtonPressed(SDL_CONTROLLER_BUTTON_START)) {
|
||||
E2D_LOG_INFO("退出应用 (ESC 键或 START 按钮)");
|
||||
Application::instance().quit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -107,8 +122,13 @@ public:
|
|||
|
||||
// 绘制提示文字(黄色)
|
||||
Color yellow(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
#ifdef PLATFORM_SWITCH
|
||||
renderer.drawText(*font_, "退出按键(START 按钮)",
|
||||
Vec2(centerX - 80.0f, centerY + 50.0f), yellow);
|
||||
#else
|
||||
renderer.drawText(*font_, "退出按键(ESC 或 START 按钮)",
|
||||
Vec2(centerX - 80.0f, centerY + 50.0f), yellow);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -130,13 +150,25 @@ static AppConfig createAppConfig() {
|
|||
config.height = 720;
|
||||
config.vsync = true;
|
||||
config.fpsLimit = 60;
|
||||
|
||||
#ifdef PLATFORM_PC
|
||||
// PC 端默认窗口模式
|
||||
config.fullscreen = false;
|
||||
config.resizable = true;
|
||||
#endif
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 程序入口
|
||||
*/
|
||||
extern "C" int main(int argc, char *argv[]) {
|
||||
#ifdef _WIN32
|
||||
int main(int argc, char *argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char *argv[])
|
||||
#endif
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
|
|
@ -146,6 +178,7 @@ extern "C" int main(int argc, char *argv[]) {
|
|||
|
||||
E2D_LOG_INFO("========================");
|
||||
E2D_LOG_INFO("Easy2D Hello World Demo");
|
||||
E2D_LOG_INFO("Platform: {}", platform::getPlatformName());
|
||||
E2D_LOG_INFO("========================");
|
||||
|
||||
// 获取应用实例
|
||||
|
|
|
|||
|
|
@ -185,11 +185,19 @@ private:
|
|||
void loadFonts() {
|
||||
auto &resources = Application::instance().resources();
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
std::vector<std::string> fontPaths = {
|
||||
"romfs:/assets/msjh.ttf",
|
||||
"romfs:/assets/default.ttf",
|
||||
"romfs:/assets/font.ttf",
|
||||
};
|
||||
#else
|
||||
std::vector<std::string> fontPaths = {
|
||||
FileSystem::resolvePath("msjh.ttf"),
|
||||
FileSystem::resolvePath("default.ttf"),
|
||||
FileSystem::resolvePath("font.ttf"),
|
||||
};
|
||||
#endif
|
||||
|
||||
titleFont_ = resources.loadFontWithFallbacks(fontPaths, 28, true);
|
||||
infoFont_ = resources.loadFontWithFallbacks(fontPaths, 16, true);
|
||||
|
|
@ -401,7 +409,12 @@ private:
|
|||
// 程序入口
|
||||
// ============================================================================
|
||||
|
||||
extern "C" int main(int argc, char *argv[]) {
|
||||
#ifdef _WIN32
|
||||
int main(int argc, char *argv[])
|
||||
#else
|
||||
extern "C" int main(int argc, char *argv[])
|
||||
#endif
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,134 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
// SDL2 键码定义
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 键盘按键码 (基于 GLFW)
|
||||
// 键盘按键码 (基于 SDL2)
|
||||
// ============================================================================
|
||||
namespace Key {
|
||||
enum : int {
|
||||
Unknown = -1,
|
||||
Space = 32,
|
||||
Apostrophe = 39,
|
||||
Comma = 44,
|
||||
Minus = 45,
|
||||
Period = 46,
|
||||
Slash = 47,
|
||||
Num0 = 48,
|
||||
Num1 = 49,
|
||||
Num2 = 50,
|
||||
Num3 = 51,
|
||||
Num4 = 52,
|
||||
Num5 = 53,
|
||||
Num6 = 54,
|
||||
Num7 = 55,
|
||||
Num8 = 56,
|
||||
Num9 = 57,
|
||||
Semicolon = 59,
|
||||
Equal = 61,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LeftBracket = 91,
|
||||
Backslash = 92,
|
||||
RightBracket = 93,
|
||||
GraveAccent = 96,
|
||||
World1 = 161,
|
||||
World2 = 162,
|
||||
Escape = 256,
|
||||
Enter = 257,
|
||||
Tab = 258,
|
||||
Backspace = 259,
|
||||
Insert = 260,
|
||||
Delete = 261,
|
||||
Right = 262,
|
||||
Left = 263,
|
||||
Down = 264,
|
||||
Up = 265,
|
||||
PageUp = 266,
|
||||
PageDown = 267,
|
||||
Home = 268,
|
||||
End = 269,
|
||||
CapsLock = 280,
|
||||
ScrollLock = 281,
|
||||
NumLock = 282,
|
||||
PrintScreen = 283,
|
||||
Pause = 284,
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
KP0 = 320,
|
||||
KP1 = 321,
|
||||
KP2 = 322,
|
||||
KP3 = 323,
|
||||
KP4 = 324,
|
||||
KP5 = 325,
|
||||
KP6 = 326,
|
||||
KP7 = 327,
|
||||
KP8 = 328,
|
||||
KP9 = 329,
|
||||
KPDecimal = 330,
|
||||
KPDivide = 331,
|
||||
KPMultiply = 332,
|
||||
KPSubtract = 333,
|
||||
KPAdd = 334,
|
||||
KPEnter = 335,
|
||||
KPEqual = 336,
|
||||
LeftShift = 340,
|
||||
LeftControl = 341,
|
||||
LeftAlt = 342,
|
||||
LeftSuper = 343,
|
||||
RightShift = 344,
|
||||
RightControl = 345,
|
||||
RightAlt = 346,
|
||||
RightSuper = 347,
|
||||
Menu = 348,
|
||||
Last = Menu
|
||||
Unknown = SDLK_UNKNOWN,
|
||||
Space = SDLK_SPACE,
|
||||
Apostrophe = SDLK_QUOTE,
|
||||
Comma = SDLK_COMMA,
|
||||
Minus = SDLK_MINUS,
|
||||
Period = SDLK_PERIOD,
|
||||
Slash = SDLK_SLASH,
|
||||
Num0 = SDLK_0,
|
||||
Num1 = SDLK_1,
|
||||
Num2 = SDLK_2,
|
||||
Num3 = SDLK_3,
|
||||
Num4 = SDLK_4,
|
||||
Num5 = SDLK_5,
|
||||
Num6 = SDLK_6,
|
||||
Num7 = SDLK_7,
|
||||
Num8 = SDLK_8,
|
||||
Num9 = SDLK_9,
|
||||
Semicolon = SDLK_SEMICOLON,
|
||||
Equal = SDLK_EQUALS,
|
||||
A = SDLK_a,
|
||||
B = SDLK_b,
|
||||
C = SDLK_c,
|
||||
D = SDLK_d,
|
||||
E = SDLK_e,
|
||||
F = SDLK_f,
|
||||
G = SDLK_g,
|
||||
H = SDLK_h,
|
||||
I = SDLK_i,
|
||||
J = SDLK_j,
|
||||
K = SDLK_k,
|
||||
L = SDLK_l,
|
||||
M = SDLK_m,
|
||||
N = SDLK_n,
|
||||
O = SDLK_o,
|
||||
P = SDLK_p,
|
||||
Q = SDLK_q,
|
||||
R = SDLK_r,
|
||||
S = SDLK_s,
|
||||
T = SDLK_t,
|
||||
U = SDLK_u,
|
||||
V = SDLK_v,
|
||||
W = SDLK_w,
|
||||
X = SDLK_x,
|
||||
Y = SDLK_y,
|
||||
Z = SDLK_z,
|
||||
LeftBracket = SDLK_LEFTBRACKET,
|
||||
Backslash = SDLK_BACKSLASH,
|
||||
RightBracket = SDLK_RIGHTBRACKET,
|
||||
GraveAccent = SDLK_BACKQUOTE,
|
||||
Escape = SDLK_ESCAPE,
|
||||
Enter = SDLK_RETURN,
|
||||
Tab = SDLK_TAB,
|
||||
Backspace = SDLK_BACKSPACE,
|
||||
Insert = SDLK_INSERT,
|
||||
Delete = SDLK_DELETE,
|
||||
Right = SDLK_RIGHT,
|
||||
Left = SDLK_LEFT,
|
||||
Down = SDLK_DOWN,
|
||||
Up = SDLK_UP,
|
||||
PageUp = SDLK_PAGEUP,
|
||||
PageDown = SDLK_PAGEDOWN,
|
||||
Home = SDLK_HOME,
|
||||
End = SDLK_END,
|
||||
CapsLock = SDLK_CAPSLOCK,
|
||||
ScrollLock = SDLK_SCROLLLOCK,
|
||||
NumLock = SDLK_NUMLOCKCLEAR,
|
||||
PrintScreen = SDLK_PRINTSCREEN,
|
||||
Pause = SDLK_PAUSE,
|
||||
F1 = SDLK_F1,
|
||||
F2 = SDLK_F2,
|
||||
F3 = SDLK_F3,
|
||||
F4 = SDLK_F4,
|
||||
F5 = SDLK_F5,
|
||||
F6 = SDLK_F6,
|
||||
F7 = SDLK_F7,
|
||||
F8 = SDLK_F8,
|
||||
F9 = SDLK_F9,
|
||||
F10 = SDLK_F10,
|
||||
F11 = SDLK_F11,
|
||||
F12 = SDLK_F12,
|
||||
F13 = SDLK_F13,
|
||||
F14 = SDLK_F14,
|
||||
F15 = SDLK_F15,
|
||||
F16 = SDLK_F16,
|
||||
F17 = SDLK_F17,
|
||||
F18 = SDLK_F18,
|
||||
F19 = SDLK_F19,
|
||||
F20 = SDLK_F20,
|
||||
F21 = SDLK_F21,
|
||||
F22 = SDLK_F22,
|
||||
F23 = SDLK_F23,
|
||||
F24 = SDLK_F24,
|
||||
KP0 = SDLK_KP_0,
|
||||
KP1 = SDLK_KP_1,
|
||||
KP2 = SDLK_KP_2,
|
||||
KP3 = SDLK_KP_3,
|
||||
KP4 = SDLK_KP_4,
|
||||
KP5 = SDLK_KP_5,
|
||||
KP6 = SDLK_KP_6,
|
||||
KP7 = SDLK_KP_7,
|
||||
KP8 = SDLK_KP_8,
|
||||
KP9 = SDLK_KP_9,
|
||||
KPDecimal = SDLK_KP_PERIOD,
|
||||
KPDivide = SDLK_KP_DIVIDE,
|
||||
KPMultiply = SDLK_KP_MULTIPLY,
|
||||
KPSubtract = SDLK_KP_MINUS,
|
||||
KPAdd = SDLK_KP_PLUS,
|
||||
KPEnter = SDLK_KP_ENTER,
|
||||
KPEqual = SDLK_KP_EQUALS,
|
||||
LeftShift = SDLK_LSHIFT,
|
||||
LeftControl = SDLK_LCTRL,
|
||||
LeftAlt = SDLK_LALT,
|
||||
LeftSuper = SDLK_LGUI,
|
||||
RightShift = SDLK_RSHIFT,
|
||||
RightControl = SDLK_RCTRL,
|
||||
RightAlt = SDLK_RALT,
|
||||
RightSuper = SDLK_RGUI,
|
||||
Menu = SDLK_MENU,
|
||||
Last = SDLK_MENU
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -137,12 +137,12 @@ enum : int {
|
|||
// ============================================================================
|
||||
namespace Mod {
|
||||
enum : int {
|
||||
Shift = 0x0001,
|
||||
Control = 0x0002,
|
||||
Alt = 0x0004,
|
||||
Super = 0x0008,
|
||||
CapsLock = 0x0010,
|
||||
NumLock = 0x0020
|
||||
Shift = KMOD_SHIFT,
|
||||
Control = KMOD_CTRL,
|
||||
Alt = KMOD_ALT,
|
||||
Super = KMOD_GUI,
|
||||
CapsLock = KMOD_CAPS,
|
||||
NumLock = KMOD_NUM
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -171,22 +171,22 @@ enum : int {
|
|||
// ============================================================================
|
||||
namespace GamepadButton {
|
||||
enum : int {
|
||||
A = 0,
|
||||
B = 1,
|
||||
X = 2,
|
||||
Y = 3,
|
||||
LeftBumper = 4,
|
||||
RightBumper = 5,
|
||||
Back = 6,
|
||||
Start = 7,
|
||||
Guide = 8,
|
||||
LeftThumb = 9,
|
||||
RightThumb = 10,
|
||||
DPadUp = 11,
|
||||
DPadRight = 12,
|
||||
DPadDown = 13,
|
||||
DPadLeft = 14,
|
||||
Last = DPadLeft,
|
||||
A = SDL_CONTROLLER_BUTTON_A,
|
||||
B = SDL_CONTROLLER_BUTTON_B,
|
||||
X = SDL_CONTROLLER_BUTTON_X,
|
||||
Y = SDL_CONTROLLER_BUTTON_Y,
|
||||
LeftBumper = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||
RightBumper = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||
Back = SDL_CONTROLLER_BUTTON_BACK,
|
||||
Start = SDL_CONTROLLER_BUTTON_START,
|
||||
Guide = SDL_CONTROLLER_BUTTON_GUIDE,
|
||||
LeftThumb = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||
RightThumb = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||
DPadUp = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||
DPadRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||
DPadDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||
DPadLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
Last = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
Cross = A,
|
||||
Circle = B,
|
||||
Square = X,
|
||||
|
|
@ -199,13 +199,13 @@ enum : int {
|
|||
// ============================================================================
|
||||
namespace GamepadAxis {
|
||||
enum : int {
|
||||
LeftX = 0,
|
||||
LeftY = 1,
|
||||
RightX = 2,
|
||||
RightY = 3,
|
||||
LeftTrigger = 4,
|
||||
RightTrigger = 5,
|
||||
Last = RightTrigger
|
||||
LeftX = SDL_CONTROLLER_AXIS_LEFTX,
|
||||
LeftY = SDL_CONTROLLER_AXIS_LEFTY,
|
||||
RightX = SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
RightY = SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
Last = SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
// Platform
|
||||
#include <extra2d/platform/window.h>
|
||||
#include <extra2d/platform/input.h>
|
||||
#include <extra2d/platform/file_system.h>
|
||||
|
||||
// Graphics
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <glad/glad.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* @file file_system.h
|
||||
* @brief 跨平台文件系统工具
|
||||
*
|
||||
* 提供统一的文件路径处理和文件操作接口
|
||||
* 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||
*/
|
||||
|
||||
#include <extra2d/platform/platform_compat.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 文件系统工具类
|
||||
// ============================================================================
|
||||
class FileSystem {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
* @return 资源根目录路径
|
||||
*
|
||||
* Switch: "romfs:/"
|
||||
* PC: 可执行文件所在目录 + "/assets/" 或当前工作目录
|
||||
*/
|
||||
static std::string getResourceRoot();
|
||||
|
||||
/**
|
||||
* @brief 解析资源路径
|
||||
* @param relativePath 相对路径 (例如 "assets/font.ttf")
|
||||
* @return 完整路径
|
||||
*
|
||||
* Switch: "romfs:/assets/font.ttf"
|
||||
* PC: "./assets/font.ttf" 或 exe目录/assets/font.ttf
|
||||
*/
|
||||
static std::string resolvePath(const std::string& relativePath);
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
* @param path 文件路径
|
||||
* @return true 如果文件存在
|
||||
*/
|
||||
static bool fileExists(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 检查目录是否存在
|
||||
* @param path 目录路径
|
||||
* @return true 如果目录存在
|
||||
*/
|
||||
static bool directoryExists(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 获取可执行文件所在目录
|
||||
* @return 可执行文件目录路径
|
||||
*/
|
||||
static std::string getExecutableDirectory();
|
||||
|
||||
/**
|
||||
* @brief 获取当前工作目录
|
||||
* @return 当前工作目录路径
|
||||
*/
|
||||
static std::string getCurrentWorkingDirectory();
|
||||
|
||||
/**
|
||||
* @brief 组合路径
|
||||
* @param base 基础路径
|
||||
* @param relative 相对路径
|
||||
* @return 组合后的路径
|
||||
*/
|
||||
static std::string combinePath(const std::string& base, const std::string& relative);
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(不含路径)
|
||||
* @param path 完整路径
|
||||
* @return 文件名
|
||||
*/
|
||||
static std::string getFileName(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
* @param path 文件路径
|
||||
* @return 扩展名(包含点,例如 ".ttf")
|
||||
*/
|
||||
static std::string getFileExtension(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 获取目录名
|
||||
* @param path 文件路径
|
||||
* @return 目录路径
|
||||
*/
|
||||
static std::string getDirectoryName(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 规范化路径(统一分隔符,去除冗余)
|
||||
* @param path 原始路径
|
||||
* @return 规范化后的路径
|
||||
*/
|
||||
static std::string normalizePath(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容为字符串
|
||||
* @param path 文件路径
|
||||
* @return 文件内容,失败返回空字符串
|
||||
*/
|
||||
static std::string readFileText(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容为字节数组
|
||||
* @param path 文件路径
|
||||
* @return 文件内容,失败返回空vector
|
||||
*/
|
||||
static std::vector<uint8_t> readFileBytes(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 获取文件大小
|
||||
* @param path 文件路径
|
||||
* @return 文件大小(字节),失败返回 -1
|
||||
*/
|
||||
static int64_t getFileSize(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 创建目录
|
||||
* @param path 目录路径
|
||||
* @return true 如果成功
|
||||
*/
|
||||
static bool createDirectory(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 创建多级目录
|
||||
* @param path 目录路径
|
||||
* @return true 如果成功
|
||||
*/
|
||||
static bool createDirectories(const std::string& path);
|
||||
|
||||
private:
|
||||
// 禁止实例化
|
||||
FileSystem() = delete;
|
||||
~FileSystem() = delete;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 便捷函数
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 解析资源路径的便捷函数
|
||||
* @param path 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
inline std::string resolvePath(const std::string& path) {
|
||||
return FileSystem::resolvePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在的便捷函数
|
||||
* @param path 文件路径
|
||||
* @return true 如果文件存在
|
||||
*/
|
||||
inline bool fileExists(const std::string& path) {
|
||||
return FileSystem::fileExists(path);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,13 +3,15 @@
|
|||
#include <array>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/platform/platform_compat.h>
|
||||
#include <extra2d/event/input_codes.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 鼠标按钮枚举 (保留接口兼容性)
|
||||
// 鼠标按钮枚举
|
||||
// ============================================================================
|
||||
enum class MouseButton {
|
||||
Left = 0,
|
||||
|
|
@ -24,14 +26,15 @@ enum class MouseButton {
|
|||
};
|
||||
|
||||
// ============================================================================
|
||||
// Input 类 - SDL2 GameController + Touch 输入管理
|
||||
// Input 类 - 跨平台输入管理
|
||||
// 支持: 键盘、鼠标、手柄、触摸屏
|
||||
// ============================================================================
|
||||
class Input {
|
||||
public:
|
||||
Input();
|
||||
~Input();
|
||||
|
||||
// 初始化 (使用 SDL2 GameController API)
|
||||
// 初始化
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
|
|
@ -39,14 +42,14 @@ public:
|
|||
void update();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 键盘输入 (映射到手柄按钮)
|
||||
// 键盘输入
|
||||
// ------------------------------------------------------------------------
|
||||
bool isKeyDown(int keyCode) const;
|
||||
bool isKeyPressed(int keyCode) const;
|
||||
bool isKeyReleased(int keyCode) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 手柄按钮 (通过 SDL_GameController)
|
||||
// 手柄按钮
|
||||
// ------------------------------------------------------------------------
|
||||
bool isButtonDown(int button) const;
|
||||
bool isButtonPressed(int button) const;
|
||||
|
|
@ -57,7 +60,7 @@ public:
|
|||
Vec2 getRightStick() const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 鼠标输入 (映射到触摸屏)
|
||||
// 鼠标输入
|
||||
// ------------------------------------------------------------------------
|
||||
bool isMouseDown(MouseButton button) const;
|
||||
bool isMousePressed(MouseButton button) const;
|
||||
|
|
@ -65,15 +68,15 @@ public:
|
|||
|
||||
Vec2 getMousePosition() const;
|
||||
Vec2 getMouseDelta() const;
|
||||
float getMouseScroll() const { return 0.0f; }
|
||||
float getMouseScrollDelta() const { return 0.0f; }
|
||||
float getMouseScroll() const { return mouseScroll_; }
|
||||
float getMouseScrollDelta() const { return mouseScroll_ - prevMouseScroll_; }
|
||||
|
||||
void setMousePosition(const Vec2 &position);
|
||||
void setMouseVisible(bool visible);
|
||||
void setMouseLocked(bool locked);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 触摸屏
|
||||
// 触摸屏 (Switch 原生支持,PC 端模拟或禁用)
|
||||
// ------------------------------------------------------------------------
|
||||
bool isTouching() const { return touching_; }
|
||||
Vec2 getTouchPosition() const { return touchPosition_; }
|
||||
|
|
@ -87,9 +90,14 @@ public:
|
|||
|
||||
private:
|
||||
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
|
||||
static constexpr int MAX_KEYS = SDL_NUM_SCANCODES;
|
||||
|
||||
SDL_GameController *controller_;
|
||||
|
||||
// 键盘状态 (PC 端使用)
|
||||
std::array<bool, MAX_KEYS> keysDown_;
|
||||
std::array<bool, MAX_KEYS> prevKeysDown_;
|
||||
|
||||
// 手柄按钮状态
|
||||
std::array<bool, MAX_BUTTONS> buttonsDown_;
|
||||
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
|
||||
|
|
@ -100,15 +108,35 @@ private:
|
|||
float rightStickX_;
|
||||
float rightStickY_;
|
||||
|
||||
// 触摸屏状态
|
||||
// 鼠标状态 (PC 端使用)
|
||||
Vec2 mousePosition_;
|
||||
Vec2 prevMousePosition_;
|
||||
float mouseScroll_;
|
||||
float prevMouseScroll_;
|
||||
std::array<bool, 8> mouseButtonsDown_;
|
||||
std::array<bool, 8> prevMouseButtonsDown_;
|
||||
|
||||
// 触摸屏状态 (Switch 原生)
|
||||
bool touching_;
|
||||
bool prevTouching_;
|
||||
Vec2 touchPosition_;
|
||||
Vec2 prevTouchPosition_;
|
||||
int touchCount_;
|
||||
|
||||
// 映射键盘 keyCode 到 SDL GameController 按钮
|
||||
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
|
||||
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
||||
|
||||
// 更新键盘状态
|
||||
void updateKeyboard();
|
||||
|
||||
// 更新鼠标状态
|
||||
void updateMouse();
|
||||
|
||||
// 更新手柄状态
|
||||
void updateGamepad();
|
||||
|
||||
// 更新触摸屏状态
|
||||
void updateTouch();
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
#pragma once
|
||||
|
||||
/**
|
||||
* @file platform_compat.h
|
||||
* @brief 跨平台兼容性头文件
|
||||
*
|
||||
* 提供所有支持平台的兼容性定义和宏
|
||||
* 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// 平台检测
|
||||
// ============================================================================
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// Nintendo Switch 平台
|
||||
#define PLATFORM_SWITCH 1
|
||||
#define PLATFORM_NAME "Nintendo Switch"
|
||||
|
||||
#elif defined(_WIN32)
|
||||
// Windows 平台
|
||||
#define PLATFORM_PC 1
|
||||
#define PLATFORM_WINDOWS 1
|
||||
#define PLATFORM_NAME "Windows"
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
// 禁用 SDL 的 main 重定义,使用标准 main 函数
|
||||
#define SDL_MAIN_HANDLED
|
||||
|
||||
#elif defined(__linux__)
|
||||
// Linux 平台
|
||||
#define PLATFORM_PC 1
|
||||
#define PLATFORM_LINUX 1
|
||||
#define PLATFORM_NAME "Linux"
|
||||
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
// macOS 平台
|
||||
#define PLATFORM_PC 1
|
||||
#define PLATFORM_MACOS 1
|
||||
#define PLATFORM_NAME "macOS"
|
||||
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Nintendo Switch 平台包含
|
||||
// ============================================================================
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
|
||||
#include <switch.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
|
||||
// RomFS路径前缀
|
||||
#define ROMFS_PREFIX "romfs:/"
|
||||
|
||||
// Switch 特定的编译器属性
|
||||
#define E2D_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||
#define E2D_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// Switch 调试输出
|
||||
#ifdef E2D_DEBUG
|
||||
#define E2D_PLATFORM_LOG(fmt, ...) printf("[Extra2D] " fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define E2D_PLATFORM_LOG(fmt, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif // PLATFORM_SWITCH
|
||||
|
||||
// ============================================================================
|
||||
// PC 平台包含 (Windows/Linux/macOS)
|
||||
// ============================================================================
|
||||
|
||||
#ifdef PLATFORM_PC
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
// PC 平台使用标准文件路径
|
||||
#define ROMFS_PREFIX ""
|
||||
|
||||
// PC 平台编译器属性
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define E2D_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||
#define E2D_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
#define E2D_LIKELY(x) (x)
|
||||
#define E2D_UNLIKELY(x) (x)
|
||||
#endif
|
||||
|
||||
// PC 调试输出
|
||||
#ifdef E2D_DEBUG
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
#define E2D_PLATFORM_LOG(fmt, ...) OutputDebugStringA((std::string("[Extra2D] ") + fmt + "\n").c_str())
|
||||
#else
|
||||
#define E2D_PLATFORM_LOG(fmt, ...) printf("[Extra2D] " fmt "\n", ##__VA_ARGS__)
|
||||
#endif
|
||||
#else
|
||||
#define E2D_PLATFORM_LOG(fmt, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
#endif // PLATFORM_PC
|
||||
|
||||
// ============================================================================
|
||||
// 跨平台通用定义
|
||||
// ============================================================================
|
||||
|
||||
namespace extra2d {
|
||||
namespace platform {
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台名称
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
inline const char* getPlatformName() {
|
||||
return PLATFORM_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前是否为 Switch 平台
|
||||
* @return true 如果是 Switch 平台
|
||||
*/
|
||||
inline bool isSwitch() {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前是否为 PC 平台
|
||||
* @return true 如果是 PC 平台 (Windows/Linux/macOS)
|
||||
*/
|
||||
inline bool isPC() {
|
||||
#ifdef PLATFORM_PC
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前是否为 Windows 平台
|
||||
* @return true 如果是 Windows 平台
|
||||
*/
|
||||
inline bool isWindows() {
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前是否为 Linux 平台
|
||||
* @return true 如果是 Linux 平台
|
||||
*/
|
||||
inline bool isLinux() {
|
||||
#ifdef PLATFORM_LINUX
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前是否为 macOS 平台
|
||||
* @return true 如果是 macOS 平台
|
||||
*/
|
||||
inline bool isMacOS() {
|
||||
#ifdef PLATFORM_MACOS
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
|
||||
// ============================================================================
|
||||
// 文件系统路径工具
|
||||
// ============================================================================
|
||||
|
||||
namespace romfs {
|
||||
|
||||
/**
|
||||
* @brief RomFS 根路径常量
|
||||
*/
|
||||
#ifdef PLATFORM_SWITCH
|
||||
static constexpr const char* ROOT = "romfs:/";
|
||||
#else
|
||||
static constexpr const char* ROOT = "";
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
* @param path 文件路径
|
||||
* @return true 如果文件存在
|
||||
*/
|
||||
inline bool fileExists(const char* path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为 romfs 路径 (Switch) 或普通路径 (PC)
|
||||
* @param path 文件路径
|
||||
* @return true 如果是 romfs 格式路径
|
||||
*/
|
||||
inline bool isRomfsPath(const char* path) {
|
||||
return path && (strncmp(path, "romfs:/", 7) == 0 || strncmp(path, "romfs:\\", 7) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 构建完整路径
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径 (Switch 添加 romfs:/ 前缀,PC 保持原样)
|
||||
*/
|
||||
inline std::string makePath(const char* relativePath) {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
std::string result = ROOT;
|
||||
result += relativePath;
|
||||
return result;
|
||||
#else
|
||||
return std::string(relativePath);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace romfs
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// ============================================================================
|
||||
// 向后兼容:保留 switch_compat.h 的宏定义
|
||||
// ============================================================================
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
#define IS_SWITCH_PLATFORM 1
|
||||
#define SWITCH_ROMFS_PREFIX "romfs:/"
|
||||
#define SWITCH_LIKELY(x) E2D_LIKELY(x)
|
||||
#define SWITCH_UNLIKELY(x) E2D_UNLIKELY(x)
|
||||
#ifdef E2D_DEBUG
|
||||
#define SWITCH_DEBUG_PRINTF(fmt, ...) E2D_PLATFORM_LOG(fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define SWITCH_DEBUG_PRINTF(fmt, ...) ((void)0)
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -2,78 +2,16 @@
|
|||
|
||||
/**
|
||||
* @file switch_compat.h
|
||||
* @brief Nintendo Switch 兼容性头文件
|
||||
* @brief Nintendo Switch 兼容性头文件 (已弃用)
|
||||
*
|
||||
* 提供 Switch 平台特定的兼容性定义和宏
|
||||
* @deprecated 请使用 platform_compat.h 替代
|
||||
* 此文件保留用于向后兼容
|
||||
*/
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// 包含新的跨平台兼容性头文件
|
||||
#include "platform_compat.h"
|
||||
|
||||
// ============================================================================
|
||||
// Switch 平台包含
|
||||
// ============================================================================
|
||||
#include <switch.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <string>
|
||||
|
||||
// ============================================================================
|
||||
// Switch 特定的内存操作
|
||||
// ============================================================================
|
||||
// 不需要特殊处理,libnx已提供malloc/free
|
||||
|
||||
// ============================================================================
|
||||
// Switch 文件系统相关
|
||||
// ============================================================================
|
||||
// RomFS路径前缀
|
||||
#define SWITCH_ROMFS_PREFIX "romfs:/"
|
||||
|
||||
// RomFS 根路径常量
|
||||
namespace extra2d {
|
||||
namespace romfs {
|
||||
static constexpr const char* ROMFS_ROOT = "romfs:/";
|
||||
|
||||
// 检查文件是否存在于 romfs 中
|
||||
inline bool fileExists(const char* path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
// 检查路径是否为 romfs 路径
|
||||
inline bool isRomfsPath(const char* path) {
|
||||
return path && (strncmp(path, "romfs:/", 7) == 0 || strncmp(path, "romfs:\\", 7) == 0);
|
||||
}
|
||||
|
||||
// 构建 romfs 完整路径
|
||||
inline std::string makePath(const char* relativePath) {
|
||||
std::string result = ROMFS_ROOT;
|
||||
result += relativePath;
|
||||
return result;
|
||||
}
|
||||
} // namespace romfs
|
||||
} // namespace extra2d
|
||||
|
||||
// ============================================================================
|
||||
// Switch 调试输出
|
||||
// ============================================================================
|
||||
#ifdef E2D_DEBUG
|
||||
#define SWITCH_DEBUG_PRINTF(fmt, ...) printf("[Extra2D] " fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define SWITCH_DEBUG_PRINTF(fmt, ...) ((void)0)
|
||||
// 发出弃用警告(仅在非 Switch 平台或调试模式下)
|
||||
#if !defined(__SWITCH__) && defined(E2D_DEBUG)
|
||||
#warning "switch_compat.h is deprecated, use platform_compat.h instead"
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// Switch 特定的编译器属性
|
||||
// ============================================================================
|
||||
#define SWITCH_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||
#define SWITCH_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// ============================================================================
|
||||
// Switch 特定的平台检查宏
|
||||
// ============================================================================
|
||||
#define IS_SWITCH_PLATFORM 1
|
||||
|
||||
#endif // __SWITCH__
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/platform/platform_compat.h>
|
||||
#include <functional>
|
||||
|
||||
#include <SDL.h>
|
||||
|
|
@ -20,15 +21,15 @@ struct WindowConfig {
|
|||
String title = "Extra2D Application";
|
||||
int width = 1280;
|
||||
int height = 720;
|
||||
bool fullscreen = true; // Switch 始终全屏
|
||||
bool resizable = false;
|
||||
bool fullscreen = true; // Switch 始终全屏,PC 可配置
|
||||
bool resizable = false; // PC 端可调整大小
|
||||
bool vsync = true;
|
||||
int msaaSamples = 0;
|
||||
bool centerWindow = false;
|
||||
bool centerWindow = true; // PC 端窗口居中
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 鼠标光标形状枚举 (保留接口兼容性,Switch 上无效)
|
||||
// 鼠标光标形状枚举
|
||||
// ============================================================================
|
||||
enum class CursorShape {
|
||||
Arrow,
|
||||
|
|
@ -44,6 +45,7 @@ enum class CursorShape {
|
|||
|
||||
// ============================================================================
|
||||
// Window 类 - SDL2 Window + GLES 3.2 封装
|
||||
// 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||
// ============================================================================
|
||||
class Window {
|
||||
public:
|
||||
|
|
@ -60,7 +62,7 @@ public:
|
|||
bool shouldClose() const;
|
||||
void setShouldClose(bool close);
|
||||
|
||||
// 窗口属性 (Switch 上大部分为空操作)
|
||||
// 窗口属性
|
||||
void setTitle(const String& title);
|
||||
void setSize(int width, int height);
|
||||
void setPosition(int x, int y);
|
||||
|
|
@ -72,19 +74,19 @@ public:
|
|||
int getWidth() const { return width_; }
|
||||
int getHeight() const { return height_; }
|
||||
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||
Vec2 getPosition() const { return Vec2::Zero(); }
|
||||
bool isFullscreen() const { return true; }
|
||||
Vec2 getPosition() const;
|
||||
bool isFullscreen() const { return fullscreen_; }
|
||||
bool isVSync() const { return vsync_; }
|
||||
|
||||
// DPI 缩放 (Switch 固定 1.0)
|
||||
float getContentScaleX() const { return 1.0f; }
|
||||
float getContentScaleY() const { return 1.0f; }
|
||||
Vec2 getContentScale() const { return Vec2(1.0f, 1.0f); }
|
||||
// DPI 缩放 (PC 端自动检测,Switch 固定 1.0)
|
||||
float getContentScaleX() const;
|
||||
float getContentScaleY() const;
|
||||
Vec2 getContentScale() const;
|
||||
|
||||
// 窗口状态
|
||||
bool isFocused() const { return true; }
|
||||
bool isMinimized() const { return false; }
|
||||
bool isMaximized() const { return true; }
|
||||
bool isFocused() const { return focused_; }
|
||||
bool isMinimized() const;
|
||||
bool isMaximized() const;
|
||||
|
||||
// 获取 SDL2 窗口和 GL 上下文
|
||||
SDL_Window* getSDLWindow() const { return sdlWindow_; }
|
||||
|
|
@ -101,9 +103,10 @@ public:
|
|||
// 获取输入管理器
|
||||
Input* getInput() const { return input_.get(); }
|
||||
|
||||
// 光标操作 (Switch 上为空操作)
|
||||
// 光标操作 (PC 端有效,Switch 上为空操作)
|
||||
void setCursor(CursorShape shape);
|
||||
void resetCursor();
|
||||
void setMouseVisible(bool visible);
|
||||
|
||||
// 窗口回调
|
||||
using ResizeCallback = std::function<void(int width, int height)>;
|
||||
|
|
@ -118,11 +121,17 @@ private:
|
|||
// SDL2 状态
|
||||
SDL_Window* sdlWindow_;
|
||||
SDL_GLContext glContext_;
|
||||
SDL_Cursor* sdlCursors_[9]; // 光标缓存
|
||||
SDL_Cursor* currentCursor_;
|
||||
|
||||
int width_;
|
||||
int height_;
|
||||
bool vsync_;
|
||||
bool shouldClose_;
|
||||
bool fullscreen_;
|
||||
bool focused_;
|
||||
float contentScaleX_;
|
||||
float contentScaleY_;
|
||||
void* userData_;
|
||||
EventQueue* eventQueue_;
|
||||
UniquePtr<Input> input_;
|
||||
|
|
@ -131,8 +140,11 @@ private:
|
|||
FocusCallback focusCallback_;
|
||||
CloseCallback closeCallback_;
|
||||
|
||||
bool initSDL();
|
||||
bool initSDL(const WindowConfig& config);
|
||||
void deinitSDL();
|
||||
void initCursors();
|
||||
void deinitCursors();
|
||||
void updateContentScale();
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -13,18 +13,29 @@
|
|||
#include <extra2d/utils/timer.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <switch.h>
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 获取当前时间(秒)
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
// PC 平台使用 chrono
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||
#endif
|
||||
}
|
||||
|
||||
Application &Application::instance() {
|
||||
|
|
@ -42,8 +53,9 @@ bool Application::init(const AppConfig &config) {
|
|||
|
||||
config_ = config;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// ========================================
|
||||
// 1. 初始化 RomFS 文件系统(必须在 SDL_Init 之前)
|
||||
// 1. 初始化 RomFS 文件系统(Switch 平台)
|
||||
// ========================================
|
||||
Result rc;
|
||||
rc = romfsInit();
|
||||
|
|
@ -54,13 +66,14 @@ bool Application::init(const AppConfig &config) {
|
|||
}
|
||||
|
||||
// ========================================
|
||||
// 2. 初始化 nxlink 调试输出(可选)
|
||||
// 2. 初始化 nxlink 调试输出(Switch 平台)
|
||||
// ========================================
|
||||
rc = socketInitializeDefault();
|
||||
if (R_FAILED(rc)) {
|
||||
E2D_LOG_WARN(
|
||||
"socketInitializeDefault failed, nxlink will not be available");
|
||||
}
|
||||
#endif
|
||||
|
||||
// ========================================
|
||||
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
|
||||
|
|
@ -70,8 +83,14 @@ bool Application::init(const AppConfig &config) {
|
|||
winConfig.title = config.title;
|
||||
winConfig.width = 1280;
|
||||
winConfig.height = 720;
|
||||
#ifdef __SWITCH__
|
||||
winConfig.fullscreen = true;
|
||||
winConfig.resizable = false;
|
||||
#else
|
||||
// PC 平台默认窗口模式
|
||||
winConfig.fullscreen = config.fullscreen;
|
||||
winConfig.resizable = true;
|
||||
#endif
|
||||
winConfig.vsync = config.vsync;
|
||||
winConfig.msaaSamples = config.msaaSamples;
|
||||
|
||||
|
|
@ -101,7 +120,7 @@ bool Application::init(const AppConfig &config) {
|
|||
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()), 0);
|
||||
|
||||
// 窗口大小回调(Switch 上不会触发,但保留接口)
|
||||
// 窗口大小回调
|
||||
window_->setResizeCallback([this](int width, int height) {
|
||||
if (camera_) {
|
||||
camera_->setViewport(0, static_cast<float>(width),
|
||||
|
|
@ -119,8 +138,13 @@ bool Application::init(const AppConfig &config) {
|
|||
// 初始化音频引擎
|
||||
AudioEngine::getInstance().initialize();
|
||||
|
||||
// 添加 romfs:/ 到资源搜索路径
|
||||
#ifdef __SWITCH__
|
||||
// 添加 romfs:/ 到资源搜索路径(Switch 平台)
|
||||
resourceManager_->addSearchPath("romfs:/");
|
||||
#endif
|
||||
// 添加默认资源路径
|
||||
resourceManager_->addSearchPath("assets/");
|
||||
resourceManager_->addSearchPath("./");
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
|
@ -161,9 +185,11 @@ void Application::shutdown() {
|
|||
window_.reset();
|
||||
}
|
||||
|
||||
// Switch 清理
|
||||
#ifdef __SWITCH__
|
||||
// Switch 平台清理
|
||||
romfsExit();
|
||||
socketExit();
|
||||
#endif
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
|
@ -179,10 +205,17 @@ void Application::run() {
|
|||
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// SDL2 on Switch 内部已处理 appletMainLoop
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
#else
|
||||
// PC 平台主循环
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::quit() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,315 @@
|
|||
#include <extra2d/platform/file_system.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <shlwapi.h>
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 平台特定辅助函数
|
||||
// ============================================================================
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
static const char PATH_SEPARATOR = '\\';
|
||||
static const char PATH_SEPARATOR_ALT = '/';
|
||||
#else
|
||||
static const char PATH_SEPARATOR = '/';
|
||||
static const char PATH_SEPARATOR_ALT = '\\';
|
||||
#endif
|
||||
|
||||
static std::string normalizeSeparators(const std::string& path) {
|
||||
std::string result = path;
|
||||
std::replace(result.begin(), result.end(), PATH_SEPARATOR_ALT, PATH_SEPARATOR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 资源根目录
|
||||
// ============================================================================
|
||||
|
||||
std::string FileSystem::getResourceRoot() {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return "romfs:/";
|
||||
#else
|
||||
// PC 端:优先使用可执行文件目录下的 assets 文件夹
|
||||
std::string exeDir = getExecutableDirectory();
|
||||
std::string assetsDir = combinePath(exeDir, "assets");
|
||||
|
||||
if (directoryExists(assetsDir)) {
|
||||
return assetsDir;
|
||||
}
|
||||
|
||||
// 备选:当前工作目录
|
||||
std::string cwd = getCurrentWorkingDirectory();
|
||||
assetsDir = combinePath(cwd, "assets");
|
||||
|
||||
if (directoryExists(assetsDir)) {
|
||||
return assetsDir;
|
||||
}
|
||||
|
||||
// 默认返回当前工作目录
|
||||
return cwd;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 路径解析
|
||||
// ============================================================================
|
||||
|
||||
std::string FileSystem::resolvePath(const std::string& relativePath) {
|
||||
std::string normalized = normalizeSeparators(relativePath);
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch: 添加 romfs:/ 前缀
|
||||
if (normalized.find("romfs:/") == 0) {
|
||||
return normalized;
|
||||
}
|
||||
return "romfs:/" + normalized;
|
||||
#else
|
||||
// PC: 如果已经是绝对路径,直接返回
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
if (normalized.size() >= 2 && normalized[1] == ':') {
|
||||
return normalized;
|
||||
}
|
||||
#else
|
||||
if (!normalized.empty() && normalized[0] == '/') {
|
||||
return normalized;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 组合资源根目录和相对路径
|
||||
return combinePath(getResourceRoot(), normalized);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 文件/目录检查
|
||||
// ============================================================================
|
||||
|
||||
bool FileSystem::fileExists(const std::string& path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
return false;
|
||||
}
|
||||
return (st.st_mode & S_IFREG) != 0;
|
||||
}
|
||||
|
||||
bool FileSystem::directoryExists(const std::string& path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
return false;
|
||||
}
|
||||
return (st.st_mode & S_IFDIR) != 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 路径操作
|
||||
// ============================================================================
|
||||
|
||||
std::string FileSystem::getExecutableDirectory() {
|
||||
std::string exePath;
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
char buffer[MAX_PATH];
|
||||
DWORD len = GetModuleFileNameA(NULL, buffer, MAX_PATH);
|
||||
if (len > 0 && len < MAX_PATH) {
|
||||
exePath = buffer;
|
||||
}
|
||||
#elif defined(PLATFORM_SWITCH)
|
||||
// Switch: 返回当前工作目录(不支持获取可执行文件路径)
|
||||
return getCurrentWorkingDirectory();
|
||||
#else
|
||||
char buffer[PATH_MAX];
|
||||
ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
|
||||
if (len != -1) {
|
||||
buffer[len] = '\0';
|
||||
exePath = buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!exePath.empty()) {
|
||||
size_t lastSep = exePath.find_last_of("/\\");
|
||||
if (lastSep != std::string::npos) {
|
||||
return exePath.substr(0, lastSep);
|
||||
}
|
||||
}
|
||||
|
||||
return getCurrentWorkingDirectory();
|
||||
}
|
||||
|
||||
std::string FileSystem::getCurrentWorkingDirectory() {
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
char buffer[MAX_PATH];
|
||||
DWORD len = GetCurrentDirectoryA(MAX_PATH, buffer);
|
||||
if (len > 0 && len < MAX_PATH) {
|
||||
return std::string(buffer);
|
||||
}
|
||||
#else
|
||||
char buffer[PATH_MAX];
|
||||
if (getcwd(buffer, sizeof(buffer)) != nullptr) {
|
||||
return std::string(buffer);
|
||||
}
|
||||
#endif
|
||||
return ".";
|
||||
}
|
||||
|
||||
std::string FileSystem::combinePath(const std::string& base, const std::string& relative) {
|
||||
if (base.empty()) {
|
||||
return normalizeSeparators(relative);
|
||||
}
|
||||
if (relative.empty()) {
|
||||
return normalizeSeparators(base);
|
||||
}
|
||||
|
||||
std::string result = normalizeSeparators(base);
|
||||
std::string rel = normalizeSeparators(relative);
|
||||
|
||||
// 移除末尾的分隔符
|
||||
while (!result.empty() && result.back() == PATH_SEPARATOR) {
|
||||
result.pop_back();
|
||||
}
|
||||
|
||||
// 移除开头的分隔符
|
||||
size_t relStart = 0;
|
||||
while (relStart < rel.size() && rel[relStart] == PATH_SEPARATOR) {
|
||||
++relStart;
|
||||
}
|
||||
|
||||
if (relStart < rel.size()) {
|
||||
result += PATH_SEPARATOR;
|
||||
result += rel.substr(relStart);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileSystem::getFileName(const std::string& path) {
|
||||
std::string normalized = normalizeSeparators(path);
|
||||
size_t lastSep = normalized.find_last_of(PATH_SEPARATOR);
|
||||
if (lastSep != std::string::npos) {
|
||||
return normalized.substr(lastSep + 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::string FileSystem::getFileExtension(const std::string& path) {
|
||||
std::string fileName = getFileName(path);
|
||||
size_t lastDot = fileName.find_last_of('.');
|
||||
if (lastDot != std::string::npos && lastDot > 0) {
|
||||
return fileName.substr(lastDot);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string FileSystem::getDirectoryName(const std::string& path) {
|
||||
std::string normalized = normalizeSeparators(path);
|
||||
size_t lastSep = normalized.find_last_of(PATH_SEPARATOR);
|
||||
if (lastSep != std::string::npos) {
|
||||
return normalized.substr(0, lastSep);
|
||||
}
|
||||
return ".";
|
||||
}
|
||||
|
||||
std::string FileSystem::normalizePath(const std::string& path) {
|
||||
return normalizeSeparators(path);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 文件读写
|
||||
// ============================================================================
|
||||
|
||||
std::string FileSystem::readFileText(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open file: {}", path);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> FileSystem::readFileBytes(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open file: {}", path);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<uint8_t> buffer(static_cast<size_t>(size));
|
||||
file.read(reinterpret_cast<char*>(buffer.data()), size);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int64_t FileSystem::getFileSize(const std::string& path) {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int64_t>(st.st_size);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 目录操作
|
||||
// ============================================================================
|
||||
|
||||
bool FileSystem::createDirectory(const std::string& path) {
|
||||
std::string normalized = normalizeSeparators(path);
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
int result = _mkdir(normalized.c_str());
|
||||
#else
|
||||
int result = mkdir(normalized.c_str(), 0755);
|
||||
#endif
|
||||
|
||||
return result == 0 || errno == EEXIST;
|
||||
}
|
||||
|
||||
bool FileSystem::createDirectories(const std::string& path) {
|
||||
std::string normalized = normalizeSeparators(path);
|
||||
|
||||
if (normalized.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 逐级创建目录
|
||||
size_t pos = 0;
|
||||
while (pos < normalized.size()) {
|
||||
pos = normalized.find(PATH_SEPARATOR, pos + 1);
|
||||
if (pos == std::string::npos) {
|
||||
pos = normalized.size();
|
||||
}
|
||||
|
||||
std::string subPath = normalized.substr(0, pos);
|
||||
if (!subPath.empty() && !directoryExists(subPath)) {
|
||||
if (!createDirectory(subPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -5,11 +5,19 @@
|
|||
namespace extra2d {
|
||||
|
||||
Input::Input()
|
||||
: controller_(nullptr), leftStickX_(0.0f), leftStickY_(0.0f),
|
||||
rightStickX_(0.0f), rightStickY_(0.0f), touching_(false),
|
||||
prevTouching_(false), touchCount_(0) {
|
||||
: controller_(nullptr),
|
||||
leftStickX_(0.0f), leftStickY_(0.0f),
|
||||
rightStickX_(0.0f), rightStickY_(0.0f),
|
||||
mouseScroll_(0.0f), prevMouseScroll_(0.0f),
|
||||
touching_(false), prevTouching_(false), touchCount_(0) {
|
||||
|
||||
// 初始化所有状态数组
|
||||
keysDown_.fill(false);
|
||||
prevKeysDown_.fill(false);
|
||||
buttonsDown_.fill(false);
|
||||
prevButtonsDown_.fill(false);
|
||||
mouseButtonsDown_.fill(false);
|
||||
prevMouseButtonsDown_.fill(false);
|
||||
}
|
||||
|
||||
Input::~Input() { shutdown(); }
|
||||
|
|
@ -28,8 +36,16 @@ void Input::init() {
|
|||
}
|
||||
|
||||
if (!controller_) {
|
||||
E2D_LOG_WARN("No game controller found, input may be limited");
|
||||
E2D_LOG_WARN("No game controller found");
|
||||
}
|
||||
|
||||
// PC 端获取初始鼠标状态
|
||||
#ifndef PLATFORM_SWITCH
|
||||
int mouseX, mouseY;
|
||||
SDL_GetMouseState(&mouseX, &mouseY);
|
||||
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||
prevMousePosition_ = mousePosition_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Input::shutdown() {
|
||||
|
|
@ -41,10 +57,49 @@ void Input::shutdown() {
|
|||
|
||||
void Input::update() {
|
||||
// 保存上一帧状态
|
||||
prevKeysDown_ = keysDown_;
|
||||
prevButtonsDown_ = buttonsDown_;
|
||||
prevMouseButtonsDown_ = mouseButtonsDown_;
|
||||
prevMousePosition_ = mousePosition_;
|
||||
prevMouseScroll_ = mouseScroll_;
|
||||
prevTouching_ = touching_;
|
||||
prevTouchPosition_ = touchPosition_;
|
||||
|
||||
// 更新各输入设备状态
|
||||
updateKeyboard();
|
||||
updateMouse();
|
||||
updateGamepad();
|
||||
updateTouch();
|
||||
}
|
||||
|
||||
void Input::updateKeyboard() {
|
||||
// 获取当前键盘状态
|
||||
const Uint8* state = SDL_GetKeyboardState(nullptr);
|
||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
||||
keysDown_[i] = state[i] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Input::updateMouse() {
|
||||
#ifndef PLATFORM_SWITCH
|
||||
// 更新鼠标位置
|
||||
int mouseX, mouseY;
|
||||
Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY);
|
||||
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||
|
||||
// 更新鼠标按钮状态
|
||||
mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
|
||||
mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
|
||||
mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
|
||||
mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0;
|
||||
mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0;
|
||||
|
||||
// 处理鼠标滚轮事件(需要在事件循环中处理,这里简化处理)
|
||||
// 实际滚轮值通过 SDL_MOUSEWHEEL 事件更新
|
||||
#endif
|
||||
}
|
||||
|
||||
void Input::updateGamepad() {
|
||||
if (controller_) {
|
||||
// 更新按钮状态
|
||||
for (int i = 0; i < MAX_BUTTONS; ++i) {
|
||||
|
|
@ -55,23 +110,22 @@ void Input::update() {
|
|||
|
||||
// 读取摇杆(归一化到 -1.0 ~ 1.0)
|
||||
leftStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
|
||||
controller_, SDL_CONTROLLER_AXIS_LEFTX)) /
|
||||
32767.0f;
|
||||
controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f;
|
||||
leftStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
|
||||
controller_, SDL_CONTROLLER_AXIS_LEFTY)) /
|
||||
32767.0f;
|
||||
controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f;
|
||||
rightStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
|
||||
controller_, SDL_CONTROLLER_AXIS_RIGHTX)) /
|
||||
32767.0f;
|
||||
controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f;
|
||||
rightStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
|
||||
controller_, SDL_CONTROLLER_AXIS_RIGHTY)) /
|
||||
32767.0f;
|
||||
controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / 32767.0f;
|
||||
} else {
|
||||
buttonsDown_.fill(false);
|
||||
leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新触摸屏(SDL2 Touch API)
|
||||
void Input::updateTouch() {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch 原生触摸屏支持
|
||||
SDL_TouchID touchId = SDL_GetTouchDevice(0);
|
||||
if (touchId != 0) {
|
||||
touchCount_ = SDL_GetNumTouchFingers(touchId);
|
||||
|
|
@ -91,10 +145,39 @@ void Input::update() {
|
|||
touchCount_ = 0;
|
||||
touching_ = false;
|
||||
}
|
||||
#else
|
||||
// PC 端:触摸屏可选支持(如果有触摸设备)
|
||||
SDL_TouchID touchId = SDL_GetTouchDevice(0);
|
||||
if (touchId != 0) {
|
||||
touchCount_ = SDL_GetNumTouchFingers(touchId);
|
||||
if (touchCount_ > 0) {
|
||||
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
|
||||
if (finger) {
|
||||
touching_ = true;
|
||||
// PC 端需要根据窗口大小转换坐标
|
||||
int windowWidth, windowHeight;
|
||||
SDL_Window* window = SDL_GL_GetCurrentWindow();
|
||||
if (window) {
|
||||
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
|
||||
touchPosition_ = Vec2(finger->x * windowWidth, finger->y * windowHeight);
|
||||
} else {
|
||||
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
|
||||
}
|
||||
} else {
|
||||
touching_ = false;
|
||||
}
|
||||
} else {
|
||||
touching_ = false;
|
||||
}
|
||||
} else {
|
||||
touchCount_ = 0;
|
||||
touching_ = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 键盘输入映射到 SDL GameController 按钮
|
||||
// 键盘输入
|
||||
// ============================================================================
|
||||
|
||||
SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
|
||||
|
|
@ -121,19 +204,19 @@ SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
|
|||
|
||||
// 常用键 → 手柄按钮
|
||||
case Key::Z:
|
||||
return SDL_CONTROLLER_BUTTON_B; // 确认
|
||||
return SDL_CONTROLLER_BUTTON_B;
|
||||
case Key::X:
|
||||
return SDL_CONTROLLER_BUTTON_A; // 取消
|
||||
return SDL_CONTROLLER_BUTTON_A;
|
||||
case Key::C:
|
||||
return SDL_CONTROLLER_BUTTON_Y;
|
||||
case Key::V:
|
||||
return SDL_CONTROLLER_BUTTON_X;
|
||||
case Key::Space:
|
||||
return SDL_CONTROLLER_BUTTON_A; // 空格 = A
|
||||
return SDL_CONTROLLER_BUTTON_A;
|
||||
case Key::Enter:
|
||||
return SDL_CONTROLLER_BUTTON_A; // 回车 = A
|
||||
return SDL_CONTROLLER_BUTTON_A;
|
||||
case Key::Escape:
|
||||
return SDL_CONTROLLER_BUTTON_START; // ESC = Start
|
||||
return SDL_CONTROLLER_BUTTON_START;
|
||||
|
||||
// 肩键
|
||||
case Key::Q:
|
||||
|
|
@ -153,28 +236,54 @@ SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
|
|||
}
|
||||
|
||||
bool Input::isKeyDown(int keyCode) const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch: 映射到手柄按钮
|
||||
SDL_GameControllerButton button = mapKeyToButton(keyCode);
|
||||
if (button == SDL_CONTROLLER_BUTTON_INVALID)
|
||||
return false;
|
||||
return buttonsDown_[button];
|
||||
#else
|
||||
// PC: 直接使用键盘扫描码
|
||||
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
|
||||
if (scancode >= 0 && scancode < MAX_KEYS) {
|
||||
return keysDown_[scancode];
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Input::isKeyPressed(int keyCode) const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
SDL_GameControllerButton button = mapKeyToButton(keyCode);
|
||||
if (button == SDL_CONTROLLER_BUTTON_INVALID)
|
||||
return false;
|
||||
return buttonsDown_[button] && !prevButtonsDown_[button];
|
||||
#else
|
||||
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
|
||||
if (scancode >= 0 && scancode < MAX_KEYS) {
|
||||
return keysDown_[scancode] && !prevKeysDown_[scancode];
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Input::isKeyReleased(int keyCode) const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
SDL_GameControllerButton button = mapKeyToButton(keyCode);
|
||||
if (button == SDL_CONTROLLER_BUTTON_INVALID)
|
||||
return false;
|
||||
return !buttonsDown_[button] && prevButtonsDown_[button];
|
||||
#else
|
||||
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
|
||||
if (scancode >= 0 && scancode < MAX_KEYS) {
|
||||
return !keysDown_[scancode] && prevKeysDown_[scancode];
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 手柄按钮直接访问
|
||||
// 手柄按钮
|
||||
// ============================================================================
|
||||
|
||||
bool Input::isButtonDown(int button) const {
|
||||
|
|
@ -200,21 +309,35 @@ Vec2 Input::getLeftStick() const { return Vec2(leftStickX_, leftStickY_); }
|
|||
Vec2 Input::getRightStick() const { return Vec2(rightStickX_, rightStickY_); }
|
||||
|
||||
// ============================================================================
|
||||
// 鼠标输入映射到触摸屏
|
||||
// 鼠标输入
|
||||
// ============================================================================
|
||||
|
||||
bool Input::isMouseDown(MouseButton button) const {
|
||||
int index = static_cast<int>(button);
|
||||
if (index < 0 || index >= 8)
|
||||
return false;
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch: 左键映射到触摸,右键映射到 A 键
|
||||
if (button == MouseButton::Left) {
|
||||
return touching_;
|
||||
}
|
||||
// A 键映射为右键
|
||||
if (button == MouseButton::Right) {
|
||||
return buttonsDown_[SDL_CONTROLLER_BUTTON_A];
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
// PC: 直接使用鼠标按钮
|
||||
return mouseButtonsDown_[index];
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Input::isMousePressed(MouseButton button) const {
|
||||
int index = static_cast<int>(button);
|
||||
if (index < 0 || index >= 8)
|
||||
return false;
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
if (button == MouseButton::Left) {
|
||||
return touching_ && !prevTouching_;
|
||||
}
|
||||
|
|
@ -223,9 +346,17 @@ bool Input::isMousePressed(MouseButton button) const {
|
|||
!prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return mouseButtonsDown_[index] && !prevMouseButtonsDown_[index];
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Input::isMouseReleased(MouseButton button) const {
|
||||
int index = static_cast<int>(button);
|
||||
if (index < 0 || index >= 8)
|
||||
return false;
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
if (button == MouseButton::Left) {
|
||||
return !touching_ && prevTouching_;
|
||||
}
|
||||
|
|
@ -234,37 +365,85 @@ bool Input::isMouseReleased(MouseButton button) const {
|
|||
prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return !mouseButtonsDown_[index] && prevMouseButtonsDown_[index];
|
||||
#endif
|
||||
}
|
||||
|
||||
Vec2 Input::getMousePosition() const { return touchPosition_; }
|
||||
Vec2 Input::getMousePosition() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return touchPosition_;
|
||||
#else
|
||||
return mousePosition_;
|
||||
#endif
|
||||
}
|
||||
|
||||
Vec2 Input::getMouseDelta() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
if (touching_ && prevTouching_) {
|
||||
return touchPosition_ - prevTouchPosition_;
|
||||
}
|
||||
return Vec2::Zero();
|
||||
#else
|
||||
return mousePosition_ - prevMousePosition_;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Input::setMousePosition(const Vec2 & /*position*/) {
|
||||
// 不支持在 Switch 上设置触摸位置
|
||||
void Input::setMousePosition(const Vec2 &position) {
|
||||
#ifndef PLATFORM_SWITCH
|
||||
SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(),
|
||||
static_cast<int>(position.x),
|
||||
static_cast<int>(position.y));
|
||||
#else
|
||||
(void)position;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Input::setMouseVisible(bool /*visible*/) {
|
||||
// Switch 无鼠标光标
|
||||
void Input::setMouseVisible(bool visible) {
|
||||
#ifndef PLATFORM_SWITCH
|
||||
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
|
||||
#else
|
||||
(void)visible;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Input::setMouseLocked(bool /*locked*/) {
|
||||
// Switch 无鼠标光标
|
||||
void Input::setMouseLocked(bool locked) {
|
||||
#ifndef PLATFORM_SWITCH
|
||||
SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE);
|
||||
#else
|
||||
(void)locked;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 便捷方法
|
||||
// ============================================================================
|
||||
|
||||
bool Input::isAnyKeyDown() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
for (int i = 0; i < MAX_BUTTONS; ++i) {
|
||||
if (buttonsDown_[i])
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
||||
if (keysDown_[i])
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Input::isAnyMouseDown() const { return touching_; }
|
||||
bool Input::isAnyMouseDown() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return touching_;
|
||||
#else
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (mouseButtonsDown_[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -10,9 +10,15 @@
|
|||
namespace extra2d {
|
||||
|
||||
Window::Window()
|
||||
: sdlWindow_(nullptr), glContext_(nullptr), width_(1280), height_(720),
|
||||
vsync_(true), shouldClose_(false), userData_(nullptr),
|
||||
eventQueue_(nullptr) {}
|
||||
: sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr),
|
||||
width_(1280), height_(720), vsync_(true), shouldClose_(false),
|
||||
fullscreen_(true), focused_(true), contentScaleX_(1.0f), contentScaleY_(1.0f),
|
||||
userData_(nullptr), eventQueue_(nullptr) {
|
||||
// 初始化光标数组
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
sdlCursors_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Window::~Window() { destroy(); }
|
||||
|
||||
|
|
@ -22,12 +28,13 @@ bool Window::create(const WindowConfig &config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
width_ = 1280; // Switch 固定分辨率
|
||||
height_ = 720;
|
||||
width_ = config.width;
|
||||
height_ = config.height;
|
||||
vsync_ = config.vsync;
|
||||
fullscreen_ = config.fullscreen;
|
||||
|
||||
// 初始化 SDL2 + 创建窗口 + GL 上下文
|
||||
if (!initSDL()) {
|
||||
if (!initSDL(config)) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL2");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -36,14 +43,19 @@ bool Window::create(const WindowConfig &config) {
|
|||
input_ = makeUnique<Input>();
|
||||
input_->init();
|
||||
|
||||
E2D_LOG_INFO("Window created: {}x{}", width_, height_);
|
||||
// PC 端初始化光标
|
||||
#ifdef PLATFORM_PC
|
||||
initCursors();
|
||||
#endif
|
||||
|
||||
E2D_LOG_INFO("Window created: {}x{} (Platform: {})",
|
||||
width_, height_, platform::getPlatformName());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Window::initSDL() {
|
||||
bool Window::initSDL(const WindowConfig &config) {
|
||||
// SDL2 全局初始化(视频 + 游戏控制器 + 音频)
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) !=
|
||||
0) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) != 0) {
|
||||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
|
@ -61,11 +73,36 @@ bool Window::initSDL() {
|
|||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
// 创建 SDL2 窗口(Switch 全屏)
|
||||
Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN;
|
||||
sdlWindow_ =
|
||||
SDL_CreateWindow("Easy2D", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
// 双缓冲
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
// 创建 SDL2 窗口
|
||||
Uint32 windowFlags = SDL_WINDOW_OPENGL;
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch 始终全屏
|
||||
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
||||
width_ = 1280;
|
||||
height_ = 720;
|
||||
#else
|
||||
// PC 端根据配置设置
|
||||
if (config.fullscreen) {
|
||||
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
} else {
|
||||
if (config.resizable) {
|
||||
windowFlags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
// 注意:SDL_WINDOWPOS_CENTERED 是位置参数,不是窗口标志
|
||||
// 窗口居中在 SDL_CreateWindow 的位置参数中处理
|
||||
}
|
||||
#endif
|
||||
|
||||
sdlWindow_ = SDL_CreateWindow(
|
||||
config.title.c_str(),
|
||||
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
|
||||
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
|
||||
width_, height_, windowFlags);
|
||||
|
||||
if (!sdlWindow_) {
|
||||
E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
|
||||
SDL_Quit();
|
||||
|
|
@ -92,8 +129,8 @@ bool Window::initSDL() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) ==
|
||||
0) {
|
||||
// 加载 OpenGL ES 函数指针
|
||||
if (gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
|
||||
E2D_LOG_ERROR("gladLoadGLES2Loader failed");
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
|
|
@ -106,11 +143,23 @@ bool Window::initSDL() {
|
|||
// 设置 VSync
|
||||
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
|
||||
|
||||
E2D_LOG_INFO("SDL2 + GLES 3.2 (glad) initialized successfully");
|
||||
// PC 端更新 DPI 缩放
|
||||
#ifndef PLATFORM_SWITCH
|
||||
updateContentScale();
|
||||
#endif
|
||||
|
||||
E2D_LOG_INFO("SDL2 + GLES 3.2 initialized successfully");
|
||||
E2D_LOG_INFO("OpenGL Version: {}",
|
||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
||||
E2D_LOG_INFO("OpenGL Renderer: {}",
|
||||
reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::deinitSDL() {
|
||||
deinitCursors();
|
||||
|
||||
if (glContext_) {
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
|
|
@ -147,18 +196,24 @@ void Window::pollEvents() {
|
|||
case SDL_WINDOWEVENT:
|
||||
switch (event.window.event) {
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
width_ = event.window.data1;
|
||||
height_ = event.window.data2;
|
||||
#ifndef PLATFORM_SWITCH
|
||||
updateContentScale();
|
||||
#endif
|
||||
if (resizeCallback_) {
|
||||
resizeCallback_(width_, height_);
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
focused_ = true;
|
||||
if (focusCallback_) {
|
||||
focusCallback_(true);
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
focused_ = false;
|
||||
if (focusCallback_) {
|
||||
focusCallback_(false);
|
||||
}
|
||||
|
|
@ -184,20 +239,50 @@ bool Window::shouldClose() const { return shouldClose_; }
|
|||
|
||||
void Window::setShouldClose(bool close) { shouldClose_ = close; }
|
||||
|
||||
void Window::setTitle(const String & /*title*/) {
|
||||
// Switch 无窗口标题
|
||||
void Window::setTitle(const String &title) {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
SDL_SetWindowTitle(sdlWindow_, title.c_str());
|
||||
}
|
||||
#else
|
||||
(void)title;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setSize(int /*width*/, int /*height*/) {
|
||||
// Switch 固定 1280x720
|
||||
void Window::setSize(int width, int height) {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
SDL_SetWindowSize(sdlWindow_, width, height);
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
#else
|
||||
(void)width;
|
||||
(void)height;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setPosition(int /*x*/, int /*y*/) {
|
||||
// Switch 无窗口位置
|
||||
void Window::setPosition(int x, int y) {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
SDL_SetWindowPosition(sdlWindow_, x, y);
|
||||
}
|
||||
#else
|
||||
(void)x;
|
||||
(void)y;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setFullscreen(bool /*fullscreen*/) {
|
||||
// Switch 始终全屏
|
||||
void Window::setFullscreen(bool fullscreen) {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
Uint32 flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
||||
SDL_SetWindowFullscreen(sdlWindow_, flags);
|
||||
fullscreen_ = fullscreen;
|
||||
}
|
||||
#else
|
||||
(void)fullscreen;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setVSync(bool enabled) {
|
||||
|
|
@ -205,16 +290,135 @@ void Window::setVSync(bool enabled) {
|
|||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
void Window::setResizable(bool /*resizable*/) {
|
||||
// Switch 不支持
|
||||
void Window::setResizable(bool resizable) {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
SDL_SetWindowResizable(sdlWindow_, resizable ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
#else
|
||||
(void)resizable;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setCursor(CursorShape /*shape*/) {
|
||||
// Switch 无鼠标光标
|
||||
Vec2 Window::getPosition() const {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
int x, y;
|
||||
SDL_GetWindowPosition(sdlWindow_, &x, &y);
|
||||
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
#endif
|
||||
return Vec2::Zero();
|
||||
}
|
||||
|
||||
float Window::getContentScaleX() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return 1.0f;
|
||||
#else
|
||||
return contentScaleX_;
|
||||
#endif
|
||||
}
|
||||
|
||||
float Window::getContentScaleY() const {
|
||||
#ifdef PLATFORM_SWITCH
|
||||
return 1.0f;
|
||||
#else
|
||||
return contentScaleY_;
|
||||
#endif
|
||||
}
|
||||
|
||||
Vec2 Window::getContentScale() const {
|
||||
return Vec2(getContentScaleX(), getContentScaleY());
|
||||
}
|
||||
|
||||
bool Window::isMinimized() const {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
|
||||
return (flags & SDL_WINDOW_MINIMIZED) != 0;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Window::isMaximized() const {
|
||||
#ifdef PLATFORM_PC
|
||||
if (sdlWindow_) {
|
||||
Uint32 flags = SDL_GetWindowFlags(sdlWindow_);
|
||||
return (flags & SDL_WINDOW_MAXIMIZED) != 0;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void Window::initCursors() {
|
||||
#ifdef PLATFORM_PC
|
||||
sdlCursors_[0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
|
||||
sdlCursors_[1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
|
||||
sdlCursors_[2] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
|
||||
sdlCursors_[3] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
|
||||
sdlCursors_[4] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
|
||||
sdlCursors_[5] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
|
||||
sdlCursors_[6] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
|
||||
sdlCursors_[7] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
|
||||
sdlCursors_[8] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::deinitCursors() {
|
||||
#ifdef PLATFORM_PC
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
if (sdlCursors_[i]) {
|
||||
SDL_FreeCursor(sdlCursors_[i]);
|
||||
sdlCursors_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
currentCursor_ = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setCursor(CursorShape shape) {
|
||||
#ifdef PLATFORM_PC
|
||||
int index = static_cast<int>(shape);
|
||||
if (index >= 0 && index < 9 && sdlCursors_[index]) {
|
||||
SDL_SetCursor(sdlCursors_[index]);
|
||||
currentCursor_ = sdlCursors_[index];
|
||||
}
|
||||
#else
|
||||
(void)shape;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::resetCursor() {
|
||||
// Switch 无鼠标光标
|
||||
#ifdef PLATFORM_PC
|
||||
SDL_SetCursor(SDL_GetDefaultCursor());
|
||||
currentCursor_ = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::setMouseVisible(bool visible) {
|
||||
#ifdef PLATFORM_PC
|
||||
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
|
||||
#else
|
||||
(void)visible;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::updateContentScale() {
|
||||
#ifndef PLATFORM_SWITCH
|
||||
if (sdlWindow_) {
|
||||
// 使用 DPI 计算内容缩放比例
|
||||
int displayIndex = SDL_GetWindowDisplayIndex(sdlWindow_);
|
||||
if (displayIndex >= 0) {
|
||||
float ddpi, hdpi, vdpi;
|
||||
if (SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) == 0) {
|
||||
// 假设标准 DPI 为 96
|
||||
contentScaleX_ = hdpi / 96.0f;
|
||||
contentScaleY_ = vdpi / 96.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,959 @@
|
|||
# Extra2D API 参考文档
|
||||
|
||||
## 目录
|
||||
|
||||
- [核心系统](#核心系统)
|
||||
- [应用管理](#应用管理)
|
||||
- [场景系统](#场景系统)
|
||||
- [节点系统](#节点系统)
|
||||
- [输入系统](#输入系统)
|
||||
- [资源管理](#资源管理)
|
||||
- [动画系统](#动画系统)
|
||||
- [音频系统](#音频系统)
|
||||
- [文件系统](#文件系统)
|
||||
|
||||
---
|
||||
|
||||
## 核心系统
|
||||
|
||||
### 类型定义
|
||||
|
||||
```cpp
|
||||
namespace extra2d {
|
||||
|
||||
// 基本类型
|
||||
using Vec2 = glm::vec2;
|
||||
using Vec3 = glm::vec3;
|
||||
using Vec4 = glm::vec4;
|
||||
using Mat3 = glm::mat3;
|
||||
using Mat4 = glm::mat4;
|
||||
|
||||
// 智能指针
|
||||
template<typename T>
|
||||
using Ptr = std::shared_ptr<T>;
|
||||
|
||||
template<typename T, typename... Args>
|
||||
Ptr<T> makePtr(Args&&... args) {
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
```
|
||||
|
||||
### 颜色类
|
||||
|
||||
```cpp
|
||||
struct Color {
|
||||
float r, g, b, a;
|
||||
|
||||
Color(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f);
|
||||
|
||||
// 预定义颜色
|
||||
static Color White;
|
||||
static Color Black;
|
||||
static Color Red;
|
||||
static Color Green;
|
||||
static Color Blue;
|
||||
static Color Yellow;
|
||||
static Color Transparent;
|
||||
};
|
||||
```
|
||||
|
||||
### 矩形类
|
||||
|
||||
```cpp
|
||||
struct Rect {
|
||||
float x, y, width, height;
|
||||
|
||||
Rect(float x = 0, float y = 0, float w = 0, float h = 0);
|
||||
|
||||
bool contains(const Vec2& point) const;
|
||||
bool intersects(const Rect& other) const;
|
||||
|
||||
float left() const { return x; }
|
||||
float right() const { return x + width; }
|
||||
float top() const { return y; }
|
||||
float bottom() const { return y + height; }
|
||||
Vec2 center() const { return Vec2(x + width/2, y + height/2); }
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 应用管理
|
||||
|
||||
### AppConfig
|
||||
|
||||
应用配置结构体。
|
||||
|
||||
```cpp
|
||||
struct AppConfig {
|
||||
String title = "Extra2D Application";
|
||||
int width = 800;
|
||||
int height = 600;
|
||||
bool fullscreen = false;
|
||||
bool resizable = true;
|
||||
bool vsync = true;
|
||||
int fpsLimit = 0; // 0 = 不限制
|
||||
BackendType renderBackend = BackendType::OpenGL;
|
||||
int msaaSamples = 0;
|
||||
};
|
||||
```
|
||||
|
||||
### Application
|
||||
|
||||
应用主类,单例模式。
|
||||
|
||||
```cpp
|
||||
class Application {
|
||||
public:
|
||||
// 获取单例实例
|
||||
static Application& instance();
|
||||
|
||||
// 初始化应用
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
// 运行主循环
|
||||
void run();
|
||||
|
||||
// 退出应用
|
||||
void quit();
|
||||
|
||||
// 进入场景
|
||||
void enterScene(Ptr<Scene> scene);
|
||||
void enterScene(Ptr<Scene> scene, Ptr<Transition> transition);
|
||||
|
||||
// 获取子系统
|
||||
Input& input();
|
||||
AudioEngine& audio();
|
||||
ResourceManager& resources();
|
||||
RenderBackend& renderer();
|
||||
|
||||
// 获取配置
|
||||
const AppConfig& getConfig() const;
|
||||
|
||||
// 获取当前 FPS
|
||||
float fps() const;
|
||||
|
||||
// Switch 特定:检测是否连接底座
|
||||
bool isDocked() const;
|
||||
};
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
int main() {
|
||||
// 初始化日志
|
||||
Logger::init();
|
||||
Logger::setLevel(LogLevel::Debug);
|
||||
|
||||
// 配置应用
|
||||
AppConfig config;
|
||||
config.title = "My Game";
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
config.vsync = true;
|
||||
|
||||
// 初始化应用
|
||||
auto& app = Application::instance();
|
||||
if (!app.init(config)) {
|
||||
E2D_LOG_ERROR("应用初始化失败!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 进入场景
|
||||
app.enterScene(makePtr<MyScene>());
|
||||
|
||||
// 运行主循环
|
||||
app.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景系统
|
||||
|
||||
### Scene
|
||||
|
||||
场景类,作为游戏内容的容器。
|
||||
|
||||
```cpp
|
||||
class Scene : public Node {
|
||||
public:
|
||||
// 构造函数
|
||||
Scene();
|
||||
|
||||
// 场景生命周期回调
|
||||
virtual void onEnter(); // 进入场景时调用
|
||||
virtual void onExit(); // 退出场景时调用
|
||||
virtual void onUpdate(float dt); // 每帧更新
|
||||
virtual void onRender(RenderBackend& renderer); // 渲染
|
||||
|
||||
// 设置背景颜色
|
||||
void setBackgroundColor(const Color& color);
|
||||
|
||||
// 空间索引
|
||||
void setSpatialIndexingEnabled(bool enabled);
|
||||
bool isSpatialIndexingEnabled() const;
|
||||
|
||||
// 查询碰撞
|
||||
std::vector<std::pair<Node*, Node*>> queryCollisions();
|
||||
|
||||
// 获取空间管理器
|
||||
SpatialManager& getSpatialManager();
|
||||
};
|
||||
```
|
||||
|
||||
### Transition
|
||||
|
||||
场景过渡动画基类。
|
||||
|
||||
```cpp
|
||||
class Transition {
|
||||
public:
|
||||
explicit Transition(float duration);
|
||||
|
||||
virtual void update(float dt) = 0;
|
||||
virtual void render(RenderBackend& renderer,
|
||||
Ptr<Scene> fromScene,
|
||||
Ptr<Scene> toScene) = 0;
|
||||
|
||||
bool isFinished() const;
|
||||
};
|
||||
|
||||
// 内置过渡效果
|
||||
class FadeTransition : public Transition {
|
||||
public:
|
||||
FadeTransition(float duration, const Color& color = Color::Black);
|
||||
};
|
||||
|
||||
class SlideTransition : public Transition {
|
||||
public:
|
||||
SlideTransition(float duration, Direction direction);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 节点系统
|
||||
|
||||
### Node
|
||||
|
||||
所有场景对象的基类。
|
||||
|
||||
```cpp
|
||||
class Node {
|
||||
public:
|
||||
Node();
|
||||
virtual ~Node();
|
||||
|
||||
// 变换
|
||||
void setPosition(const Vec2& pos);
|
||||
Vec2 getPosition() const;
|
||||
|
||||
void setRotation(float degrees);
|
||||
float getRotation() const;
|
||||
|
||||
void setScale(const Vec2& scale);
|
||||
Vec2 getScale() const;
|
||||
|
||||
void setAnchor(const Vec2& anchor);
|
||||
Vec2 getAnchor() const;
|
||||
|
||||
// 层级
|
||||
void addChild(Ptr<Node> child);
|
||||
void removeChild(Ptr<Node> child);
|
||||
void removeFromParent();
|
||||
|
||||
// 可见性
|
||||
void setVisible(bool visible);
|
||||
bool isVisible() const;
|
||||
|
||||
// 动作
|
||||
void runAction(Ptr<Action> action);
|
||||
void stopAllActions();
|
||||
|
||||
// 渲染
|
||||
virtual void onRender(RenderBackend& renderer);
|
||||
|
||||
// 更新
|
||||
virtual void onUpdate(float dt);
|
||||
|
||||
// 边界框(用于碰撞检测)
|
||||
virtual Rect getBoundingBox() const;
|
||||
|
||||
// 空间索引
|
||||
void setSpatialIndexed(bool indexed);
|
||||
bool isSpatialIndexed() const;
|
||||
};
|
||||
```
|
||||
|
||||
### Sprite
|
||||
|
||||
精灵节点,用于显示2D图像。
|
||||
|
||||
```cpp
|
||||
class Sprite : public Node {
|
||||
public:
|
||||
// 创建方法
|
||||
static Ptr<Sprite> create(Ptr<Texture> texture);
|
||||
static Ptr<Sprite> create(const std::string& texturePath);
|
||||
|
||||
// 设置纹理
|
||||
void setTexture(Ptr<Texture> texture);
|
||||
Ptr<Texture> getTexture() const;
|
||||
|
||||
// 设置纹理矩形(用于精灵表)
|
||||
void setTextureRect(const Rect& rect);
|
||||
|
||||
// 设置颜色调制
|
||||
void setColor(const Color& color);
|
||||
Color getColor() const;
|
||||
|
||||
// 翻转
|
||||
void setFlippedX(bool flipped);
|
||||
void setFlippedY(bool flipped);
|
||||
};
|
||||
```
|
||||
|
||||
### Text
|
||||
|
||||
文本节点。
|
||||
|
||||
```cpp
|
||||
class Text : public Node {
|
||||
public:
|
||||
static Ptr<Text> create(const std::string& text = "");
|
||||
|
||||
// 文本内容
|
||||
void setText(const std::string& text);
|
||||
std::string getText() const;
|
||||
|
||||
// 字体
|
||||
void setFont(Ptr<FontAtlas> font);
|
||||
void setFontSize(int size);
|
||||
|
||||
// 颜色
|
||||
void setTextColor(const Color& color);
|
||||
Color getTextColor() const;
|
||||
|
||||
// 对齐
|
||||
void setHorizontalAlignment(Alignment align);
|
||||
void setVerticalAlignment(Alignment align);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 输入系统
|
||||
|
||||
### Input
|
||||
|
||||
输入管理类。
|
||||
|
||||
```cpp
|
||||
class Input {
|
||||
public:
|
||||
// 键盘
|
||||
bool isKeyDown(int keyCode) const; // 按键是否按下
|
||||
bool isKeyPressed(int keyCode) const; // 按键是否刚按下
|
||||
bool isKeyReleased(int keyCode) const; // 按键是否刚释放
|
||||
|
||||
// 手柄按钮
|
||||
bool isButtonDown(int button) const;
|
||||
bool isButtonPressed(int button) const;
|
||||
bool isButtonReleased(int button) const;
|
||||
|
||||
// 摇杆
|
||||
Vec2 getLeftStick() const;
|
||||
Vec2 getRightStick() const;
|
||||
|
||||
// 鼠标(PC端)
|
||||
Vec2 getMousePosition() const;
|
||||
bool isMouseDown(int button) const;
|
||||
bool isMousePressed(int button) const;
|
||||
|
||||
// 触摸(Switch/移动端)
|
||||
bool isTouching() const;
|
||||
Vec2 getTouchPosition() const;
|
||||
int getTouchCount() const;
|
||||
};
|
||||
```
|
||||
|
||||
### 按键码 (Key)
|
||||
|
||||
```cpp
|
||||
namespace Key {
|
||||
// 字母
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M,
|
||||
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
|
||||
|
||||
// 数字
|
||||
Num0, Num1, Num2, Num3, Num4,
|
||||
Num5, Num6, Num7, Num8, Num9,
|
||||
|
||||
// 功能键
|
||||
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
|
||||
|
||||
// 方向键
|
||||
Up, Down, Left, Right,
|
||||
|
||||
// 特殊键
|
||||
Escape, Enter, Tab, Backspace, Space,
|
||||
Insert, Delete, Home, End, PageUp, PageDown,
|
||||
LeftShift, LeftControl, LeftAlt, LeftSuper,
|
||||
RightShift, RightControl, RightAlt, RightSuper
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
void MyScene::onUpdate(float dt) {
|
||||
auto& input = Application::instance().input();
|
||||
|
||||
// 键盘输入
|
||||
if (input.isKeyPressed(Key::Space)) {
|
||||
// 空格键按下
|
||||
jump();
|
||||
}
|
||||
|
||||
if (input.isKeyDown(Key::Left)) {
|
||||
// 左方向键按住
|
||||
moveLeft();
|
||||
}
|
||||
|
||||
// 手柄输入
|
||||
if (input.isButtonPressed(GamepadButton::A)) {
|
||||
// A 按钮按下
|
||||
jump();
|
||||
}
|
||||
|
||||
// 摇杆输入
|
||||
Vec2 leftStick = input.getLeftStick();
|
||||
move(leftStick.x * speed, leftStick.y * speed);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 资源管理
|
||||
|
||||
### ResourceManager
|
||||
|
||||
资源管理类,负责加载和管理游戏资源。
|
||||
|
||||
```cpp
|
||||
class ResourceManager {
|
||||
public:
|
||||
// 纹理
|
||||
Ptr<Texture> loadTexture(const std::string& path);
|
||||
Ptr<Texture> getTexture(const std::string& path);
|
||||
void unloadTexture(const std::string& path);
|
||||
|
||||
// 字体
|
||||
Ptr<FontAtlas> loadFont(const std::string& path, int size, bool useSDF = false);
|
||||
Ptr<FontAtlas> loadFontWithFallbacks(const std::vector<std::string>& paths,
|
||||
int size, bool useSDF = false);
|
||||
|
||||
// 音频
|
||||
Ptr<Sound> loadSound(const std::string& path);
|
||||
Ptr<Music> loadMusic(const std::string& path);
|
||||
|
||||
// 精灵表
|
||||
Ptr<SpriteFrameCache> loadSpriteSheet(const std::string& path);
|
||||
|
||||
// 清理
|
||||
void unloadAll();
|
||||
void unloadUnused();
|
||||
};
|
||||
```
|
||||
|
||||
### Texture
|
||||
|
||||
纹理类。
|
||||
|
||||
```cpp
|
||||
class Texture {
|
||||
public:
|
||||
// 获取尺寸
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
Vec2 getSize() const;
|
||||
|
||||
// 获取 OpenGL 纹理 ID
|
||||
GLuint getTextureID() const;
|
||||
};
|
||||
```
|
||||
|
||||
### FontAtlas
|
||||
|
||||
字体图集类。
|
||||
|
||||
```cpp
|
||||
class FontAtlas {
|
||||
public:
|
||||
// 获取字体信息
|
||||
int getFontSize() const;
|
||||
bool isSDF() const;
|
||||
|
||||
// 获取字符信息
|
||||
const Glyph& getGlyph(char32_t charCode) const;
|
||||
|
||||
// 获取行高
|
||||
float getLineHeight() const;
|
||||
|
||||
// 获取纹理
|
||||
Ptr<Texture> getTexture() const;
|
||||
};
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
void MyScene::onEnter() {
|
||||
auto& resources = Application::instance().resources();
|
||||
|
||||
// 加载纹理
|
||||
auto playerTexture = resources.loadTexture("player.png");
|
||||
auto enemyTexture = resources.loadTexture("enemies/slime.png");
|
||||
|
||||
// 加载字体
|
||||
auto font = resources.loadFont("font.ttf", 24, true);
|
||||
|
||||
// 创建精灵
|
||||
auto player = Sprite::create(playerTexture);
|
||||
addChild(player);
|
||||
|
||||
// 创建文本
|
||||
auto text = Text::create("Hello World");
|
||||
text->setFont(font);
|
||||
addChild(text);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 动画系统
|
||||
|
||||
### Action
|
||||
|
||||
动作基类。
|
||||
|
||||
```cpp
|
||||
class Action {
|
||||
public:
|
||||
virtual void update(float dt) = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
void reset();
|
||||
};
|
||||
```
|
||||
|
||||
### 基础动作
|
||||
|
||||
```cpp
|
||||
// 移动
|
||||
class MoveTo : public Action {
|
||||
public:
|
||||
MoveTo(float duration, const Vec2& position);
|
||||
};
|
||||
|
||||
class MoveBy : public Action {
|
||||
public:
|
||||
MoveBy(float duration, const Vec2& delta);
|
||||
};
|
||||
|
||||
// 缩放
|
||||
class ScaleTo : public Action {
|
||||
public:
|
||||
ScaleTo(float duration, const Vec2& scale);
|
||||
ScaleTo(float duration, float scale);
|
||||
};
|
||||
|
||||
class ScaleBy : public Action {
|
||||
public:
|
||||
ScaleBy(float duration, const Vec2& scale);
|
||||
ScaleBy(float duration, float scale);
|
||||
};
|
||||
|
||||
// 旋转
|
||||
class RotateTo : public Action {
|
||||
public:
|
||||
RotateTo(float duration, float degrees);
|
||||
};
|
||||
|
||||
class RotateBy : public Action {
|
||||
public:
|
||||
RotateBy(float duration, float degrees);
|
||||
};
|
||||
|
||||
// 淡入淡出
|
||||
class FadeTo : public Action {
|
||||
public:
|
||||
FadeTo(float duration, float opacity);
|
||||
};
|
||||
|
||||
class FadeIn : public Action {
|
||||
public:
|
||||
FadeIn(float duration);
|
||||
};
|
||||
|
||||
class FadeOut : public Action {
|
||||
public:
|
||||
FadeOut(float duration);
|
||||
};
|
||||
|
||||
// 延迟
|
||||
class Delay : public Action {
|
||||
public:
|
||||
Delay(float duration);
|
||||
};
|
||||
|
||||
// 回调
|
||||
class CallFunc : public Action {
|
||||
public:
|
||||
CallFunc(std::function<void()> callback);
|
||||
};
|
||||
```
|
||||
|
||||
### 组合动作
|
||||
|
||||
```cpp
|
||||
// 顺序执行
|
||||
class Sequence : public Action {
|
||||
public:
|
||||
Sequence(std::vector<Ptr<Action>> actions);
|
||||
|
||||
template<typename... Args>
|
||||
static Ptr<Sequence> create(Args&&... args) {
|
||||
return makePtr<Sequence>(std::vector<Ptr<Action>>{std::forward<Args>(args)...});
|
||||
}
|
||||
};
|
||||
|
||||
// 同时执行
|
||||
class Spawn : public Action {
|
||||
public:
|
||||
Spawn(std::vector<Ptr<Action>> actions);
|
||||
};
|
||||
|
||||
// 重复
|
||||
class Repeat : public Action {
|
||||
public:
|
||||
Repeat(Ptr<Action> action, int times = -1); // -1 = 无限重复
|
||||
};
|
||||
|
||||
// 反向
|
||||
class Reverse : public Action {
|
||||
public:
|
||||
Reverse(Ptr<Action> action);
|
||||
};
|
||||
```
|
||||
|
||||
### 缓动函数
|
||||
|
||||
```cpp
|
||||
namespace Ease {
|
||||
// 线性
|
||||
float linear(float t);
|
||||
|
||||
// 二次
|
||||
float inQuad(float t);
|
||||
float outQuad(float t);
|
||||
float inOutQuad(float t);
|
||||
|
||||
// 三次
|
||||
float inCubic(float t);
|
||||
float outCubic(float t);
|
||||
float inOutCubic(float t);
|
||||
|
||||
// 弹性
|
||||
float inElastic(float t);
|
||||
float outElastic(float t);
|
||||
|
||||
// 弹跳
|
||||
float inBounce(float t);
|
||||
float outBounce(float t);
|
||||
|
||||
// 回退
|
||||
float inBack(float t);
|
||||
float outBack(float t);
|
||||
}
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
// 创建精灵
|
||||
auto sprite = Sprite::create("player.png");
|
||||
sprite->setPosition(Vec2(100, 100));
|
||||
addChild(sprite);
|
||||
|
||||
// 简单动作
|
||||
sprite->runAction(makePtr<MoveTo>(1.0f, Vec2(300, 200)));
|
||||
|
||||
// 组合动作
|
||||
sprite->runAction(makePtr<Sequence>(
|
||||
makePtr<ScaleTo>(0.5f, Vec2(1.5f, 1.5f)),
|
||||
makePtr<Delay>(0.2f),
|
||||
makePtr<ScaleTo>(0.5f, Vec2(1.0f, 1.0f)),
|
||||
makePtr<CallFunc>([]() {
|
||||
E2D_LOG_INFO("动画完成!");
|
||||
})
|
||||
));
|
||||
|
||||
// 重复动画
|
||||
sprite->runAction(makePtr<Repeat>(
|
||||
makePtr<Sequence>(
|
||||
makePtr<RotateBy>(1.0f, 360.0f),
|
||||
makePtr<Delay>(0.5f)
|
||||
)
|
||||
));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 音频系统
|
||||
|
||||
### AudioEngine
|
||||
|
||||
音频引擎类。
|
||||
|
||||
```cpp
|
||||
class AudioEngine {
|
||||
public:
|
||||
// 音量控制 (0.0 - 1.0)
|
||||
void setMasterVolume(float volume);
|
||||
float getMasterVolume() const;
|
||||
|
||||
void setBGMVolume(float volume);
|
||||
void setSFXVolume(float volume);
|
||||
|
||||
// 播放控制
|
||||
void pauseAll();
|
||||
void resumeAll();
|
||||
void stopAll();
|
||||
};
|
||||
```
|
||||
|
||||
### Sound
|
||||
|
||||
音效类(短音频,适合音效)。
|
||||
|
||||
```cpp
|
||||
class Sound {
|
||||
public:
|
||||
void play(int loops = 0); // loops: 0=播放1次, -1=无限循环
|
||||
void stop();
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
void setVolume(float volume);
|
||||
bool isPlaying() const;
|
||||
};
|
||||
```
|
||||
|
||||
### Music
|
||||
|
||||
音乐类(长音频,适合背景音乐)。
|
||||
|
||||
```cpp
|
||||
class Music {
|
||||
public:
|
||||
void play(int loops = -1);
|
||||
void stop();
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
void setVolume(float volume);
|
||||
bool isPlaying() const;
|
||||
|
||||
void setLoopPoints(double start, double end);
|
||||
};
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
void MyScene::onEnter() {
|
||||
auto& audio = Application::instance().audio();
|
||||
auto& resources = Application::instance().resources();
|
||||
|
||||
// 加载并播放背景音乐
|
||||
auto bgm = resources.loadMusic("bgm/level1.ogg");
|
||||
bgm->play(-1); // 无限循环
|
||||
bgm->setVolume(0.7f);
|
||||
|
||||
// 加载音效
|
||||
jumpSound_ = resources.loadSound("sfx/jump.wav");
|
||||
coinSound_ = resources.loadSound("sfx/coin.wav");
|
||||
}
|
||||
|
||||
void MyScene::jump() {
|
||||
jumpSound_->play();
|
||||
}
|
||||
|
||||
void MyScene::collectCoin() {
|
||||
coinSound_->play();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件系统
|
||||
|
||||
### FileSystem
|
||||
|
||||
跨平台文件系统工具类。
|
||||
|
||||
```cpp
|
||||
class FileSystem {
|
||||
public:
|
||||
// 路径解析(自动处理平台差异)
|
||||
// PC: "assets/font.ttf" -> "C:/.../assets/font.ttf"
|
||||
// Switch: "assets/font.ttf" -> "romfs:/assets/font.ttf"
|
||||
static std::string resolvePath(const std::string& relativePath);
|
||||
|
||||
// 获取资源根目录
|
||||
static std::string getResourceRoot();
|
||||
|
||||
// 获取可执行文件目录
|
||||
static std::string getExecutableDirectory();
|
||||
|
||||
// 获取当前工作目录
|
||||
static std::string getCurrentWorkingDirectory();
|
||||
|
||||
// 路径组合
|
||||
static std::string combinePath(const std::string& path1,
|
||||
const std::string& path2);
|
||||
|
||||
// 文件/目录检查
|
||||
static bool fileExists(const std::string& path);
|
||||
static bool directoryExists(const std::string& path);
|
||||
|
||||
// 读取文件内容
|
||||
static std::vector<uint8_t> readFile(const std::string& path);
|
||||
static std::string readTextFile(const std::string& path);
|
||||
|
||||
// 写入文件
|
||||
static bool writeFile(const std::string& path,
|
||||
const std::vector<uint8_t>& data);
|
||||
static bool writeTextFile(const std::string& path,
|
||||
const std::string& content);
|
||||
};
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
// 解析资源路径(跨平台)
|
||||
std::string fontPath = FileSystem::resolvePath("fonts/main.ttf");
|
||||
// PC: "C:/.../assets/fonts/main.ttf"
|
||||
// Switch: "romfs:/assets/fonts/main.ttf"
|
||||
|
||||
// 检查文件是否存在
|
||||
if (FileSystem::fileExists(fontPath)) {
|
||||
auto font = resources.loadFont(fontPath, 24);
|
||||
}
|
||||
|
||||
// 读取配置文件
|
||||
std::string configPath = FileSystem::resolvePath("config/game.json");
|
||||
std::string jsonContent = FileSystem::readTextFile(configPath);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 日志系统
|
||||
|
||||
### Logger
|
||||
|
||||
日志系统。
|
||||
|
||||
```cpp
|
||||
enum class LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static void init();
|
||||
static void shutdown();
|
||||
|
||||
static void setLevel(LogLevel level);
|
||||
|
||||
// 日志宏
|
||||
#define E2D_LOG_DEBUG(...) // 调试日志
|
||||
#define E2D_LOG_INFO(...) // 信息日志
|
||||
#define E2D_LOG_WARN(...) // 警告日志
|
||||
#define E2D_LOG_ERROR(...) // 错误日志
|
||||
};
|
||||
```
|
||||
|
||||
**使用示例:**
|
||||
|
||||
```cpp
|
||||
void MyClass::doSomething() {
|
||||
E2D_LOG_DEBUG("进入函数 doSomething");
|
||||
|
||||
if (!loadData()) {
|
||||
E2D_LOG_ERROR("加载数据失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("成功加载 {} 条记录", recordCount);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 平台兼容性
|
||||
|
||||
### 平台检测
|
||||
|
||||
```cpp
|
||||
// 编译时检测
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch 代码
|
||||
#elif defined(PLATFORM_PC)
|
||||
// PC 代码
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
// Windows 代码
|
||||
#elif defined(PLATFORM_LINUX)
|
||||
// Linux 代码
|
||||
#elif defined(PLATFORM_MACOS)
|
||||
// macOS 代码
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 运行时检测
|
||||
namespace platform {
|
||||
bool isSwitch();
|
||||
bool isPC();
|
||||
bool isWindows();
|
||||
bool isLinux();
|
||||
bool isMacOS();
|
||||
const char* getPlatformName();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 更多文档
|
||||
|
||||
- [Switch 构建指南](./SWITCH_BUILD_GUIDE.md)
|
||||
- [PC 构建指南](./PC_BUILD_GUIDE.md)
|
||||
- [迁移完成记录](./SWITCH_MIGRATION_COMPLETE.md)
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026年2月10日
|
||||
**Extra2D 版本**: 3.1.0
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
# Extra2D 文档索引
|
||||
|
||||
欢迎来到 Extra2D 文档中心!这里包含了使用 Extra2D 游戏引擎所需的所有文档。
|
||||
|
||||
## 📚 快速导航
|
||||
|
||||
### 入门指南
|
||||
|
||||
| 文档 | 描述 |
|
||||
|------|------|
|
||||
| [README.md](../README.md) | 项目概述和快速开始 |
|
||||
| [Switch 构建指南](./SWITCH_BUILD_GUIDE.md) | Nintendo Switch 平台构建教程 |
|
||||
| [PC 构建指南](./PC_BUILD_GUIDE.md) | Windows/Linux/macOS 构建教程 |
|
||||
|
||||
### API 参考
|
||||
|
||||
| 文档 | 描述 |
|
||||
|------|------|
|
||||
| [API 参考文档](./API_REFERENCE.md) | 完整的 API 文档和示例 |
|
||||
|
||||
### 开发文档
|
||||
|
||||
| 文档 | 描述 |
|
||||
|------|------|
|
||||
| [迁移完成记录](./SWITCH_MIGRATION_COMPLETE.md) | 项目迁移历史记录 |
|
||||
| [数据存储文档](./DataStore.md) | 数据持久化系统文档 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 选择平台
|
||||
|
||||
**Nintendo Switch:**
|
||||
```bash
|
||||
# 设置环境变量
|
||||
$env:DEVKITPRO = "C:/devkitPro"
|
||||
|
||||
# 配置并构建
|
||||
xmake f --plat=switch -a arm64
|
||||
xmake
|
||||
```
|
||||
|
||||
**Windows PC:**
|
||||
```bash
|
||||
# 设置环境变量
|
||||
$env:VCPKG_ROOT = "C:\vcpkg"
|
||||
|
||||
# 配置并构建
|
||||
xmake f --plat=windows -a x64
|
||||
xmake
|
||||
```
|
||||
|
||||
### 2. 运行示例
|
||||
|
||||
```bash
|
||||
# Switch(生成 .nro 文件)
|
||||
./build/switch/hello_world.nro
|
||||
|
||||
# Windows
|
||||
./build/windows/hello_world.exe
|
||||
```
|
||||
|
||||
### 3. 开始开发
|
||||
|
||||
```cpp
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
int main() {
|
||||
// 初始化
|
||||
Logger::init();
|
||||
|
||||
AppConfig config;
|
||||
config.title = "My Game";
|
||||
config.width = 1280;
|
||||
config.height = 720;
|
||||
|
||||
auto& app = Application::instance();
|
||||
app.init(config);
|
||||
|
||||
// 创建场景
|
||||
auto scene = makePtr<Scene>();
|
||||
|
||||
// 添加精灵
|
||||
auto sprite = Sprite::create("player.png");
|
||||
scene->addChild(sprite);
|
||||
|
||||
// 运行动画
|
||||
sprite->runAction(makePtr<MoveTo>(1.0f, Vec2(300, 200)));
|
||||
|
||||
// 运行
|
||||
app.enterScene(scene);
|
||||
app.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 核心概念
|
||||
|
||||
### 应用生命周期
|
||||
|
||||
```
|
||||
main()
|
||||
└── Application::init()
|
||||
└── Scene::onEnter()
|
||||
└── Node::onUpdate() [每帧]
|
||||
└── Node::onRender() [每帧]
|
||||
└── Scene::onExit()
|
||||
```
|
||||
|
||||
### 场景图结构
|
||||
|
||||
```
|
||||
Scene (场景)
|
||||
├── Node (节点)
|
||||
│ ├── Sprite (精灵)
|
||||
│ ├── Text (文本)
|
||||
│ └── CustomNode (自定义节点)
|
||||
└── Node
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 坐标系统
|
||||
|
||||
- **原点**: 左上角 (0, 0)
|
||||
- **X轴**: 向右为正
|
||||
- **Y轴**: 向下为正
|
||||
- **单位**: 像素
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 平台差异
|
||||
|
||||
| 功能 | Switch | PC |
|
||||
|------|--------|-----|
|
||||
| 窗口 | 固定全屏 | 可调整大小 |
|
||||
| 输入 | 手柄/触摸 | 键盘/鼠标/手柄 |
|
||||
| 资源路径 | `romfs:/` | `./assets/` |
|
||||
| 渲染 | OpenGL ES | OpenGL ES (Angle) |
|
||||
|
||||
---
|
||||
|
||||
## 💡 示例代码
|
||||
|
||||
### 基础示例
|
||||
|
||||
- [Hello World](../Extra2D/examples/hello_world/main.cpp) - 基础窗口和文本
|
||||
- [Collision Demo](../Extra2D/examples/collision_demo/main.cpp) - 碰撞检测
|
||||
- [Spatial Index Demo](../Extra2D/examples/spatial_index_demo/main.cpp) - 空间索引
|
||||
|
||||
### 常用模式
|
||||
|
||||
**场景切换:**
|
||||
```cpp
|
||||
auto newScene = makePtr<GameScene>();
|
||||
auto transition = makePtr<FadeTransition>(0.5f);
|
||||
app.enterScene(newScene, transition);
|
||||
```
|
||||
|
||||
**输入处理:**
|
||||
```cpp
|
||||
void onUpdate(float dt) {
|
||||
auto& input = app.input();
|
||||
|
||||
if (input.isKeyPressed(Key::Space)) {
|
||||
jump();
|
||||
}
|
||||
|
||||
if (input.isButtonPressed(GamepadButton::A)) {
|
||||
jump();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**资源加载:**
|
||||
```cpp
|
||||
auto& resources = app.resources();
|
||||
auto texture = resources.loadTexture("player.png");
|
||||
auto font = resources.loadFont("font.ttf", 24);
|
||||
auto sound = resources.loadSound("jump.wav");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 项目结构
|
||||
|
||||
```
|
||||
Extra2D/
|
||||
├── docs/ # 文档
|
||||
├── Extra2D/
|
||||
│ ├── include/extra2d/ # 头文件
|
||||
│ ├── src/ # 源文件
|
||||
│ └── examples/ # 示例程序
|
||||
├── squirrel/ # Squirrel 脚本引擎
|
||||
├── xmake/ # xmake 配置
|
||||
│ ├── toolchains/ # 工具链定义
|
||||
│ └── targets/ # 构建目标
|
||||
├── xmake.lua # 主构建配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关链接
|
||||
|
||||
- **GitHub**: https://github.com/ChestnutYueyue/extra2d
|
||||
- **Issues**: https://github.com/ChestnutYueyue/extra2d/issues
|
||||
- **devkitPro**: https://devkitpro.org/
|
||||
- **Switch 开发**: https://switchbrew.org/
|
||||
|
||||
---
|
||||
|
||||
## 📝 许可证
|
||||
|
||||
Extra2D 使用 [MIT 许可证](../LICENSE)。
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026年2月10日
|
||||
**版本**: 3.1.0
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
# Easy2D PC 端编译指南
|
||||
|
||||
本文档说明如何在 PC 端(Windows/Linux/macOS)编译和运行 Easy2D 引擎。
|
||||
|
||||
## 概述
|
||||
|
||||
Easy2D 现在支持多平台:
|
||||
- **Nintendo Switch** (ARM64) - 原始平台
|
||||
- **Windows** (x64) - 新增
|
||||
- **Linux** (x64) - 新增
|
||||
- **macOS** (x64/ARM64) - 新增
|
||||
|
||||
PC 端使用 OpenGL ES 3.2(通过 Angle 或 Mesa)保持与 Switch 的代码兼容性。
|
||||
|
||||
## 前置条件
|
||||
|
||||
### Windows
|
||||
|
||||
1. **Visual Studio 2019/2022** - 安装 C++ 桌面开发工作负载
|
||||
2. **xmake** - 构建系统 (https://xmake.io)
|
||||
3. **SDL2** - 可以通过以下方式安装:
|
||||
- vcpkg: `vcpkg install sdl2 sdl2-mixer`
|
||||
- 手动下载:https://www.libsdl.org/download-2.0.php
|
||||
4. **Angle** (可选) - Google 的 GLES 实现
|
||||
- 下载 Angle 二进制文件或从 Chromium 构建
|
||||
- 放置到 `third_party/angle/` 目录
|
||||
|
||||
### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
sudo apt-get install -y libsdl2-dev libsdl2-mixer-dev
|
||||
sudo apt-get install -y libgles2-mesa-dev libegl1-mesa-dev
|
||||
|
||||
# 安装 xmake
|
||||
sudo add-apt-repository ppa:xmake-io/xmake
|
||||
sudo apt-get update
|
||||
sudo apt-get install xmake
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
```bash
|
||||
# 安装 Homebrew (如果还没有)
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# 安装依赖
|
||||
brew install sdl2 sdl2_mixer
|
||||
brew install angle # 或 mesa
|
||||
|
||||
# 安装 xmake
|
||||
brew install xmake
|
||||
```
|
||||
|
||||
## 编译步骤
|
||||
|
||||
### 1. 配置项目
|
||||
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd c:\Users\soulcoco\Desktop\Easy2D\Extra2D
|
||||
|
||||
# 配置 PC 平台构建
|
||||
xmake config -p pc -m release
|
||||
|
||||
# 或使用环境变量
|
||||
set E2D_PLATFORM=pc # Windows
|
||||
export E2D_PLATFORM=pc # Linux/macOS
|
||||
xmake config
|
||||
```
|
||||
|
||||
### 2. 编译引擎库
|
||||
|
||||
```bash
|
||||
# 编译 Easy2D 静态库
|
||||
xmake build extra2d
|
||||
```
|
||||
|
||||
### 3. 编译示例程序
|
||||
|
||||
```bash
|
||||
# 编译 Hello World 示例
|
||||
xmake build hello_world
|
||||
|
||||
# 编译所有示例
|
||||
xmake build -a
|
||||
```
|
||||
|
||||
### 4. 运行示例
|
||||
|
||||
```bash
|
||||
# 运行 Hello World
|
||||
xmake run hello_world
|
||||
|
||||
# 或手动运行
|
||||
./build/windows/hello_world.exe # Windows
|
||||
./build/linux/hello_world # Linux
|
||||
./build/macos/hello_world # macOS
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
Extra2D/
|
||||
├── xmake.lua # 主构建配置
|
||||
├── xmake/
|
||||
│ ├── toolchains/
|
||||
│ │ ├── switch.lua # Switch 工具链
|
||||
│ │ └── pc.lua # PC 工具链 (新增)
|
||||
│ └── targets/
|
||||
│ ├── extra2d.lua # 引擎库配置
|
||||
│ └── examples.lua # 示例程序配置
|
||||
├── Extra2D/
|
||||
│ ├── include/
|
||||
│ │ └── extra2d/
|
||||
│ │ └── platform/
|
||||
│ │ ├── platform_compat.h # 跨平台兼容性 (新增)
|
||||
│ │ ├── file_system.h # 文件系统工具 (新增)
|
||||
│ │ ├── window.h # 窗口系统 (修改)
|
||||
│ │ └── input.h # 输入系统 (修改)
|
||||
│ └── src/
|
||||
│ └── platform/
|
||||
│ ├── window.cpp # (修改)
|
||||
│ ├── input.cpp # (修改)
|
||||
│ └── file_system.cpp # (新增)
|
||||
└── examples/
|
||||
└── hello_world/
|
||||
└── main.cpp # (修改,支持多平台)
|
||||
```
|
||||
|
||||
## 平台差异
|
||||
|
||||
### 窗口系统
|
||||
|
||||
| 功能 | Switch | PC |
|
||||
|------|--------|-----|
|
||||
| 窗口模式 | 始终全屏 (1280x720) | 可配置全屏/窗口 |
|
||||
| 窗口大小 | 固定 | 可调整 |
|
||||
| DPI 缩放 | 1.0 | 自动检测 |
|
||||
| 鼠标光标 | 无 | 支持多种形状 |
|
||||
|
||||
### 输入系统
|
||||
|
||||
| 功能 | Switch | PC |
|
||||
|------|--------|-----|
|
||||
| 键盘 | 映射到手柄 | 原生支持 |
|
||||
| 鼠标 | 映射到触摸 | 原生支持 |
|
||||
| 手柄 | SDL GameController | SDL GameController |
|
||||
| 触摸屏 | 原生 | 可选支持 |
|
||||
|
||||
### 文件系统
|
||||
|
||||
| 功能 | Switch | PC |
|
||||
|------|--------|-----|
|
||||
| 资源路径 | `romfs:/assets/` | `./assets/` 或 exe 目录 |
|
||||
| 文件访问 | RomFS | 标准文件系统 |
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 编写跨平台代码
|
||||
|
||||
```cpp
|
||||
#include <extra2d/platform/platform_compat.h>
|
||||
#include <extra2d/platform/file_system.h>
|
||||
|
||||
// 检查平台
|
||||
if (platform::isSwitch()) {
|
||||
// Switch 特定代码
|
||||
} else if (platform::isPC()) {
|
||||
// PC 特定代码
|
||||
}
|
||||
|
||||
// 解析资源路径(自动处理平台差异)
|
||||
std::string fontPath = FileSystem::resolvePath("assets/font.ttf");
|
||||
// Switch: "romfs:/assets/font.ttf"
|
||||
// PC: "./assets/font.ttf"
|
||||
|
||||
// 条件编译
|
||||
#ifdef PLATFORM_SWITCH
|
||||
// Switch 代码
|
||||
#elif defined(PLATFORM_PC)
|
||||
// PC 代码
|
||||
#endif
|
||||
```
|
||||
|
||||
### 输入处理
|
||||
|
||||
```cpp
|
||||
// 跨平台输入(推荐)
|
||||
auto& input = Application::instance().input();
|
||||
|
||||
// 键盘(PC 原生,Switch 映射到手柄)
|
||||
if (input.isKeyPressed(Key::Space)) {
|
||||
// 处理空格键
|
||||
}
|
||||
|
||||
// 手柄(所有平台)
|
||||
if (input.isButtonPressed(SDL_CONTROLLER_BUTTON_A)) {
|
||||
// 处理 A 按钮
|
||||
}
|
||||
|
||||
// 鼠标(PC 原生,Switch 映射到触摸)
|
||||
if (input.isMouseDown(MouseButton::Left)) {
|
||||
// 处理鼠标左键
|
||||
}
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 找不到 SDL2
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
# 设置 SDL2 路径
|
||||
set SDL2_DIR=C:\SDL2-2.28.0
|
||||
xmake config
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
# 安装 SDL2 开发库
|
||||
sudo apt-get install libsdl2-dev libsdl2-mixer-dev
|
||||
```
|
||||
|
||||
### 找不到 GLES
|
||||
|
||||
**Windows (Angle):**
|
||||
```bash
|
||||
# 下载 Angle 并设置路径
|
||||
set ANGLE_DIR=C:\angle
|
||||
xmake config
|
||||
```
|
||||
|
||||
**Linux (Mesa):**
|
||||
```bash
|
||||
# 安装 Mesa GLES
|
||||
sudo apt-get install libgles2-mesa-dev libegl1-mesa-dev
|
||||
```
|
||||
|
||||
### 编译错误
|
||||
|
||||
1. 确保使用 C++17 或更高版本
|
||||
2. 检查 xmake 版本是否最新:`xmake --version`
|
||||
3. 清理构建缓存:`xmake clean -a`
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 尝试编译和运行 `hello_world` 示例
|
||||
2. 阅读 `platform_compat.h` 了解平台 API
|
||||
3. 使用 `FileSystem` 类处理跨平台文件路径
|
||||
4. 参考示例代码编写跨平台应用
|
||||
|
||||
## 许可证
|
||||
|
||||
Easy2D 采用 MIT 许可证。详见 LICENSE 文件。
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026年2月10日
|
||||
**Easy2D 版本**: 3.1.0
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
*link:
|
||||
+ -T C:/devkitPro/libnx/switch.ld -pie --no-dynamic-linker --spare-dynamic-tags=0 --gc-sections -z text -z now -z nodynamic-undefined-weak -z pack-relative-relocs --build-id=sha1 --nx-module-name
|
||||
|
||||
*startfile:
|
||||
crti%O%s crtbegin%O%s --require-defined=main
|
||||
63
xmake.lua
63
xmake.lua
|
|
@ -1,8 +1,8 @@
|
|||
-- ==============================================
|
||||
-- Extra2D for Nintendo Switch - Xmake Build Script
|
||||
-- Purpose: Build Extra2D static library and Switch demo programs
|
||||
-- Platform: Nintendo Switch (ARM64)
|
||||
-- Graphics: Desktop OpenGL 3.3+ via Mesa EGL
|
||||
-- Extra2D - Xmake Build Script
|
||||
-- Purpose: Build Extra2D static library and demo programs
|
||||
-- Platform: Nintendo Switch (ARM64) / PC (Windows/Linux/macOS)
|
||||
-- Graphics: OpenGL ES 3.2 via Mesa/Angle
|
||||
-- Audio: SDL2_mixer
|
||||
-- ==============================================
|
||||
|
||||
|
|
@ -12,15 +12,66 @@ set_languages("c++17")
|
|||
set_encodings("utf-8")
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
-- ==============================================
|
||||
-- 平台检测与配置 (必须在 includes 之前)
|
||||
-- ==============================================
|
||||
|
||||
-- 检测目标平台 - 优先级:命令行 > 环境变量 > 默认值
|
||||
local target_platform = "switch" -- 默认 Switch 平台
|
||||
|
||||
-- 方式1: 检查命令行传入的平台配置 (最高优先级)
|
||||
local plat_config = get_config("plat")
|
||||
if plat_config and plat_config ~= "" then
|
||||
if plat_config == "windows" or plat_config == "linux" or plat_config == "macosx" then
|
||||
target_platform = "pc"
|
||||
elseif plat_config == "switch" then
|
||||
target_platform = "switch"
|
||||
end
|
||||
-- 方式2: 通过环境变量检测 PC 平台
|
||||
elseif os.getenv("E2D_PLATFORM") == "pc" then
|
||||
target_platform = "pc"
|
||||
-- 方式3: 检查 platform 配置
|
||||
elseif has_config("platform") then
|
||||
local platform_val = get_config("platform")
|
||||
if platform_val == "pc" then
|
||||
target_platform = "pc"
|
||||
end
|
||||
end
|
||||
|
||||
-- 设置默认平台和架构 (必须在 includes 之前调用)
|
||||
if target_platform == "switch" then
|
||||
set_config("plat", "switch")
|
||||
set_config("arch", "arm64")
|
||||
else
|
||||
-- PC 平台:根据主机自动选择
|
||||
if is_host("windows") then
|
||||
set_config("plat", "windows")
|
||||
set_config("arch", "x64")
|
||||
elseif is_host("linux") then
|
||||
set_config("plat", "linux")
|
||||
elseif is_host("macosx") then
|
||||
set_config("plat", "macosx")
|
||||
end
|
||||
end
|
||||
|
||||
print("Extra2D Build Configuration:")
|
||||
print(" Platform: " .. target_platform)
|
||||
print(" Mode: " .. (is_mode("debug") and "debug" or "release"))
|
||||
|
||||
-- ==============================================
|
||||
-- 包含子模块配置
|
||||
-- ==============================================
|
||||
|
||||
-- 包含工具链定义
|
||||
includes("xmake/toolchains/switch.lua")
|
||||
includes("xmake/toolchains/pc.lua")
|
||||
|
||||
-- 定义 Switch 工具链
|
||||
define_switch_toolchain()
|
||||
-- 根据平台定义工具链
|
||||
if target_platform == "switch" then
|
||||
define_switch_toolchain()
|
||||
else
|
||||
define_pc_toolchain()
|
||||
end
|
||||
|
||||
-- 包含目标定义
|
||||
includes("xmake/targets/extra2d.lua")
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
-- ==============================================
|
||||
-- Extra2D for Nintendo Switch - Xmake Build Script
|
||||
-- Purpose: Build Extra2D static library and Switch demo programs
|
||||
-- Platform: Nintendo Switch (ARM64)
|
||||
-- Graphics: Desktop OpenGL 3.3+ via Mesa EGL
|
||||
-- Audio: SDL2_mixer
|
||||
-- ==============================================
|
||||
|
||||
set_project("Extra2D")
|
||||
set_version("3.1.0")
|
||||
set_languages("c++17")
|
||||
set_encodings("utf-8")
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
-- ==============================================
|
||||
-- 包含子模块配置
|
||||
-- ==============================================
|
||||
|
||||
-- 包含工具链定义
|
||||
includes("xmake/toolchains/switch.lua")
|
||||
|
||||
-- 定义 Switch 工具链
|
||||
define_switch_toolchain()
|
||||
|
||||
-- 包含目标定义
|
||||
includes("xmake/targets/extra2d.lua")
|
||||
includes("xmake/targets/examples.lua")
|
||||
|
||||
-- ==============================================
|
||||
-- 定义构建目标
|
||||
-- ==============================================
|
||||
|
||||
-- Extra2D 引擎库
|
||||
define_extra2d_target()
|
||||
|
||||
-- 示例程序
|
||||
define_example_targets()
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
-- ==============================================
|
||||
-- Extra2D 示例程序构建目标
|
||||
-- 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||
-- ==============================================
|
||||
|
||||
-- 获取 devkitPro 路径
|
||||
|
|
@ -49,10 +50,10 @@ local function generate_nro_after_build(target_name, app_title, app_author, app_
|
|||
end)
|
||||
end
|
||||
|
||||
-- 定义示例程序的通用配置
|
||||
-- 定义 Switch 示例程序的通用配置
|
||||
-- @param name 目标名称
|
||||
-- @param options 配置选项表
|
||||
local function define_example_target(name, options)
|
||||
local function define_switch_example_target(name, options)
|
||||
target(name)
|
||||
set_kind("binary")
|
||||
set_plat("switch")
|
||||
|
|
@ -69,6 +70,11 @@ local function define_example_target(name, options)
|
|||
-- 链接 extra2d 库
|
||||
add_deps("extra2d")
|
||||
|
||||
-- Windows 控制台应用程序(仅 PC 平台)
|
||||
if is_plat("windows") then
|
||||
add_ldflags("-mconsole", {force = true})
|
||||
end
|
||||
|
||||
-- 可选:添加链接器标志
|
||||
if options.ldflags then
|
||||
add_ldflags(options.ldflags, {force = true})
|
||||
|
|
@ -85,31 +91,121 @@ local function define_example_target(name, options)
|
|||
target_end()
|
||||
end
|
||||
|
||||
-- 定义 PC 示例程序的通用配置
|
||||
-- @param name 目标名称
|
||||
-- @param options 配置选项表
|
||||
local function define_pc_example_target(name, options)
|
||||
target(name)
|
||||
set_kind("binary")
|
||||
set_toolchains("pc")
|
||||
|
||||
-- 设置输出目录
|
||||
if is_host("windows") then
|
||||
set_targetdir("build/windows")
|
||||
elseif is_host("linux") then
|
||||
set_targetdir("build/linux")
|
||||
elseif is_host("macosx") then
|
||||
set_targetdir("build/macos")
|
||||
else
|
||||
set_targetdir("build/pc")
|
||||
end
|
||||
|
||||
-- 添加源文件
|
||||
add_files(options.source_file or ("Extra2D/examples/" .. name .. "/main.cpp"))
|
||||
|
||||
-- 添加头文件路径
|
||||
add_includedirs("Extra2D/include")
|
||||
|
||||
-- 链接 extra2d 库
|
||||
add_deps("extra2d")
|
||||
|
||||
-- 可选:添加链接器标志
|
||||
if options.ldflags then
|
||||
add_ldflags(options.ldflags, {force = true})
|
||||
end
|
||||
|
||||
-- PC 端构建后复制资源文件和 DLL
|
||||
after_build(function (target)
|
||||
local target_file = target:targetfile()
|
||||
local output_dir = path.directory(target_file)
|
||||
local romfs_dir = options.romfs_dir or ("Extra2D/examples/" .. name .. "/romfs")
|
||||
local romfs_absolute = path.absolute(romfs_dir)
|
||||
|
||||
-- 复制 vcpkg DLL 到输出目录
|
||||
local vcpkg_root = os.getenv("VCPKG_ROOT")
|
||||
if vcpkg_root then
|
||||
local triplet = is_arch("x64") and "x64-windows" or "x86-windows"
|
||||
local vcpkg_bin = path.join(vcpkg_root, "installed", triplet, "bin")
|
||||
if os.isdir(vcpkg_bin) then
|
||||
-- 复制 SDL2 相关 DLL
|
||||
local dlls = {"SDL2.dll", "SDL2_mixer.dll", "ogg.dll", "vorbis.dll", "vorbisfile.dll", "wavpackdll.dll"}
|
||||
for _, dll in ipairs(dlls) do
|
||||
local dll_path = path.join(vcpkg_bin, dll)
|
||||
if os.isfile(dll_path) then
|
||||
os.cp(dll_path, output_dir)
|
||||
end
|
||||
end
|
||||
print("Copied DLLs from: " .. vcpkg_bin)
|
||||
end
|
||||
end
|
||||
|
||||
-- 复制资源文件到输出目录
|
||||
if os.isdir(romfs_absolute) then
|
||||
local assets_dir = path.join(output_dir, "assets")
|
||||
os.mkdir(assets_dir)
|
||||
|
||||
-- 复制 romfs 内容到 assets 目录
|
||||
local romfs_assets = path.join(romfs_absolute, "assets")
|
||||
if os.isdir(romfs_assets) then
|
||||
print("Copying assets from: " .. romfs_assets .. " to " .. assets_dir)
|
||||
os.cp(romfs_assets .. "/*", assets_dir)
|
||||
end
|
||||
|
||||
print("Built " .. path.filename(target_file) .. " (with assets)")
|
||||
else
|
||||
print("Built " .. path.filename(target_file))
|
||||
end
|
||||
end)
|
||||
target_end()
|
||||
end
|
||||
|
||||
-- 定义所有示例程序目标
|
||||
function define_example_targets()
|
||||
-- 根据平台选择示例程序定义方式
|
||||
if is_plat("switch") then
|
||||
-- ============================================
|
||||
-- Switch 简单测试程序
|
||||
-- Switch 示例程序
|
||||
-- ============================================
|
||||
define_example_target("hello_world", {
|
||||
define_switch_example_target("hello_world", {
|
||||
app_title = "Extra2D hello_world",
|
||||
app_author = "Extra2D hello_world",
|
||||
app_version = "1.0.0"
|
||||
})
|
||||
|
||||
-- ============================================
|
||||
-- 引擎空间索引演示(1000个节点)
|
||||
-- ============================================
|
||||
define_example_target("spatial_index_demo", {
|
||||
define_switch_example_target("spatial_index_demo", {
|
||||
app_title = "Extra2D Spatial Index Demo",
|
||||
app_version = "1.0.0",
|
||||
ldflags = "-Wl,-Map=build/switch/spatial_index_demo.map"
|
||||
})
|
||||
|
||||
-- ============================================
|
||||
-- 碰撞检测演示程序
|
||||
-- ============================================
|
||||
define_example_target("collision_demo", {
|
||||
define_switch_example_target("collision_demo", {
|
||||
app_title = "Extra2D Collision Demo",
|
||||
app_version = "1.0.0"
|
||||
})
|
||||
else
|
||||
-- ============================================
|
||||
-- PC 示例程序 (Windows/Linux/macOS)
|
||||
-- ============================================
|
||||
define_pc_example_target("hello_world", {
|
||||
romfs_dir = "Extra2D/examples/hello_world/romfs"
|
||||
})
|
||||
|
||||
define_pc_example_target("spatial_index_demo", {
|
||||
romfs_dir = "Extra2D/examples/spatial_index_demo/romfs"
|
||||
})
|
||||
|
||||
define_pc_example_target("collision_demo", {
|
||||
romfs_dir = "Extra2D/examples/collision_demo/romfs"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
-- ==============================================
|
||||
-- Extra2D 引擎库构建目标
|
||||
-- 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||
-- ==============================================
|
||||
|
||||
-- 核心路径定义
|
||||
|
|
@ -10,9 +11,6 @@ local INC_DIR = "Extra2D/include"
|
|||
function define_extra2d_target()
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
set_plat("switch")
|
||||
set_arch("arm64")
|
||||
set_toolchains("switch")
|
||||
set_basename(is_mode("debug") and "libeasy2dd" or "libeasy2d")
|
||||
|
||||
-- ==============================================
|
||||
|
|
@ -37,21 +35,28 @@ function define_extra2d_target()
|
|||
-- 第三方头文件目录
|
||||
add_includedirs("squirrel/include", {public = true})
|
||||
|
||||
-- 平台兼容性头文件路径
|
||||
add_includedirs(path.join(INC_DIR, "extra2d/platform"), {public = true})
|
||||
|
||||
-- ==============================================
|
||||
-- 平台特定配置
|
||||
-- ==============================================
|
||||
|
||||
if is_plat("switch") then
|
||||
-- ==============================================
|
||||
-- Nintendo Switch 平台配置
|
||||
-- ==============================================
|
||||
set_plat("switch")
|
||||
set_arch("arm64")
|
||||
set_toolchains("switch")
|
||||
|
||||
-- devkitPro mesa 路径(EGL + 桌面 OpenGL)
|
||||
-- devkitPro mesa 路径(EGL + OpenGL ES)
|
||||
local devkitPro = "C:/devkitPro"
|
||||
add_includedirs(path.join(devkitPro, "portlibs/switch/include"), {public = true})
|
||||
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
|
||||
|
||||
-- 使用系统 GLES3.2 头文件 (位于 devkitPro/portlibs/switch/include)
|
||||
|
||||
-- 链接 EGL、OpenGL ES 3.0(mesa)和 SDL2 音频
|
||||
-- 注意:链接顺序很重要!被依赖的库必须放在后面
|
||||
-- 依赖链:SDL2 -> EGL -> drm_nouveau
|
||||
-- GLESv2 -> glapi -> drm_nouveau
|
||||
add_syslinks("SDL2_mixer", "SDL2",
|
||||
"opusfile", "opus", "vorbisidec", "ogg",
|
||||
"modplug", "mpg123", "FLAC",
|
||||
|
|
@ -60,41 +65,31 @@ function define_extra2d_target()
|
|||
"glapi",
|
||||
"drm_nouveau",
|
||||
{public = true})
|
||||
else
|
||||
-- ==============================================
|
||||
-- PC 平台配置 (Windows/Linux/macOS)
|
||||
-- ==============================================
|
||||
set_toolchains("pc")
|
||||
|
||||
-- 注意:pfd (portable-file-dialogs) 暂时禁用,需要进一步修复
|
||||
-- add_files(path.join(INC_DIR, "pfd/pfd_switch.cpp"))
|
||||
|
||||
-- 添加 Switch 兼容性头文件路径
|
||||
add_includedirs(path.join(INC_DIR, "extra2d/platform"), {public = true})
|
||||
-- PC 平台使用标准 OpenGL (通过 MinGW)
|
||||
-- 依赖库在 pc.lua 工具链中配置 (使用 vcpkg)
|
||||
add_syslinks("SDL2_mixer", "SDL2", "opengl32", {public = true})
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 编译器配置
|
||||
-- ==============================================
|
||||
|
||||
-- Switch 特定编译标志
|
||||
-- 注意:Squirrel 脚本绑定使用 dynamic_cast,需要 RTTI 支持
|
||||
-- add_cxflags("-fno-rtti", {force = true})
|
||||
-- ==============================================
|
||||
-- 编译器标志 (MinGW GCC)
|
||||
-- ==============================================
|
||||
add_cxflags("-Wall", "-Wextra", {force = true})
|
||||
add_cxflags("-Wno-unused-variable", "-Wno-unused-function", {force = true})
|
||||
add_cxflags("-Wno-unused-parameter", {force = true})
|
||||
|
||||
-- Squirrel 第三方库警告抑制
|
||||
add_cxflags("-Wno-deprecated-copy", "-Wno-strict-aliasing", "-Wno-implicit-fallthrough", "-Wno-class-memaccess", {force = true})
|
||||
|
||||
-- 使用 switch 工具链
|
||||
set_toolchains("switch")
|
||||
|
||||
-- ==============================================
|
||||
-- 头文件安装配置
|
||||
-- ==============================================
|
||||
add_headerfiles(path.join(INC_DIR, "extra2d/**.h"), {prefixdir = "extra2d"})
|
||||
-- 使用 devkitPro 的 switch-glm 替代项目自带的 GLM
|
||||
-- add_headerfiles(path.join(INC_DIR, "glm/**.hpp"), {prefixdir = "glm"})
|
||||
add_headerfiles(path.join(INC_DIR, "stb/**.h"), {prefixdir = "stb"})
|
||||
add_headerfiles(path.join(INC_DIR, "simpleini/**.h"), {prefixdir = "simpleini"})
|
||||
|
||||
-- 编译器通用配置
|
||||
add_cxxflags("-Wall", "-Wextra", {force = true})
|
||||
add_cxxflags("-Wno-unused-parameter", {force = true})
|
||||
|
||||
-- 调试/发布模式配置
|
||||
if is_mode("debug") then
|
||||
add_defines("E2D_DEBUG", "_DEBUG", {public = true})
|
||||
|
|
@ -103,5 +98,12 @@ function define_extra2d_target()
|
|||
add_defines("NDEBUG", {public = true})
|
||||
add_cxxflags("-O2", {force = true})
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 头文件安装配置
|
||||
-- ==============================================
|
||||
add_headerfiles(path.join(INC_DIR, "extra2d/**.h"), {prefixdir = "extra2d"})
|
||||
add_headerfiles(path.join(INC_DIR, "stb/**.h"), {prefixdir = "stb"})
|
||||
add_headerfiles(path.join(INC_DIR, "simpleini/**.h"), {prefixdir = "simpleini"})
|
||||
target_end()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
-- ==============================================
|
||||
-- PC 平台工具链定义 (Windows/Linux/macOS)
|
||||
-- ==============================================
|
||||
|
||||
function define_pc_toolchain()
|
||||
toolchain("pc")
|
||||
set_kind("standalone")
|
||||
set_description("PC Platform Toolchain (Windows/Linux/macOS)")
|
||||
|
||||
on_load(function (toolchain)
|
||||
-- ==============================================
|
||||
-- 平台检测与配置
|
||||
-- ==============================================
|
||||
|
||||
if is_host("windows") then
|
||||
-- Windows: 使用 MinGW
|
||||
toolchain:set("toolset", "cc", "gcc")
|
||||
toolchain:set("toolset", "cxx", "g++")
|
||||
toolchain:set("toolset", "ld", "g++")
|
||||
toolchain:set("toolset", "ar", "ar")
|
||||
else
|
||||
-- Linux/macOS: 使用 GCC/Clang
|
||||
toolchain:set("toolset", "cc", "gcc")
|
||||
toolchain:set("toolset", "cxx", "g++")
|
||||
toolchain:set("toolset", "ld", "g++")
|
||||
toolchain:set("toolset", "ar", "ar")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- PC 平台宏定义
|
||||
-- ==============================================
|
||||
toolchain:add("defines", "__PC__")
|
||||
|
||||
if is_host("windows") then
|
||||
toolchain:add("defines", "_WIN32", "NOMINMAX", "WIN32_LEAN_AND_MEAN")
|
||||
elseif is_host("linux") then
|
||||
toolchain:add("defines", "__linux__")
|
||||
elseif is_host("macosx") then
|
||||
toolchain:add("defines", "__APPLE__", "__MACH__")
|
||||
end
|
||||
|
||||
-- SimpleIni 配置
|
||||
toolchain:add("defines", "SI_NO_CONVERSION")
|
||||
|
||||
-- ==============================================
|
||||
-- OpenGL 配置
|
||||
-- ==============================================
|
||||
|
||||
if is_host("windows") then
|
||||
-- Windows: 使用标准 OpenGL
|
||||
toolchain:add("links", "opengl32")
|
||||
else
|
||||
-- Linux/macOS: 使用 Mesa OpenGL
|
||||
toolchain:add("links", "GL")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- vcpkg 依赖配置
|
||||
-- ==============================================
|
||||
|
||||
-- 获取 vcpkg 路径
|
||||
local vcpkg_root = os.getenv("VCPKG_ROOT")
|
||||
if vcpkg_root then
|
||||
local triplet = is_arch("x64") and "x64-windows" or "x86-windows"
|
||||
local vcpkg_installed = path.join(vcpkg_root, "installed", triplet)
|
||||
if os.isdir(vcpkg_installed) then
|
||||
-- 添加头文件路径
|
||||
toolchain:add("includedirs", path.join(vcpkg_installed, "include"))
|
||||
toolchain:add("includedirs", path.join(vcpkg_installed, "include", "SDL2"))
|
||||
-- 添加库路径
|
||||
toolchain:add("linkdirs", path.join(vcpkg_installed, "lib"))
|
||||
print("vcpkg packages: " .. vcpkg_installed)
|
||||
end
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 链接库
|
||||
-- ==============================================
|
||||
|
||||
-- SDL2 及其扩展
|
||||
toolchain:add("links", "SDL2_mixer", "SDL2")
|
||||
|
||||
-- 音频编解码库 (vcpkg 中可用的)
|
||||
toolchain:add("links", "ogg")
|
||||
|
||||
-- OpenGL (Windows 使用标准 OpenGL)
|
||||
toolchain:add("links", "opengl32")
|
||||
|
||||
-- 系统库
|
||||
if is_host("windows") then
|
||||
toolchain:add("syslinks", "gdi32", "user32", "shell32", "winmm", "imm32", "ole32", "oleaut32", "version", "uuid", "advapi32", "setupapi")
|
||||
else
|
||||
toolchain:add("syslinks", "m", "dl", "pthread")
|
||||
if is_host("linux") then
|
||||
toolchain:add("syslinks", "X11", "Xext")
|
||||
end
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
-- 编译器标志 (MinGW GCC)
|
||||
-- ==============================================
|
||||
toolchain:add("cxflags", "-Wall", "-Wextra", {force = true})
|
||||
toolchain:add("cxflags", "-Wno-unused-parameter", "-Wno-unused-variable", {force = true})
|
||||
|
||||
-- Windows 控制台应用程序
|
||||
toolchain:add("ldflags", "-mconsole", {force = true})
|
||||
|
||||
if is_mode("debug") then
|
||||
toolchain:add("defines", "E2D_DEBUG", "_DEBUG")
|
||||
toolchain:add("cxflags", "-O0", "-g", {force = true})
|
||||
else
|
||||
toolchain:add("defines", "NDEBUG")
|
||||
toolchain:add("cxflags", "-O2", {force = true})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- 获取 PC 平台包含路径
|
||||
function get_pc_includedirs()
|
||||
local dirs = {}
|
||||
|
||||
-- Angle
|
||||
local angle_dir = os.getenv("ANGLE_DIR") or "third_party/angle"
|
||||
if os.isdir(angle_dir) then
|
||||
table.insert(dirs, path.join(angle_dir, "include"))
|
||||
end
|
||||
|
||||
return dirs
|
||||
end
|
||||
|
||||
-- 获取 PC 平台库路径
|
||||
function get_pc_linkdirs()
|
||||
local dirs = {}
|
||||
|
||||
-- Angle
|
||||
local angle_dir = os.getenv("ANGLE_DIR") or "third_party/angle"
|
||||
if os.isdir(angle_dir) then
|
||||
table.insert(dirs, path.join(angle_dir, "lib"))
|
||||
end
|
||||
|
||||
return dirs
|
||||
end
|
||||
|
||||
-- 获取 PC 平台系统链接库
|
||||
function get_pc_syslinks()
|
||||
return {
|
||||
"SDL2_mixer", "SDL2",
|
||||
"opengl32"
|
||||
}
|
||||
end
|
||||
|
|
@ -21,8 +21,8 @@ function define_switch_toolchain()
|
|||
-- 架构标志
|
||||
local arch_flags = "-march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE"
|
||||
add_cxflags(arch_flags)
|
||||
-- 使用修复后的 switch_fix.specs 文件(使用 Windows 路径)
|
||||
add_ldflags("-specs=switch_fix.specs", "-g", arch_flags)
|
||||
-- 使用 devkitPro 提供的 switch.specs 文件
|
||||
add_ldflags("-specs=" .. path.join(devkitPro, "libnx/switch.specs"), "-g", arch_flags)
|
||||
|
||||
-- 定义 Switch 平台宏
|
||||
add_defines("__SWITCH__", "__NX__", "MA_SWITCH", "PFD_SWITCH")
|
||||
|
|
|
|||
Loading…
Reference in New Issue