Embed Switch game assets in RomFS
This commit is contained in:
@@ -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=<build>\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 和纹理,
|
||||
会表现为黑屏或没有场景内容。
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <frostbite2D/utils/startup_trace.h>
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
## 后续架构步骤
|
||||
|
||||
@@ -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 卡外部资源部署脚本。
|
||||
|
||||
## 数据流白板
|
||||
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#ifndef __SWITCH__
|
||||
#define SDL_MAIN_HANDLED
|
||||
#else
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "core/game_config.h"
|
||||
#include "scene/whitebox_scene.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <frostbite2D/core/application.h>
|
||||
#include <frostbite2D/scene/scene_manager.h>
|
||||
#include <switch.h>
|
||||
|
||||
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<ns_game::WhiteboxScene>());
|
||||
});
|
||||
|
||||
app.shutdown();
|
||||
return 0;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user