From 2e08bff567b51dc351a878c17bd1cb5f279034cc Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Tue, 10 Feb 2026 05:15:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B9=B3=E5=8F=B0=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7):=20=E6=B7=BB=E5=8A=A0=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B9=B6=E9=87=8D=E6=9E=84=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(构建系统): 重构xmake配置以支持多平台构建 feat(文件系统): 新增FileSystem类处理跨平台路径 refactor(窗口系统): 重构Window类支持PC平台特性 feat(输入系统): 扩展Input类支持PC输入设备 docs: 添加PC构建指南和Switch构建指南文档 style: 统一平台相关代码风格和命名规范 chore: 删除过时的文档和配置文件 --- .trae/documents/DataStore 改进计划.md | 80 -- .../Extra2D 数据结构与算法优化计划.md | 105 -- .trae/documents/Node 节点优化计划.md | 62 -- Extra2D/examples/collision_demo/main.cpp | 11 +- Extra2D/examples/hello_world/main.cpp | 45 +- Extra2D/examples/spatial_index_demo/main.cpp | 15 +- Extra2D/include/extra2d/event/input_codes.h | 304 +++--- Extra2D/include/extra2d/extra2d.h | 1 + .../extra2d/graphics/opengl/gl_texture.h | 1 + .../include/extra2d/platform/file_system.h | 166 +++ Extra2D/include/extra2d/platform/input.h | 50 +- .../extra2d/platform/platform_compat.h | 263 +++++ .../include/extra2d/platform/switch_compat.h | 78 +- Extra2D/include/extra2d/platform/window.h | 44 +- Extra2D/src/app/application.cpp | 45 +- Extra2D/src/platform/file_system.cpp | 315 ++++++ Extra2D/src/platform/input.cpp | 239 ++++- Extra2D/src/platform/window.cpp | 266 ++++- docs/API_REFERENCE.md | 959 ++++++++++++++++++ docs/INDEX.md | 225 ++++ docs/PC_BUILD_GUIDE.md | 262 +++++ .../SWITCH_BUILD_GUIDE.md | 0 .../SWITCH_MIGRATION_COMPLETE.md | 0 switch_fix.specs | 5 - xmake.lua | 63 +- xmake.lua.backup | 37 - xmake/targets/examples.lua | 146 ++- xmake/targets/extra2d.lua | 96 +- xmake/toolchains/pc.lua | 150 +++ xmake/toolchains/switch.lua | 4 +- 30 files changed, 3344 insertions(+), 693 deletions(-) delete mode 100644 .trae/documents/DataStore 改进计划.md delete mode 100644 .trae/documents/Extra2D 数据结构与算法优化计划.md delete mode 100644 .trae/documents/Node 节点优化计划.md create mode 100644 Extra2D/include/extra2d/platform/file_system.h create mode 100644 Extra2D/include/extra2d/platform/platform_compat.h create mode 100644 Extra2D/src/platform/file_system.cpp create mode 100644 docs/API_REFERENCE.md create mode 100644 docs/INDEX.md create mode 100644 docs/PC_BUILD_GUIDE.md rename SWITCH_BUILD_GUIDE.md => docs/SWITCH_BUILD_GUIDE.md (100%) rename SWITCH_MIGRATION_COMPLETE.md => docs/SWITCH_MIGRATION_COMPLETE.md (100%) delete mode 100644 switch_fix.specs delete mode 100644 xmake.lua.backup create mode 100644 xmake/toolchains/pc.lua diff --git a/.trae/documents/DataStore 改进计划.md b/.trae/documents/DataStore 改进计划.md deleted file mode 100644 index 9b8e3fa..0000000 --- a/.trae/documents/DataStore 改进计划.md +++ /dev/null @@ -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) \ No newline at end of file diff --git a/.trae/documents/Extra2D 数据结构与算法优化计划.md b/.trae/documents/Extra2D 数据结构与算法优化计划.md deleted file mode 100644 index 5a7a2a8..0000000 --- a/.trae/documents/Extra2D 数据结构与算法优化计划.md +++ /dev/null @@ -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% | - ---- - -请确认此计划后,我将开始实施具体的代码修改。 \ No newline at end of file diff --git a/.trae/documents/Node 节点优化计划.md b/.trae/documents/Node 节点优化计划.md deleted file mode 100644 index b79f1d8..0000000 --- a/.trae/documents/Node 节点优化计划.md +++ /dev/null @@ -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> nameIndex_` - - 添加 `std::unordered_map> 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>` 替代 `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>&& children)` 批量添加 - - 优化 `removeAllChildren` 使用批量处理 -- **预期收益**: 减少多次操作的开销 - ---- - -**预计总工作量**: 4-6 小时 -**优先级**: 高(1.1, 1.2)> 中(2.1, 3.1)> 低(3.2, 4.1, 4.2) \ No newline at end of file diff --git a/Extra2D/examples/collision_demo/main.cpp b/Extra2D/examples/collision_demo/main.cpp index 8a1e74b..7d4bac0 100644 --- a/Extra2D/examples/collision_demo/main.cpp +++ b/Extra2D/examples/collision_demo/main.cpp @@ -124,9 +124,13 @@ private: auto &resources = Application::instance().resources(); // 使用后备字体加载功能 +#ifdef PLATFORM_SWITCH std::vector fontPaths = { "romfs:/assets/font.ttf" // 备选字体 }; +#else + std::vector 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; diff --git a/Extra2D/examples/hello_world/main.cpp b/Extra2D/examples/hello_world/main.cpp index fb60c31..be25d7e 100644 --- a/Extra2D/examples/hello_world/main.cpp +++ b/Extra2D/examples/hello_world/main.cpp @@ -1,5 +1,9 @@ #include +#include + +#ifdef PLATFORM_SWITCH #include +#endif using namespace extra2d; @@ -13,9 +17,9 @@ using namespace extra2d; */ static std::vector 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("========================"); // 获取应用实例 diff --git a/Extra2D/examples/spatial_index_demo/main.cpp b/Extra2D/examples/spatial_index_demo/main.cpp index 21e5f6e..b0fe530 100644 --- a/Extra2D/examples/spatial_index_demo/main.cpp +++ b/Extra2D/examples/spatial_index_demo/main.cpp @@ -185,11 +185,19 @@ private: void loadFonts() { auto &resources = Application::instance().resources(); +#ifdef PLATFORM_SWITCH std::vector fontPaths = { "romfs:/assets/msjh.ttf", "romfs:/assets/default.ttf", "romfs:/assets/font.ttf", }; +#else + std::vector 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; diff --git a/Extra2D/include/extra2d/event/input_codes.h b/Extra2D/include/extra2d/event/input_codes.h index 49a7381..eeb4f43 100644 --- a/Extra2D/include/extra2d/event/input_codes.h +++ b/Extra2D/include/extra2d/event/input_codes.h @@ -1,134 +1,134 @@ #pragma once +// SDL2 键码定义 +#include + 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 }; } diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index 1cf3117..4424b26 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -12,6 +12,7 @@ // Platform #include #include +#include // Graphics #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_texture.h b/Extra2D/include/extra2d/graphics/opengl/gl_texture.h index 195c0a8..8d559a7 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_texture.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_texture.h @@ -6,6 +6,7 @@ #include #include +#include namespace extra2d { diff --git a/Extra2D/include/extra2d/platform/file_system.h b/Extra2D/include/extra2d/platform/file_system.h new file mode 100644 index 0000000..2cc61a9 --- /dev/null +++ b/Extra2D/include/extra2d/platform/file_system.h @@ -0,0 +1,166 @@ +#pragma once + +/** + * @file file_system.h + * @brief 跨平台文件系统工具 + * + * 提供统一的文件路径处理和文件操作接口 + * 支持平台: Nintendo Switch, Windows, Linux, macOS + */ + +#include +#include +#include + +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 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 diff --git a/Extra2D/include/extra2d/platform/input.h b/Extra2D/include/extra2d/platform/input.h index 81106f1..e35952a 100644 --- a/Extra2D/include/extra2d/platform/input.h +++ b/Extra2D/include/extra2d/platform/input.h @@ -3,13 +3,15 @@ #include #include #include +#include +#include #include 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 keysDown_; + std::array prevKeysDown_; + // 手柄按钮状态 std::array buttonsDown_; std::array prevButtonsDown_; @@ -100,15 +108,35 @@ private: float rightStickX_; float rightStickY_; - // 触摸屏状态 + // 鼠标状态 (PC 端使用) + Vec2 mousePosition_; + Vec2 prevMousePosition_; + float mouseScroll_; + float prevMouseScroll_; + std::array mouseButtonsDown_; + std::array 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 diff --git a/Extra2D/include/extra2d/platform/platform_compat.h b/Extra2D/include/extra2d/platform/platform_compat.h new file mode 100644 index 0000000..6c3703f --- /dev/null +++ b/Extra2D/include/extra2d/platform/platform_compat.h @@ -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 +#include +#include +#include +#include +#include +#include + +// 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 +#include +#include +#include +#include +#include + +// 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 + #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 diff --git a/Extra2D/include/extra2d/platform/switch_compat.h b/Extra2D/include/extra2d/platform/switch_compat.h index 62f163d..2f559c3 100644 --- a/Extra2D/include/extra2d/platform/switch_compat.h +++ b/Extra2D/include/extra2d/platform/switch_compat.h @@ -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 -#include -#include -#include -#include -#include -#include - -// ============================================================================ -// 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__ diff --git a/Extra2D/include/extra2d/platform/window.h b/Extra2D/include/extra2d/platform/window.h index 620a0f9..e084e9b 100644 --- a/Extra2D/include/extra2d/platform/window.h +++ b/Extra2D/include/extra2d/platform/window.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -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(width_), static_cast(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; @@ -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_; @@ -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 diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index dbdacb5..d155e0b 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -13,18 +13,29 @@ #include #include -#include #include #include +#ifdef __SWITCH__ +#include +#endif + namespace extra2d { // 获取当前时间(秒) static double getTimeSeconds() { +#ifdef __SWITCH__ struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return static_cast(ts.tv_sec) + static_cast(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>(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(0, static_cast(window_->getWidth()), static_cast(window_->getHeight()), 0); - // 窗口大小回调(Switch 上不会触发,但保留接口) + // 窗口大小回调 window_->setResizeCallback([this](int width, int height) { if (camera_) { camera_->setViewport(0, static_cast(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() { diff --git a/Extra2D/src/platform/file_system.cpp b/Extra2D/src/platform/file_system.cpp new file mode 100644 index 0000000..edf4df9 --- /dev/null +++ b/Extra2D/src/platform/file_system.cpp @@ -0,0 +1,315 @@ +#include +#include + +#include +#include +#include +#include + +#ifdef PLATFORM_WINDOWS + #include + #include + #include + #pragma comment(lib, "shlwapi.lib") +#else + #include + #include + #include + #include + #include +#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 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 buffer(static_cast(size)); + file.read(reinterpret_cast(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(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 diff --git a/Extra2D/src/platform/input.cpp b/Extra2D/src/platform/input.cpp index ce87fb3..3b7f0c9 100644 --- a/Extra2D/src/platform/input.cpp +++ b/Extra2D/src/platform/input.cpp @@ -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(mouseX), static_cast(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(mouseX), static_cast(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(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTX)) / - 32767.0f; + controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f; leftStickY_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTY)) / - 32767.0f; + controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f; rightStickX_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / - 32767.0f; + controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f; rightStickY_ = static_cast(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(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(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(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(position.x), + static_cast(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 diff --git a/Extra2D/src/platform/window.cpp b/Extra2D/src/platform/window.cpp index 8f9c6db..3b1ebd9 100644 --- a/Extra2D/src/platform/window.cpp +++ b/Extra2D/src/platform/window.cpp @@ -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_->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, - width_, height_, windowFlags); + // 双缓冲 + 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(SDL_GL_GetProcAddress)) == - 0) { + // 加载 OpenGL ES 函数指针 + if (gladLoadGLES2Loader(reinterpret_cast(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(glGetString(GL_VERSION))); + E2D_LOG_INFO("OpenGL Renderer: {}", + reinterpret_cast(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(x), static_cast(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(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 diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..410f1b6 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -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 +using Ptr = std::shared_ptr; + +template +Ptr makePtr(Args&&... args) { + return std::make_shared(std::forward(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); + void enterScene(Ptr scene, Ptr transition); + + // 获取子系统 + Input& input(); + AudioEngine& audio(); + ResourceManager& resources(); + RenderBackend& renderer(); + + // 获取配置 + const AppConfig& getConfig() const; + + // 获取当前 FPS + float fps() const; + + // Switch 特定:检测是否连接底座 + bool isDocked() const; +}; +``` + +**使用示例:** + +```cpp +#include + +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()); + + // 运行主循环 + 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> queryCollisions(); + + // 获取空间管理器 + SpatialManager& getSpatialManager(); +}; +``` + +### Transition + +场景过渡动画基类。 + +```cpp +class Transition { +public: + explicit Transition(float duration); + + virtual void update(float dt) = 0; + virtual void render(RenderBackend& renderer, + Ptr fromScene, + Ptr 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 child); + void removeChild(Ptr child); + void removeFromParent(); + + // 可见性 + void setVisible(bool visible); + bool isVisible() const; + + // 动作 + void runAction(Ptr 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 create(Ptr texture); + static Ptr create(const std::string& texturePath); + + // 设置纹理 + void setTexture(Ptr texture); + Ptr 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 create(const std::string& text = ""); + + // 文本内容 + void setText(const std::string& text); + std::string getText() const; + + // 字体 + void setFont(Ptr 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 loadTexture(const std::string& path); + Ptr getTexture(const std::string& path); + void unloadTexture(const std::string& path); + + // 字体 + Ptr loadFont(const std::string& path, int size, bool useSDF = false); + Ptr loadFontWithFallbacks(const std::vector& paths, + int size, bool useSDF = false); + + // 音频 + Ptr loadSound(const std::string& path); + Ptr loadMusic(const std::string& path); + + // 精灵表 + Ptr 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 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 callback); +}; +``` + +### 组合动作 + +```cpp +// 顺序执行 +class Sequence : public Action { +public: + Sequence(std::vector> actions); + + template + static Ptr create(Args&&... args) { + return makePtr(std::vector>{std::forward(args)...}); + } +}; + +// 同时执行 +class Spawn : public Action { +public: + Spawn(std::vector> actions); +}; + +// 重复 +class Repeat : public Action { +public: + Repeat(Ptr action, int times = -1); // -1 = 无限重复 +}; + +// 反向 +class Reverse : public Action { +public: + Reverse(Ptr 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(1.0f, Vec2(300, 200))); + +// 组合动作 +sprite->runAction(makePtr( + makePtr(0.5f, Vec2(1.5f, 1.5f)), + makePtr(0.2f), + makePtr(0.5f, Vec2(1.0f, 1.0f)), + makePtr([]() { + E2D_LOG_INFO("动画完成!"); + }) +)); + +// 重复动画 +sprite->runAction(makePtr( + makePtr( + makePtr(1.0f, 360.0f), + makePtr(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 readFile(const std::string& path); + static std::string readTextFile(const std::string& path); + + // 写入文件 + static bool writeFile(const std::string& path, + const std::vector& 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 diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 0000000..68c0e48 --- /dev/null +++ b/docs/INDEX.md @@ -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 + +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(); + + // 添加精灵 + auto sprite = Sprite::create("player.png"); + scene->addChild(sprite); + + // 运行动画 + sprite->runAction(makePtr(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(); +auto transition = makePtr(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 diff --git a/docs/PC_BUILD_GUIDE.md b/docs/PC_BUILD_GUIDE.md new file mode 100644 index 0000000..20abe7c --- /dev/null +++ b/docs/PC_BUILD_GUIDE.md @@ -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 +#include + +// 检查平台 +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 diff --git a/SWITCH_BUILD_GUIDE.md b/docs/SWITCH_BUILD_GUIDE.md similarity index 100% rename from SWITCH_BUILD_GUIDE.md rename to docs/SWITCH_BUILD_GUIDE.md diff --git a/SWITCH_MIGRATION_COMPLETE.md b/docs/SWITCH_MIGRATION_COMPLETE.md similarity index 100% rename from SWITCH_MIGRATION_COMPLETE.md rename to docs/SWITCH_MIGRATION_COMPLETE.md diff --git a/switch_fix.specs b/switch_fix.specs deleted file mode 100644 index ee79a2f..0000000 --- a/switch_fix.specs +++ /dev/null @@ -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 diff --git a/xmake.lua b/xmake.lua index afbb03e..8684871 100644 --- a/xmake.lua +++ b/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") diff --git a/xmake.lua.backup b/xmake.lua.backup deleted file mode 100644 index afbb03e..0000000 --- a/xmake.lua.backup +++ /dev/null @@ -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() diff --git a/xmake/targets/examples.lua b/xmake/targets/examples.lua index b2e1d93..c8f8d1a 100644 --- a/xmake/targets/examples.lua +++ b/xmake/targets/examples.lua @@ -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() - -- ============================================ - -- Switch 简单测试程序 - -- ============================================ - define_example_target("hello_world", { - app_title = "Extra2D hello_world", - app_author = "Extra2D hello_world", - app_version = "1.0.0" - }) + -- 根据平台选择示例程序定义方式 + if is_plat("switch") then + -- ============================================ + -- Switch 示例程序 + -- ============================================ + 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", { - app_title = "Extra2D Spatial Index Demo", - app_version = "1.0.0", - ldflags = "-Wl,-Map=build/switch/spatial_index_demo.map" - }) + 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", { - app_title = "Extra2D Collision Demo", - app_version = "1.0.0" - }) + 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 diff --git a/xmake/targets/extra2d.lua b/xmake/targets/extra2d.lua index b555201..875e286 100644 --- a/xmake/targets/extra2d.lua +++ b/xmake/targets/extra2d.lua @@ -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,63 +35,60 @@ function define_extra2d_target() -- 第三方头文件目录 add_includedirs("squirrel/include", {public = true}) + -- 平台兼容性头文件路径 + add_includedirs(path.join(INC_DIR, "extra2d/platform"), {public = true}) + -- ============================================== - -- Nintendo Switch 平台配置 + -- 平台特定配置 -- ============================================== - -- devkitPro mesa 路径(EGL + 桌面 OpenGL) - local devkitPro = "C:/devkitPro" - add_includedirs(path.join(devkitPro, "portlibs/switch/include"), {public = true}) - add_linkdirs(path.join(devkitPro, "portlibs/switch/lib")) + if is_plat("switch") then + -- ============================================== + -- Nintendo Switch 平台配置 + -- ============================================== + set_plat("switch") + set_arch("arm64") + set_toolchains("switch") + + -- 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", - "GLESv2", - "EGL", - "glapi", - "drm_nouveau", - {public = true}) - - -- 注意: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}) + -- 链接 EGL、OpenGL ES 3.0(mesa)和 SDL2 音频 + -- 注意:链接顺序很重要!被依赖的库必须放在后面 + add_syslinks("SDL2_mixer", "SDL2", + "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", + "GLESv2", + "EGL", + "glapi", + "drm_nouveau", + {public = true}) + else + -- ============================================== + -- PC 平台配置 (Windows/Linux/macOS) + -- ============================================== + set_toolchains("pc") + + -- 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 @@ -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 diff --git a/xmake/toolchains/pc.lua b/xmake/toolchains/pc.lua new file mode 100644 index 0000000..d78a32e --- /dev/null +++ b/xmake/toolchains/pc.lua @@ -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 diff --git a/xmake/toolchains/switch.lua b/xmake/toolchains/switch.lua index 3ec7735..18333b6 100644 --- a/xmake/toolchains/switch.lua +++ b/xmake/toolchains/switch.lua @@ -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")