From b78aaa5cad78ac03005c499415bef269e3bd2ebd Mon Sep 17 00:00:00 2001 From: 1165565692-lang <1165565692@qq.com> Date: Tue, 9 Jun 2026 00:52:04 +0800 Subject: [PATCH] Add Switch game framework target --- BUILD.md | 76 +++++++++++++++++++++++++++ engine.lua | 4 +- game/assets/shaders/colored_quad.frag | 4 +- game/assets/shaders/colored_quad.vert | 4 +- game/assets/shaders/sprite.frag | 4 +- game/assets/shaders/sprite.vert | 4 +- game/docs/ARCHITECTURE.md | 28 +++++++++- game/docs/GAME_ARCHITECTURE_BOARD.md | 30 +++++++++-- game/src/switch_game_main.cpp | 43 +++++++++++++++ xmake.lua | 33 ++++++++++++ 10 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 game/src/switch_game_main.cpp diff --git a/BUILD.md b/BUILD.md index e65713b..5ff7b01 100644 --- a/BUILD.md +++ b/BUILD.md @@ -237,6 +237,64 @@ Frostbite2D Switch smoke test Press PLUS to exit. ``` +### 2.4 编译 Switch 游戏框架 NRO + +当前游戏框架已经接入 Switch 构建目标: + +```text +Frostbite2DGameSwitch +``` + +该目标使用: + +```text +game\src\switch_game_main.cpp +``` + +作为 Switch 专用入口,运行同一套 `WhiteboxScene`、`StageRenderer`、 +`DebugOverlay`、`PlayerActor` 和 `LevelDefinition` 游戏框架。 + +构建命令: + +```powershell +$env:PATH='C:\devkitPro\devkitA64\bin;C:\devkitPro\tools\bin;C:\devkitPro\msys2\usr\bin;C:\Program Files\CMake\bin;C:\Program Files\xmake;' + $env:PATH +$env:DEVKITPRO='C:\devkitPro' +$env:DEVKITA64='C:\devkitPro\devkitA64' + +& 'C:\Program Files\xmake\xmake.exe' f -c -p switch -a aarch64 -m release --toolchain=cross --sdk='C:\devkitPro\devkitA64' --bin='C:\devkitPro\devkitA64\bin' --cross='aarch64-none-elf-' -y +& 'C:\Program Files\xmake\xmake.exe' -b Frostbite2DGameSwitch +``` + +产物: + +```text +build\switch\aarch64\release\Frostbite2DGame.elf +build\switch\aarch64\release\switch_game\NSUnknownGame.nro +build\switch\aarch64\release\switch_game\control.nacp +build\switch\aarch64\release\switch_game\assets\ +``` + +`xmake.lua` 会在构建后自动: + +- 复制 `game\assets` 到 `build\switch\aarch64\release\switch_game\assets` +- 生成 `control.nacp` +- 调用 `elf2nro` 生成带元数据的 `NSUnknownGame.nro` + +Switch 运行时资源目录当前由引擎解析为: + +```text +/switch/Frostbite2D/NS Unknown Game/assets +``` + +在真机 SD 卡或模拟器文件系统中测试完整游戏时,需要确保资源也放到该目录。 +构建输出目录中的 `switch_game\assets` 是用于部署的资源副本。 + +当前已验证: + +- `Frostbite2DGameSwitch` 可成功编译为 ELF/NRO +- PC 和 Switch 共用同一套 GLES 300 es shader +- Windows 游戏目标仍可正常编译和启动 + ## 3. 常见问题 ### 3.1 切换平台后编译异常 @@ -267,3 +325,21 @@ Frostbite2D_smoke_meta.nro 不要优先使用裸的 `Frostbite2D_smoke.nro`。 +### 3.4 Switch 游戏 NRO 启动后资源加载失败 + +确认资源目录存在: + +```text +/switch/Frostbite2D/NS Unknown Game/assets +``` + +至少应包含: + +```text +assets\shaders\shaders.json +assets\shaders\sprite.vert +assets\shaders\sprite.frag +assets\shaders\colored_quad.vert +assets\shaders\colored_quad.frag +assets\character\male\*.png +``` diff --git a/engine.lua b/engine.lua index 1e54dfa..b7f3592 100644 --- a/engine.lua +++ b/engine.lua @@ -37,7 +37,7 @@ function add_frostbite2d_target() local libnx = path.join(devkitpro, "libnx") add_cxflags("-D__SWITCH__", "-march=armv8-a+crc+crypto", "-mtune=cortex-a57", "-mtp=soft", {public = true}) - add_cxflags("-ffunction-sections", "-fdata-sections", {public = true}) + add_cxflags("-ffunction-sections", "-fdata-sections", "-fPIE", {public = true}) add_cxflags("-specs=" .. path.join(libnx, "switch.specs"), {public = true, force = true}) add_ldflags("-specs=" .. path.join(libnx, "switch.specs"), {public = true, force = true}) @@ -47,7 +47,7 @@ function add_frostbite2d_target() add_includedirs(path.join(libnx, "include"), {public = true}) add_linkdirs(path.join(portlibs, "lib"), path.join(libnx, "lib"), {public = true}) add_links("SDL2_ttf", "freetype", "SDL2_mixer", "vorbisidec", "opusfile", "opus", "modplug", "mpg123", "mikmod", "FLAC", "ogg", {public = true}) - add_links("SDL2_image", "png", "jpeg", "webp", "z", "SDL2", "EGL", "GLESv2", "glapi", "drm_nouveau", "nx", "m", "pthread", {public = true}) + add_links("SDL2_image", "png", "jpeg", "webp", "z", "SDL2", "EGL", "GLESv2", "glapi", "drm_nouveau", "nx", "m", "pthread", "stdc++", {public = true}) else add_packages("libsdl2", {public = true}) add_packages("libsdl2_image", {public = true}) diff --git a/game/assets/shaders/colored_quad.frag b/game/assets/shaders/colored_quad.frag index 8b6ef84..8877043 100644 --- a/game/assets/shaders/colored_quad.frag +++ b/game/assets/shaders/colored_quad.frag @@ -1,4 +1,6 @@ -#version 330 core +#version 300 es +precision mediump float; + in vec4 v_color; out vec4 FragColor; diff --git a/game/assets/shaders/colored_quad.vert b/game/assets/shaders/colored_quad.vert index a0763de..1c819de 100644 --- a/game/assets/shaders/colored_quad.vert +++ b/game/assets/shaders/colored_quad.vert @@ -1,4 +1,6 @@ -#version 330 core +#version 300 es +precision mediump float; + layout (location = 0) in vec2 a_position; layout (location = 1) in vec2 a_texCoord; layout (location = 2) in vec4 a_color; diff --git a/game/assets/shaders/sprite.frag b/game/assets/shaders/sprite.frag index 0894353..5ef8d90 100644 --- a/game/assets/shaders/sprite.frag +++ b/game/assets/shaders/sprite.frag @@ -1,4 +1,6 @@ -#version 330 core +#version 300 es +precision mediump float; + in vec2 v_texCoord; in vec4 v_color; diff --git a/game/assets/shaders/sprite.vert b/game/assets/shaders/sprite.vert index fbc4e8a..25b0f7d 100644 --- a/game/assets/shaders/sprite.vert +++ b/game/assets/shaders/sprite.vert @@ -1,4 +1,6 @@ -#version 330 core +#version 300 es +precision mediump float; + layout (location = 0) in vec2 a_position; layout (location = 1) in vec2 a_texCoord; layout (location = 2) in vec4 a_color; diff --git a/game/docs/ARCHITECTURE.md b/game/docs/ARCHITECTURE.md index 0be5454..280badb 100644 --- a/game/docs/ARCHITECTURE.md +++ b/game/docs/ARCHITECTURE.md @@ -21,6 +21,7 @@ game/ ├─ movement/ 可复用的移动和碰撞辅助工具 ├─ scene/ 游戏场景和关卡编排 ├─ stage/ 关卡视觉层渲染 + ├─ switch_game_main.cpp Switch/NS 游戏入口 └─ main.cpp 游戏可执行文件入口点 ``` @@ -99,8 +100,8 @@ main.cpp ## 当前构建目标 -`xmake.lua` 为 Windows 定义 `Frostbite2DGame`。它编译所有 -`game/src/**.cpp` 文件,链接到 `Frostbite2D`,并在构建后 +`xmake.lua` 为 Windows 定义 `Frostbite2DGame`。它编译桌面游戏入口 +`game/src/main.cpp` 和所有通用游戏源码,链接到 `Frostbite2D`,并在构建后 将 `game/assets` 复制到可执行文件旁边。 构建命令: @@ -115,6 +116,29 @@ xmake -b Frostbite2DGame build/windows/x64/release/Frostbite2DGame.exe ``` +Switch/NS 游戏框架目标为 `Frostbite2DGameSwitch`。它使用 +`game/src/switch_game_main.cpp` 作为平台入口,复用同一套 Scene、Stage、 +Actor、Movement、Debug 和 Data 模块。 + +构建命令: + +```powershell +xmake -b Frostbite2DGameSwitch +``` + +输出: + +```text +build/switch/aarch64/release/Frostbite2DGame.elf +build/switch/aarch64/release/switch_game/NSUnknownGame.nro +``` + +Switch 运行时资源目录: + +```text +/switch/Frostbite2D/NS Unknown Game/assets +``` + ## 后续架构步骤 1. 添加 `actor/enemy_actor.*` 并复用 `PlatformMover` diff --git a/game/docs/GAME_ARCHITECTURE_BOARD.md b/game/docs/GAME_ARCHITECTURE_BOARD.md index 4e15e4b..ba2368d 100644 --- a/game/docs/GAME_ARCHITECTURE_BOARD.md +++ b/game/docs/GAME_ARCHITECTURE_BOARD.md @@ -77,6 +77,7 @@ mindmap Hitbox可视化 帧率与日志 构建文档 + NS平台构建 ``` ## 白板总览 @@ -295,6 +296,26 @@ mindmap - 绘制 hitbox / hurtbox。 - 输出当前场景、角色状态、FPS。 +### 11. Platform + +职责: + +- 保持 PC 和 NS 共用游戏逻辑。 +- 只在入口、构建、资源部署、平台输入处做差异化。 + +已有: + +- `game/src/main.cpp`:Windows/PC 游戏入口。 +- `game/src/switch_game_main.cpp`:Switch/NS 游戏入口。 +- `Frostbite2DGame`:Windows 游戏目标。 +- `Frostbite2DGameSwitch`:Switch 游戏目标,生成 `NSUnknownGame.nro`。 + +下一步: + +- 将 Switch Joy-Con 输入接入 `InputStateProvider`。 +- 整理资源部署脚本,把 `assets` 放到 `/switch/Frostbite2D/NS Unknown Game/assets`。 +- 在 Ryujinx 或真机上验证主循环、shader 和角色纹理。 + ## 数据流白板 ```text @@ -380,10 +401,11 @@ mindmap 下一步建议按这个顺序做: 1. `BattleZone`:做清版推进的核心框架。 -2. `EnemyActor`:做第一个敌人占位。 -3. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 -4. `SpriteAnimator`:把玩家动画从硬编码改成组件。 -5. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。 +2. `SwitchInput`:把 Joy-Con/手柄输入接入 `InputStateProvider`。 +3. `EnemyActor`:做第一个敌人占位。 +4. `CombatSystem`:把 hitbox/hurtbox 真正跑起来。 +5. `SpriteAnimator`:把玩家动画从硬编码改成组件。 +6. `StageLayer`:把白盒视觉层扩展为背景、tiles 和 props。 ## 素材协作提示 diff --git a/game/src/switch_game_main.cpp b/game/src/switch_game_main.cpp new file mode 100644 index 0000000..a2d9382 --- /dev/null +++ b/game/src/switch_game_main.cpp @@ -0,0 +1,43 @@ +#include "core/game_config.h" +#include "scene/whitebox_scene.h" + +#include + +#include +#include +#include + +int main(int argc, char** argv) { + (void)argc; + (void)argv; + + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + + auto config = frostbite2D::AppConfig::createDefault(); + config.appName = "NS Unknown Game"; + config.windowConfig.title = "NS Unknown Game"; + config.windowConfig.width = ns_game::config::kVirtualWidth; + config.windowConfig.height = ns_game::config::kVirtualHeight; + config.windowConfig.resizable = false; + config.windowConfig.vsync = true; + config.useVirtualResolution = true; + config.virtualWidth = ns_game::config::kVirtualWidth; + config.virtualHeight = ns_game::config::kVirtualHeight; + config.resolutionMode = frostbite2D::ResolutionScaleMode::Fit; + config.renderStyleProfile = frostbite2D::RenderStyleProfileId::PixelArt2D; + config.maxFps = 60; + + auto& app = frostbite2D::Application::get(); + if (!app.init(config)) { + std::puts("NS Unknown Game Switch init failed."); + return 1; + } + + app.run([]() { + frostbite2D::SceneManager::get().ReplaceScene( + frostbite2D::MakePtr()); + }); + + app.shutdown(); + return 0; +} diff --git a/xmake.lua b/xmake.lua index 424cc3b..1145318 100644 --- a/xmake.lua +++ b/xmake.lua @@ -35,6 +35,7 @@ if is_plat("windows") then target("Frostbite2DGame") set_kind("binary") add_files("game/src/**.cpp") + remove_files("game/src/switch_game_main.cpp") add_deps("Frostbite2D") add_packages("libsdl2") add_packages("libsdl2_image") @@ -49,3 +50,35 @@ if is_plat("windows") then end) target_end() end + +if is_plat("switch") then + target("Frostbite2DGameSwitch") + set_kind("binary") + set_filename("Frostbite2DGame.elf") + add_files("game/src/**.cpp") + remove_files("game/src/main.cpp") + add_deps("Frostbite2D") + add_cxflags("-D__SWITCH__", "-fPIE", {public = true}) + add_ldflags("-specs=" .. path.join(os.getenv("DEVKITPRO") or "C:/devkitPro", "libnx/switch.specs"), "-fPIE", {force = true}) + after_build(function (target) + local outdir = path.join(target:targetdir(), "switch_game") + os.mkdir(outdir) + os.cp(path.join(os.projectdir(), "game/assets"), + path.join(outdir, "assets")) + + local devkitpro = os.getenv("DEVKITPRO") or "C:/devkitPro" + local tools = path.join(devkitpro, "tools/bin") + local libnx = path.join(devkitpro, "libnx") + local nacp = path.join(outdir, "control.nacp") + local nro = path.join(outdir, "NSUnknownGame.nro") + os.execv(path.join(tools, "nacptool.exe"), { + "--create", "NS Unknown Game", "Frostbite2D", "1.0.0", nacp + }) + os.execv(path.join(tools, "elf2nro.exe"), { + target:targetfile(), nro, + "--nacp=" .. nacp, + "--icon=" .. path.join(libnx, "default_icon.jpg") + }) + end) + target_end() +end