VNLEWIKIBuild stories. Connect worlds.
English
VNLE Handbook

Bring content into your game

VNLE exports text, translations and the authored flow. Your game reads these data, displays the dialogue box and connects game actions to its own systems. Use your own scripts or a community integration. These examples explain the data without requiring a VNLE handler.

Files, JSON and entry points

Export → Understand files and JSON → Look up flow rules

All examples use the actual Harbour Key export. Its long IDs belong to that project. Replace them with IDs from your export; names and translated text are not reference keys.

story.json · story-index.json · Editable example project

Choose your engine

Each page explains loading and display in its engine. The learning path below explains the shared data rules once. Other engines can use the same JSON contract; this does not promise a ready-made plugin.

1 · Display one text from JSON

Start with “Welcome to the harbour. I am Mira.” A dialogue holds a textRef, not a string directly. The text table contains segments. This JavaScript excerpt assumes story.json is already parsed into story; engine pages show loading and display.

// 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);

The first test uses a fixed text ID. Do not confuse an empty translation with a missing one: a present, valid empty translation may remain empty.

2 · Start, continue and end a conversation

The player talks to Mira. Your game chooses the matching entry from story-index.json, opens its dialogue box and resolves the entry’s first node. Follow references, never JSON property order.

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.

After Continue, follow continuation. kind: node uses nodeRef to identify the next node; kind: end finishes the conversation. A node whose kind is end has the same effect. The game then closes the dialogue box and restores controls. VNLE does not quit the application or automatically end a level.

{
  "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"
  }
}

In this excerpt, “Maybe later” leads to the End node. The sample greeting instead leads to Call Substory first. A basic dialogue/end script must stop with an explanation there until Call/Return is supported.

3 · Offer answers

Mira offers “Buy the key (5 gold)” and “Maybe later”. optionOrder defines their order. options holds the records. Each button retains its option ID. On click, apply only that answer’s effects and 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.

An option.condition, when present, determines whether the answer is offered. The Harbour export has two answers without their own conditions here; its gold check is in the following branch node. If no answer is available, follow whenEmpty: a specified continuation or an explained fault.

4 · Check conditions and change values

Ten gold buys the key; two gold does not. The condition is already in the JSON. The game supplies the current gold value. This excerpt demonstrates the specific gte comparison; other operators and nested conditions are in the flow reference.

// 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.
Actual JSON fields for the condition and change
{
  "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 exports as an action with effects. Here it subtracts five gold and sets QuestAccepted to true. Initialize variables from execution.variables only for a new game context; loading a save restores saved values. If gold also exists in the inventory system, establish one authoritative source and a coordinated update.

5 · Change language

The complete export stores translations in content.translations. These files do not require loading a separate JSON for each language. Use content.translations[locale][textRef].message when present, otherwise content.texts[textRef].sourceMessage. Get the locale from the game settings.

On language change, resolve the current dialogue, speaker name and offered answers again. Keep position, chosen answers and variables. Do not re-run commands or variable changes. Reuse the placeholder values captured when the current line was presented.

Placeholders: “You have 5 gold left” · Maintain translations in VNLE

6 · Name, dialogue and portrait in one dialogue bar

  1. Dialogue text: resolve node.text.textRef.
  2. Speaker: look up content.characters[node.speakerRef] and translate nameTextRef. Without speakerRef, the line may be narration without a character.
  3. Portrait in the complete export: prefer the requested emotion’s portrait, otherwise defaultPortraitRef. Load content.assets[assetRef].relativePath relative to the folder containing story.json. JSON holds the path, not PNG image bytes.
  4. Answers: create one button per available option ID. For a regular dialogue offer Continue.
  5. At End close the entire dialogue bar, disable answer buttons and restore player controls if needed.

If you use your own engine images or your export has no portrait mapping, define one in the game. Character ID → image resource remains stable across renaming and translation. For emotions include the emotion 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.

Engine pages demonstrate this mapping with imported textures, sprites or atlas images. The VNLE export has no language-dependent image selection. Remove the previous portrait when an image is missing and optionally show a placeholder.

Advanced flow when needed

Jump · Call Substory / Return · Command · Saves and error cases

The narrative artist authors the flow. The developer implements node semantics once and connects agreed commands to the game. Later story changes then follow their exported connections.