VNLEWIKIBuild stories. Connect worlds.Deutsch
VNLE Handbook

GameMaker

← Bring content into your game

GameMaker · GML (struct_get / struct_exists)

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

Add story.json as an Included File. Create obj_dialogue and place one instance in the first room. Paste the code into Create. Add a Draw GUI event containing draw_text_ext(24, 24, dialogue_text, -1, 700);. This entry uses desktop file access.

story.json · Export files explained

2 · Show the first text

// obj_dialogue: Create event. story.json is an Included File.
dialogue_text = "Cannot read story.json";
var file = buffer_load(working_directory + "story.json");
if (file >= 0) {
    var raw = buffer_read(file, buffer_text);
    buffer_delete(file);
    try {
        story = json_parse(raw);
        var locale = "en";
        var id = "fd7b61a3-8233-4ba7-8f77-b88b119a91bf";
        var record = struct_get(story.content.texts, id);
        var message = record.sourceMessage;
        if (struct_exists(story.content.translations, locale)) {
            var translations = struct_get(story.content.translations, locale);
            if (struct_exists(translations, id)) message = struct_get(translations, id).message;
        }
        dialogue_text = "";
        for (var i = 0; i < array_length(message.segments); i++) {
            var segment = message.segments[i];
            if (segment.kind != "text") throw "Text-only example";
            dialogue_text += segment.text;
        }
    } catch (error) { dialogue_text = "Cannot read this story text"; }
}
// Put the following line in the same object's Draw GUI event, not Create:
// draw_text_ext(24, 24, dialogue_text, -1, 700);

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.

Draw name/dialogue in Draw GUI. Use button instances carrying an option ID for answers. End: set a dialogue_open variable to false and gate drawing/input on it. Do not call game_end().

  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.

// After story was loaded successfully.
var choice = struct_get(story.execution.nodes, "42edfff8-b8fe-46bf-ae0d-9e2347b6e726");
for (var i = 0; i < array_length(choice.optionOrder); i++) {
    var option_id = choice.optionOrder[i];
    var option = struct_get(choice.options, option_id);
    show_debug_message(option_id + " " + option.text.textRef);
    // Resolve text; keep option_id on your answer button instance.
}
var branch = struct_get(story.execution.nodes, "c1c3c372-0a73-4cd0-851d-ede59d5709bb");
var gold = 10; // Repeat with 2.
var next_step = gold >= branch.condition.right.value.value ? branch.whenTrue : branch.whenFalse;
show_debug_message(next_step.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.

// Import mira.png as sprite spr_mira. In Create:
portraits = {};
struct_set(portraits, "807b2957-7bab-4a28-83a4-1f3d1589bbc2", spr_mira);
// In Draw GUI, with node holding the current dialogue struct:
if (struct_exists(portraits, node.speakerRef)) {
    draw_sprite(struct_get(portraits, node.speakerRef), 0, 96, 180);
}

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

Include the file for your selected build target. Read ID keys with struct_get; names containing hyphens are not dot properties. Adapt and test the file-loading path separately for browser targets.

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: