Skip to content

项目文件读写指南 ​

本文说明 WasiCore 用户代码如何通过 .NET File / Directory API 读取项目随包文件,以及在编辑器调试阶段写入客户端项目文件。

如果文件是由引擎 API 加载的图片、Spine、模型、粒子、音频、材质等资源,不适用本文的 user_files 规则。引擎资源路径与脚本文件读写路径是两套机制:UI 图片和 Spine 资源放在项目 ui/ 下,代码路径从 ui/ 下一级开始;未上传云但需要随包的非 UI 引擎资源放在项目 res/ 下,代码路径从 res/ 下一级开始。详见 项目资源路径指南。

核心规则 ​

WasiCore 运行在 WASI 沙盒中。代码看到的路径不是本机工程根目录,而是运行时预先开放的虚拟目录。不要使用 E:\...、C:\... 等本机绝对路径,也不要依赖 .. 逃出开放目录。

当前公开给项目代码直接读写普通文件的自定义文件目录是 user_files:

访问目的客户端工程目录服务端工程目录代码中的路径权限
主项目自定义文件ui/AppBundle/user_files/AppBundle/user_files/user_files/...读;客户端编辑器调试阶段可写,包括纯客户端调试
依赖库自定义文件<依赖库>/ui/AppBundle/user_files/<依赖库>/AppBundle/user_files/deps/<依赖库名>/user_files/...只读

此外,运行时 AppBundle 根目录会以 . 只读方式开放,用于框架和运行时自身加载。业务自定义可读写文件应放在 user_files,不要把 . 当成完整项目根目录使用。

目录应该放在哪里 ​

客户端和服务端发布时下载的是项目的不同部分,所以需要按端分别放置文件:

text
MyProject/
├── AppBundle/
│   └── user_files/
│       └── server-config.json      # 服务端通过 user_files/server-config.json 读取
└── ui/
    └── AppBundle/
        └── user_files/
            └── client-config.json  # 客户端通过 user_files/client-config.json 读取

如果同一份文件客户端和服务端都要读取,需要分别放到 AppBundle/user_files/ 与 ui/AppBundle/user_files/。如果文件只给服务端使用,就只放 AppBundle/user_files/;如果只给客户端使用,就只放 ui/AppBundle/user_files/。

项目模板的 .gitignore 已允许提交 AppBundle/user_files/** 和 ui/AppBundle/user_files/**。运行时生成的临时文件如果不希望进版本库,应额外添加更具体的忽略规则,例如忽略 ui/AppBundle/user_files/generated/。

读取主项目文件 ​

直接使用 .NET 的 File / Directory API,并使用相对路径 user_files/...。

csharp
using System.Text.Json;

var configPath = Path.Combine("user_files", "client-config.json");

if (!File.Exists(configPath))
{
    Game.Logger.LogWarning("配置文件不存在: {Path}", configPath);
    return;
}

var json = File.ReadAllText(configPath);
var config = JsonSerializer.Deserialize<MyConfig>(json);
Game.Logger.LogInformation("配置读取成功: {Path}", configPath);

枚举目录也使用同一个根:

csharp
if (Directory.Exists("user_files"))
{
    foreach (var file in Directory.EnumerateFiles("user_files", "*.json", SearchOption.AllDirectories))
    {
        Game.Logger.LogInformation("发现配置文件: {File}", file);
    }
}

读取依赖库文件 ​

依赖库的 user_files 会映射到 deps/<依赖库名>/user_files。依赖库名使用项目依赖中的库名,运行时路径中会按小写处理。

例如依赖库 GameSparkCore 中有:

text
GameSparkCore/
└── AppBundle/
    └── user_files/
        └── shared-rules.json

服务端代码读取:

csharp
var rules = File.ReadAllText("deps/gamesparkcore/user_files/shared-rules.json");

客户端如果要读取同名依赖文件,则该文件应存在于依赖库的 ui/AppBundle/user_files/ 下:

csharp
var uiRules = File.ReadAllText("deps/gamesparkcore/user_files/client-rules.json");

读取依赖库时没有“主项目读不到就 fallback 到依赖库”的规则。主项目使用 user_files/...,依赖库使用 deps/<依赖库名>/user_files/...,路径要显式写清楚。依赖库名应使用字母、数字、下划线、连字符或点;包含其他字符的依赖库名不会被映射到 deps。

写入规则 ​

写入只面向客户端编辑器调试阶段开放,用于调试工具、生成本地测试文件、保存临时客户端配置等。这里的客户端编辑器调试包括普通客户端调试和纯客户端调试;纯客户端调试通过 -client_only 启动,但仍属于编辑器调试进程,因此主项目 user_files 可写。示例:

csharp
#if CLIENT
var outputDir = Path.Combine("user_files", "generated");
Directory.CreateDirectory(outputDir);

var outputPath = Path.Combine(outputDir, "client-debug-output.txt");
File.WriteAllText(outputPath, $"CreatedAt={DateTime.UtcNow:O}");
Game.Logger.LogInformation("客户端调试文件已写入: {Path}", outputPath);
#endif

以下场景应视为只读:

  • 服务端始终只读。
  • 客户端非编辑器调试阶段只读。
  • 所有依赖库的 deps/<依赖库名>/user_files 始终只读。
  • AppBundle 根目录 . 始终只读。

写入只适合编辑器调试和本地辅助文件,不适合保存正式玩家进度。正式的用户持久化数据应使用 CloudData;见 CloudData 快速指南 与 CloudData 最佳实践。

纯客户端调试的更多用法见 纯客户端调试指南。

常见异常 ​

DirectoryNotFoundException 通常表示对应的 user_files 目录没有被放到当前端的 AppBundle 中。例如客户端读取 user_files/readme.txt 时,应检查 ui/AppBundle/user_files/readme.txt 是否存在;服务端则检查 AppBundle/user_files/readme.txt。

UnauthorizedAccessException 通常表示当前路径是只读目录,或当前运行阶段不允许写入。例如服务端写 user_files/...、客户端发布环境写 user_files/...、写入 deps/... 都会失败。

FileNotFoundException 通常表示目录已开放,但目标文件不存在。检查大小写、文件名、扩展名以及客户端/服务端是否放在了对应目录。

调试验证 ​

code_sample 模板中包含 FileAccessTest 游戏模式,可用于验证当前环境的读写权限。运行后查看运行时日志:

  • 日志根目录:<EditorRoot>/logs
  • 客户端日志:<EditorRoot>/logs/client
  • 服务端日志:<EditorRoot>/logs/server

客户端调试通过时应能看到类似日志。普通客户端调试和纯客户端调试都应显示 writable[1]:

text
WASI user_files main preopen: host[.../ui/AppBundle/user_files], writable[1]
[FileAccessTest:Client] PASS user_files directory exists
[FileAccessTest:Client] PASS seed file can be read
[FileAccessTest:Client] PASS client write/read roundtrip

服务端通过时应能看到读取成功、写入被阻止的日志。这说明服务端读取权限正常,同时只读规则生效。

使用建议 ​

把 user_files 用作随项目发布的配置、模板、关卡辅助数据或调试辅助文件;不要把它当成数据库或玩家存档系统。客户端 user_files 会随客户端资源发布,不能放密钥、内部配置或不希望玩家看到的内容。服务端敏感配置应只放在服务端 AppBundle/user_files,并由服务端代码读取。