VNLEWIKI创作故事,连接世界。
简体中文
VNLE 使用手册

将内容接入游戏

VNLE 导出文本、译文和编写好的流程。游戏读取这些数据,显示对话框,并将游戏动作连接到自身系统。你可以编写脚本或使用社区接入方案。这些示例讲解数据的使用方式,不要求使用 VNLE 专用处理器。

文件、JSON 与入口

导出理解文件与 JSON查看流程规则

所有示例均使用《港口的钥匙》的实际导出文件。其中较长的 ID 属于该项目。请替换为你自己导出文件中的 ID;名称和译文不能作为引用键。

story.json · story-index.json · 可编辑的示例项目

选择你的引擎

各引擎页面讲解如何加载和显示数据。下方教程集中说明共通的数据规则。其他引擎也可以使用相同的 JSON 数据约定,但这不表示已提供现成插件。

1 · 显示 JSON 中的一条文本

先显示“Welcome to the harbour. I am Mira.”。对话节点保存的是 textRef,而不是直接保存字符串。文本表包含消息片段。此 JavaScript 片段假定 story.json 已解析为 story;加载与显示方法请见对应引擎页面。

// story is the parsed story.json. This excerpt reads text-only messages.
const locale = 'en';
const textRef = 'fd7b61a3-8233-4ba7-8f77-b88b119a91bf';
const record = story.content.texts[textRef];
const translation = story.content.translations[locale]?.[textRef];
const message = translation ? translation.message : record.sourceMessage;
const text = message.segments.map(segment => {
  if (segment.kind !== 'text') throw new Error('This example needs text-only segments');
  return segment.text;
}).join('');
console.log(text);

首次测试使用固定的文本 ID。请区分空译文和缺失译文:存在且有效的空译文可以保持为空。

2 · 开始、继续和结束对话

玩家与米拉交谈时,游戏从 story-index.json 选择匹配的入口,打开对话框并解析入口的第一个节点。请沿引用执行,不要按 JSON 属性的排列顺序执行。

const entryRef = '5e6b2e12-1ac1-4c46-8221-1a051e87370a';
const entry = story.execution.entries[entryRef];
const node = story.execution.nodes[entry.nodeRef];
console.log(node.kind, node.text.textRef);
// Read this dialogue's text; wait for the player's Continue action.
// node.continuation describes the next step, not the next array element.

点击 Continue 后按 continuation 继续。kind: node 使用 nodeRef 指向下一个节点;kind: end 结束对话。kind 为 end 的节点也有相同效果。随后游戏关闭对话框并恢复玩家操作。VNLE 不会退出应用或自动结束关卡。

{
  "continuation": {
    "kind": "node",
    "nodeRef": "7c804744-b002-4136-9a7c-703fcf2d0880"
  },
  "flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
  "id": "d789af84-d51d-4b97-b564-86a6756e2f2e",
  "kind": "dialogue",
  "speakerRef": "807b2957-7bab-4a28-83a4-1f3d1589bbc2",
  "text": {
    "bindings": {},
    "textRef": "cbe1a637-991f-4857-81d1-6cc053d68927"
  }
}

此片段中的 Maybe later 通向 End 节点。示例的问候则先进入 Call Substory。若脚本只支持基本的对话和结束节点,就必须在这里停止并解释原因,直到实现 Call/Return。

3 · 提供选项

米拉提供 Buy the key (5 gold) 和 Maybe later 两个选项。optionOrder 定义显示顺序,options 保存各选项记录。每个按钮保留自己的选项 ID。点击后,只应用该选项的 effects 和 continuation。

// Read this exact choice from the Harbour story. No condition on these two options.
const choice = story.execution.nodes['42edfff8-b8fe-46bf-ae0d-9e2347b6e726'];
for (const optionId of choice.optionOrder) {
  const option = choice.options[optionId];
  console.log(optionId, option.text.textRef, option.continuation);
}
// Each answer button must retain its optionId.
// On click: check availability, apply that option's effects once,
// then follow that option's continuation.

若存在 option.condition,它会决定选项是否可用。港口示例中的两个选项自身没有条件,金币检查位于随后的 branch 节点。没有可用选项时,按 whenEmpty 处理:进入指定的后续流程,或报告明确的错误。

4 · 检查条件并修改值

十枚金币可以买到钥匙,两枚则不够。条件已经包含在 JSON 中,游戏提供当前金币值。此片段展示具体的 gte 比较;其他运算符和嵌套条件见流程参考。

