VNLEWIKIBuild stories. Connect worlds.
English
VNLE Handbook

Godot

← Bring content into your game

Godot 4.x · GDScript

Start with one visible sentence. This small example reads the real Harbour export directly; it is not a complete dialogue player. Then connect the data to your game UI.

1 · Prepare files and scene

Create a scene with a Label root and save it. Put the complete Harbour export under res://story/. Attach FirstText.gd to the Label and run the scene with F6.

story.json · Export files explained

2 · Show the first text

extends Label

const TEXT_ID = "fd7b61a3-8233-4ba7-8f77-b88b119a91bf"
var story: Dictionary

func _ready() -> void:
    var parser = JSON.new()
    var error = parser.parse(FileAccess.get_file_as_string("res://story/story.json"))
    if error != OK or not parser.data is Dictionary:
        text = "Cannot read story/story.json"
        return
    story = parser.data
    show_language("en")

func show_language(locale: String) -> void:
    var record: Dictionary = story["content"]["texts"][TEXT_ID]
    var translation: Dictionary = story["content"]["translations"].get(locale, {}).get(TEXT_ID, {})
    var message: Dictionary = translation.get("message", record["sourceMessage"])
    var line := ""
    for segment in message["segments"]:
        if segment["kind"] != "text":
            text = "This example needs text-only segments"
            return
        line += segment["text"]
    text = line

Expected: “Welcome to the harbour. I am Mira.” With locale = "de" or the language parameter de: “Willkommen im Hafen. Ich bin Mira.” A missing language uses source text. This sample supports text segments; it deliberately does not silently discard placeholders.

3 · From one sentence to a conversation

The fixed text ID above is for the first test. In your game choose flowRef/entryRef from story-index.json, read execution.entries[entryRef].nodeRef, then execution.nodes[nodeRef]. The current dialogue supplies text.textRef and speakerRef. Continuations depend on node kind.

Name/dialogue: Label.text. Answers: Button.text and the pressed signal, retaining the option ID. End: hide() the shared Control container. In the sample, show_language("de") changes the same text without advancing a node.

  1. Continue and End: follow connections, close the text box
  2. Choices: answer ID, order and selected continuation
  3. Conditions and variables: ten or two gold
  4. Language: the same IDs, newly resolved content
  5. Command: give an item or open a shop
  6. Call/Return: visit a substory and return
Inspect answers and the gold check in this language

Read the data for a choice

Insert this fragment where story is available after loading (for RPG Maker: const story = $gameTemp.vnleExample.story;). It prints IDs for inspection. Replace the output with your answer buttons. It covers the two Harbour answers and their following gold check, not a general interpreter.

# story is the Dictionary loaded above. These Harbour options have no conditions.
var choice: Dictionary = story["execution"]["nodes"]["42edfff8-b8fe-46bf-ae0d-9e2347b6e726"]
for option_id in choice["optionOrder"]:
    var option: Dictionary = choice["options"][option_id]
    print(option_id, " ", option["text"]["textRef"])
# Resolve each text and bind its option_id to a Button's pressed signal.
var branch: Dictionary = story["execution"]["nodes"]["c1c3c372-0a73-4cd0-851d-ede59d5709bb"]
var gold := 10 # Repeat with 2.
var next: Dictionary = branch["whenTrue"] if gold >= branch["condition"]["right"]["value"]["value"] else branch["whenFalse"]
print(next["nodeRef"])

With 10 gold, next refers to “Pay five gold”; with 2 it refers to “Not enough gold”. In the actual game, run the check only when the selected path reaches this node. Each selected answer supplies its own continuation.

4 · Map your own images

This supplementary excerpt expects a current dialogue object named node and the UI elements named in its comments. For C++ and MonoGame, speakerRef has already been read as SpeakerId or speakerId. The key is Mira’s actual character ID; the image name and location belong to your game. Insert the lines at the indicated locations.

# Put mira.png at res://portraits/mira.png and add a TextureRect named Portrait.
var portraits = {"807b2957-7bab-4a28-83a4-1f3d1589bbc2": preload("res://portraits/mira.png")}
# node is the current dialogue dictionary, not a Godot scene node.
$Portrait.texture = portraits.get(node.get("speakerRef", ""))

Resolve speaker names through content.characters[speakerRef].nameTextRef just like dialogue text. Missing mappings should hide the previous portrait or show a placeholder. Images are not selected by language.

Use image references from the complete export

5 · Ship files with the game

For FileAccess, preserve the raw JSON in the export: use Keep File where available, or the appropriate non-resource export filter. For images, use imported Texture2D resources or explicitly include the original PNG files. Run an exported build too.

Check your result

  1. English and German greetings display correctly; an unavailable language falls back to English.
  2. When adding flow: try both answers, gold 10/2, the return from the substory and End.
  3. Run the built game outside your project folder. JSON, fonts and portraits must be available there too.

Official documentation

API and setup references for this approach: