NarraLeaf

store

插件作用域的持久键值存储,存放在玩家存档旁边,而不是存档里面

app.game.store 是你的插件在玩家游戏中自己的持久区域。它位于存档旁边而不是存档里面,所以开新游戏也不会丢——解锁内容记录、成就镜像,以及“他们看过这个了吗”之类的标记,正需要这一点

先声明它:

{
  "contributes": {
    "runtimeCapabilities": ["store"]
  }
}

方法

方法签名
get<T>(key: string) => Promise<T | null>
set<T>(key: string, value: T) => Promise<void>
remove(key: string) => Promise<void>
keys() => Promise<string[]>

一切都是异步的,没有存过任何东西时 get 解析为 null

export default defineRuntimePlugin({
    async setup(app) {
        const seen = (await app.game.store?.get<string[]>("unlocked")) ?? [];
        app.game.log("info", `${seen.length} unlocked`);
    },
});

键名由宿主加命名空间

直接写裸键名。宿主会在键抵达底层存储之前替每个键加上你的插件 id 前缀,所以两个都用 "unlocked" 的插件永远不会撞车,你也永远不必重复写自己的 id

await app.game.store?.set("unlocked", [...seen, "cg_01"]);
const unlocked = await app.game.store?.get<string[]>("unlocked");

keys() 返回的是不带前缀的键,所以你拿回来的就是你传进去的

存在哪里

目标底层
桌面游戏玩家用户数据中该游戏的持久化文件
Web 导出IndexedDB
Android / iOS外壳的持久化,与桌面相同

store 不属于任何存档槽位,也不是编辑器侧的 app.services.storage——后者只在工作区打开时存在。如果你需要把在 Studio 里编写的数据送到游戏中,请用 contributes.runtimeData 一起发布,并改用 app.game.data.readJson 读取——那是只读的,并随包一同分发

store 在编辑器内预览中不可用,那里节点跑在编辑器画布上,背后没有游戏。用 app.game.store?. 防护,让节点在那里什么都不做

异步,这会影响你的节点

每个方法都返回 promise。等待 store 的蓝图节点必须声明 isLatent: true,而 latent 节点不能用在内联故事表达式里——请把它放进事件图或宏图

{
    type: `${PLUGIN_ID}.isUnlocked`,
    isLatent: true,
    execute: async ctx => {
        const unlocked = (await ctx.game.store?.get<string[]>("unlocked")) ?? [];
        return { nextPort: "next", outputValues: { value: unlocked.includes(String(ctx.params.id ?? "")) } };
    },
}

本页目录