// This excerpt evaluates the Harbour purchase check: Gold >= 5.
const branch = story.execution.nodes['c1c3c372-0a73-4cd0-851d-ede59d5709bb'];
const condition = branch.condition;
const gold = 10; // Supply the current value from your game's saved state.
const minimum = condition.right.value.value; // 5, from the exported typed literal.
const continuation = gold >= minimum ? branch.whenTrue : branch.whenFalse;
console.log(story.execution.nodes[continuation.nodeRef].kind);
// Repeat with gold = 2: the next node is a dialogue explaining the price.
// The full condition grammar is documented in Flow rules.
条件和变量变更实际使用的 JSON 字段
{
  "condition": {
    "kind": "compare",
    "left": {
      "kind": "variable",
      "variableRef": "a547610e-661f-4b8d-a25b-21d042f1a2d5"
    },
    "operator": "gte",
    "right": {
      "kind": "literal",
      "value": {
        "type": "integer",
        "value": 5
      }
    }
  },
  "flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
  "id": "c1c3c372-0a73-4cd0-851d-ede59d5709bb",
  "kind": "branch",
  "whenFalse": {
    "kind": "node",
    "nodeRef": "eaffd4b0-397d-40b7-abc6-404a0a801e0e"
  },
  "whenTrue": {
    "kind": "node",
    "nodeRef": "242b2fd4-8101-4a7f-9306-17b789508bdc"
  }
}
{
  "continuation": {
    "kind": "node",
    "nodeRef": "500c28a1-ed85-4c36-9a38-bedacce1c6b7"
  },
  "effects": [
    {
      "kind": "subtract",
      "value": {
        "kind": "literal",
        "value": {
          "type": "integer",
          "value": 5
        }
      },
      "variableRef": "a547610e-661f-4b8d-a25b-21d042f1a2d5"
    },
    {
      "kind": "set",
      "value": {
        "kind": "literal",
        "value": {
          "type": "boolean",
          "value": true
        }
      },
      "variableRef": "76407a53-5ce1-408e-a8c6-a31c10f69ca5"
    }
  ],
  "flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
  "id": "242b2fd4-8101-4a7f-9306-17b789508bdc",
  "kind": "action"
}

Change Variable 会导出为带 effects 的 action。本例扣除五枚金币,并将 QuestAccepted 设为 true。只有创建新的游戏状态时才从 execution.variables 初始化变量;加载存档时应恢复保存的值。如果背包系统也记录金币,请指定唯一的权威数据来源,并协调两边的更新。

5 · 切换语言

完整导出将译文保存在 content.translations 中,不需要为每种语言加载单独的 JSON 文件。若 content.translations[locale][textRef].message 存在则使用译文,否则使用 content.texts[textRef].sourceMessage。locale 来自游戏设置。

切换语言时,重新解析当前对话、说话角色姓名和可用选项,保留当前位置、已选选项和变量。不要重新执行命令或变量变更。占位符应复用当前句显示时获取的值。

占位符:“你还剩 5 枚金币” · 在 VNLE 中管理翻译

6 · 在同一对话栏中显示姓名、对话和立绘

  1. 对话文本:解析 node.text.textRef。
  2. 说话角色:查找 content.characters[node.speakerRef] 并翻译 nameTextRef。没有 speakerRef 时,该句可以是不属于任何角色的旁白。
  3. 完整导出中的立绘:优先使用所选表情的立绘,否则使用 defaultPortraitRef。以 story.json 所在文件夹为基准,加载 content.assets[assetRef].relativePath。JSON 保存的是路径,而不是 PNG 图片的字节数据。
  4. 选项:为每个可用的选项 ID 创建一个按钮。普通对话提供 Continue。
  5. 到达 End 时,关闭整个对话栏、禁用选项按钮,并按需恢复玩家操作。

如果使用引擎自有图片,或导出文件没有立绘映射,请在游戏中定义映射。角色 ID → 图片资源的对应关系不会因重命名或翻译而改变。表情图片还需包含表情 ID。

// The game owns this mapping. These are real Harbour character IDs.
const portraits = {
  '807b2957-7bab-4a28-83a4-1f3d1589bbc2': 'assets/portraits/mira.png'
};
const node = story.execution.nodes['ffa7141b-97ed-44c1-a346-8bba7e26d94f'];
const file = portraits[node.speakerRef];
// Load file using your engine's image loader; show a placeholder if absent.
// Optional emotion variants: portraits[characterId][emotionId].
// Do not index by the translated character name.

引擎页面展示如何用导入的纹理、精灵或图集图片实现映射。VNLE 导出不会根据语言选择图片。图片缺失时,请移除上一张立绘,也可以显示占位图。

需要时实现更复杂的流程

跳转 · 调用子故事/返回 · 命令 · 存档与错误情况

叙事创作者编写流程。开发者只需实现一次各节点的执行规则,并将约定的命令接入游戏。之后修改故事,游戏即可按新导出的连接继续执行。