From e8b05e93b9040d63740fc422c92c66f371acab2e Mon Sep 17 00:00:00 2001 From: 1165565692-lang <1165565692@qq.com> Date: Tue, 9 Jun 2026 01:00:20 +0800 Subject: [PATCH] Embed Switch game assets in RomFS --- BUILD.md | 46 ++++++++++++------- .../src/frostbite2D/core/application.cpp | 9 +++- game/docs/ARCHITECTURE.md | 11 ++--- game/docs/GAME_ARCHITECTURE_BOARD.md | 6 +-- game/src/main.cpp | 20 ++++++++ game/src/switch_game_main.cpp | 43 ----------------- xmake.lua | 10 ++-- 7 files changed, 72 insertions(+), 73 deletions(-) delete mode 100644 game/src/switch_game_main.cpp diff --git a/BUILD.md b/BUILD.md index 5ff7b01..5efaf90 100644 --- a/BUILD.md +++ b/BUILD.md @@ -245,14 +245,15 @@ Press PLUS to exit. Frostbite2DGameSwitch ``` -该目标使用: +该目标和 Windows 共用: ```text -game\src\switch_game_main.cpp +game\src\main.cpp ``` -作为 Switch 专用入口,运行同一套 `WhiteboxScene`、`StageRenderer`、 -`DebugOverlay`、`PlayerActor` 和 `LevelDefinition` 游戏框架。 +入口内部通过 `__SWITCH__` 编译分支处理平台差异,运行同一套 +`WhiteboxScene`、`StageRenderer`、`DebugOverlay`、`PlayerActor` 和 +`LevelDefinition` 游戏框架。 构建命令: @@ -279,15 +280,22 @@ build\switch\aarch64\release\switch_game\assets\ - 复制 `game\assets` 到 `build\switch\aarch64\release\switch_game\assets` - 生成 `control.nacp` - 调用 `elf2nro` 生成带元数据的 `NSUnknownGame.nro` +- 将 `game\assets` 复制到临时 `romfs\assets` 后内嵌到 NRO 中,因此运行时可以直接读取 + `romfs:/assets/...` -Switch 运行时资源目录当前由引擎解析为: +Switch 运行时资源目录当前优先由引擎解析为: + +```text +romfs:/assets +``` + +如果 RomFS 挂载失败,才会回退到: ```text /switch/Frostbite2D/NS Unknown Game/assets ``` -在真机 SD 卡或模拟器文件系统中测试完整游戏时,需要确保资源也放到该目录。 -构建输出目录中的 `switch_game\assets` 是用于部署的资源副本。 +构建输出目录中的 `switch_game\assets` 是调试和外部部署用的资源副本。 当前已验证: @@ -325,21 +333,25 @@ Frostbite2D_smoke_meta.nro 不要优先使用裸的 `Frostbite2D_smoke.nro`。 -### 3.4 Switch 游戏 NRO 启动后资源加载失败 +### 3.4 Switch 游戏 NRO 启动后资源加载失败或黑屏 -确认资源目录存在: +优先确认 `NSUnknownGame.nro` 是新构建的 RomFS 版本。构建时 `elf2nro` +命令应包含: ```text -/switch/Frostbite2D/NS Unknown Game/assets +--romfsdir=\switch_game\romfs ``` -至少应包含: +NRO 内部应能访问: ```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 +romfs:/assets/shaders/shaders.json +romfs:/assets/shaders/sprite.vert +romfs:/assets/shaders/sprite.frag +romfs:/assets/shaders/colored_quad.vert +romfs:/assets/shaders/colored_quad.frag +romfs:/assets/character/male/*.png ``` + +旧版本曾只把资源复制到 NRO 旁边,运行时引擎找不到 shader 和纹理, +会表现为黑屏或没有场景内容。 diff --git a/Frostbite2D/src/frostbite2D/core/application.cpp b/Frostbite2D/src/frostbite2D/core/application.cpp index aab1b87..a7c155b 100644 --- a/Frostbite2D/src/frostbite2D/core/application.cpp +++ b/Frostbite2D/src/frostbite2D/core/application.cpp @@ -27,6 +27,9 @@ #include #include #include +#ifdef __SWITCH__ +#include +#endif namespace frostbite2D { @@ -150,7 +153,11 @@ bool Application::initCoreModules() { SDL_Log("Asset working directory: %s", workingDir.c_str()); } #else - asset.setWorkingDirectory("/switch/Frostbite2D/" + config_.appName); + if (R_SUCCEEDED(romfsInit())) { + asset.setWorkingDirectory("romfs:/"); + } else { + asset.setWorkingDirectory("/switch/Frostbite2D/" + config_.appName); + } SDL_Log("Asset working directory: %s", asset.getWorkingDirectory().c_str()); #endif diff --git a/game/docs/ARCHITECTURE.md b/game/docs/ARCHITECTURE.md index 280badb..f48c864 100644 --- a/game/docs/ARCHITECTURE.md +++ b/game/docs/ARCHITECTURE.md @@ -21,7 +21,6 @@ game/ ├─ movement/ 可复用的移动和碰撞辅助工具 ├─ scene/ 游戏场景和关卡编排 ├─ stage/ 关卡视觉层渲染 - ├─ switch_game_main.cpp Switch/NS 游戏入口 └─ main.cpp 游戏可执行文件入口点 ``` @@ -116,9 +115,9 @@ xmake -b Frostbite2DGame build/windows/x64/release/Frostbite2DGame.exe ``` -Switch/NS 游戏框架目标为 `Frostbite2DGameSwitch`。它使用 -`game/src/switch_game_main.cpp` 作为平台入口,复用同一套 Scene、Stage、 -Actor、Movement、Debug 和 Data 模块。 +Switch/NS 游戏框架目标为 `Frostbite2DGameSwitch`。它复用 +`game/src/main.cpp`,入口内部通过 `__SWITCH__` 编译分支处理平台差异, +并复用同一套 Scene、Stage、Actor、Movement、Debug 和 Data 模块。 构建命令: @@ -133,10 +132,10 @@ build/switch/aarch64/release/Frostbite2DGame.elf build/switch/aarch64/release/switch_game/NSUnknownGame.nro ``` -Switch 运行时资源目录: +Switch 运行时资源目录优先使用内嵌 RomFS: ```text -/switch/Frostbite2D/NS Unknown Game/assets +romfs:/assets ``` ## 后续架构步骤 diff --git a/game/docs/GAME_ARCHITECTURE_BOARD.md b/game/docs/GAME_ARCHITECTURE_BOARD.md index ba2368d..63acdd3 100644 --- a/game/docs/GAME_ARCHITECTURE_BOARD.md +++ b/game/docs/GAME_ARCHITECTURE_BOARD.md @@ -305,16 +305,16 @@ mindmap 已有: -- `game/src/main.cpp`:Windows/PC 游戏入口。 -- `game/src/switch_game_main.cpp`:Switch/NS 游戏入口。 +- `game/src/main.cpp`:统一游戏入口,通过 `__SWITCH__` 区分 Windows/PC 与 Switch/NS。 - `Frostbite2DGame`:Windows 游戏目标。 - `Frostbite2DGameSwitch`:Switch 游戏目标,生成 `NSUnknownGame.nro`。 +- Switch NRO 已内嵌 `game/` 为 RomFS,运行时优先读取 `romfs:/assets`。 下一步: - 将 Switch Joy-Con 输入接入 `InputStateProvider`。 -- 整理资源部署脚本,把 `assets` 放到 `/switch/Frostbite2D/NS Unknown Game/assets`。 - 在 Ryujinx 或真机上验证主循环、shader 和角色纹理。 +- 如果 RomFS 不可用,再补 SD 卡外部资源部署脚本。 ## 数据流白板 diff --git a/game/src/main.cpp b/game/src/main.cpp index b864ae6..551f790 100644 --- a/game/src/main.cpp +++ b/game/src/main.cpp @@ -3,7 +3,12 @@ #include +#ifndef __SWITCH__ #define SDL_MAIN_HANDLED +#else +#include +#endif + #include #include @@ -11,12 +16,23 @@ int main(int argc, char** argv) { (void)argc; (void)argv; +#ifdef __SWITCH__ + padConfigureInput(1, HidNpadStyleSet_NpadStandard); +#endif + auto config = frostbite2D::AppConfig::createDefault(); config.appName = "NS Unknown Game"; +#ifdef __SWITCH__ + config.windowConfig.title = "NS Unknown Game"; + config.windowConfig.width = ns_game::config::kVirtualWidth; + config.windowConfig.height = ns_game::config::kVirtualHeight; + config.windowConfig.resizable = false; +#else config.windowConfig.title = "NS Unknown Game - Whitebox"; config.windowConfig.width = 1280; config.windowConfig.height = 720; config.windowConfig.resizable = true; +#endif config.windowConfig.vsync = true; config.useVirtualResolution = true; config.virtualWidth = ns_game::config::kVirtualWidth; @@ -27,7 +43,11 @@ int main(int argc, char** argv) { auto& app = frostbite2D::Application::get(); if (!app.init(config)) { +#ifdef __SWITCH__ + std::puts("NS Unknown Game Switch init failed."); +#else std::puts("NS Unknown Game init failed."); +#endif return 1; } diff --git a/game/src/switch_game_main.cpp b/game/src/switch_game_main.cpp deleted file mode 100644 index a2d9382..0000000 --- a/game/src/switch_game_main.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#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 1145318..9d8faf0 100644 --- a/xmake.lua +++ b/xmake.lua @@ -35,7 +35,6 @@ 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") @@ -56,7 +55,6 @@ if is_plat("switch") then 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}) @@ -71,13 +69,19 @@ if is_plat("switch") then local libnx = path.join(devkitpro, "libnx") local nacp = path.join(outdir, "control.nacp") local nro = path.join(outdir, "NSUnknownGame.nro") + local romfsdir = path.join(outdir, "romfs") + os.rm(romfsdir) + os.mkdir(romfsdir) + os.cp(path.join(os.projectdir(), "game/assets"), + path.join(romfsdir, "assets")) 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") + "--icon=" .. path.join(libnx, "default_icon.jpg"), + "--romfsdir=" .. romfsdir }) end) target_end()