Documentation menu

How Narratyr fits into your game

Description: The shape of a Narratyr integration — what the exporter writes, what runs your graphs, and how your game and your story talk to each other.

Prerelease. The engine plugins are still in development, so specifics may change. The shape described here is stable.

Narratyr is an editor, but to be effective it needs to integrate with your game code. You author dialogues and quests in Narratyr, export them, and a plugin in your engine runs them during play. Narratyr provides a plugin for Unreal Engine, Godot, and Unity, along with documentation for integrating with your own engine or other engines.

This page is the mental model: what lands in your project, what walks the graph at runtime, and how a quest that needs to know whether the player has killed five boars finds that out. It is deliberately engine-agnostic. Exporting and the per-engine pages cover the buttons and the class names.

What the exporter writes

Exporting produces three distinct things, and knowing which is which explains most of the day-to-day workflow.

Your Narratyr document, split into Shapes and Values, feeding a game project containing generated code, imported assets, and the plugin — with your game code depending on the generated code and the plugin

The plugin is fixed code. It knows how to walk a graph, evaluate an expression, hold variable state, and import the exported files. It is identical in every project that uses Narratyr and you never edit it — it knows nothing about your story.

The generated code is the part that does. Your option sets become real enumerations, your property sets become real structs, your entity types become real row types, and each function you declared becomes a method waiting to be implemented. This is why an entity's Faction property is a genuine enum in your engine's editor rather than a string you compare by hand.

The imported assets are your content — the graphs, the entities, the variable defaults, the quest definitions, the localized text — converted into whatever your engine calls a data asset and picked up by the plugin's importer.

Two operations, two schedules

That split is why exporting has two operations rather than one.

Install module Export data
Writes The plugin and the generated code The data files
Driven by Your shapes — option sets, property sets, entity types, function declarations, the list of quests Your values — graph structure, dialogue text, entity fields, variable defaults
Run it Occasionally, when the schema moves Constantly, whenever content changes

The rule of thumb: if you changed what shape the data has, install the module. If you changed what the data says, export data. If you update Narratyr you should also install the module, as there's likely to be updated code with fixes.

On engines that use static types — Unreal and Unity — installing the module is a code change, so it requires a build (and sometimes an editor restart) before the new types show up. Exporting data does not require a new build: the plugin's importer notices the changed files and updates the assets in place. In practice the loop is edit in Narratyr, press Export Data, alt-tab back to the engine.

Everything the exporter writes is overwritten on the next export. Put your own code in your own module and subclass what you need, rather than editing generated files in place.

What runs your graphs

At runtime, a flow player walks the graph. It is the piece that holds a cursor on the current node and decides where to go next.

A graph is walked by a flow player, which sends events out to your game and receives calls back in, while an event listener can interrupt it

The flow player advances by itself through everything that needs nothing from you — instructions run, gates evaluate, hubs pick a branch — and stops when it reaches something only your game can resolve. By default that means dialogue and choice nodes; you can add other node types to the stop set when the game needs to do something at one.

Every stop, and most things that happen between stops, are announced as events. Your game listens for them and reacts:

  • a dialogue event says here is a line and here is who says it — draw it
  • a choice event says here are the options, including which are hidden or blocked — draw them
  • an objective event says this objective just became active, or just changed state — update the quest log
  • a custom node event says the author placed a marker you defined — play the cutscene, start the timer, whatever you built it for

Then your game calls back in to resume: continue, choose option n, complete this objective, start that quest.

Two consequences worth internalizing early:

Narratyr never draws anything. There is no dialogue widget in the box. The runtime tells you what to show and what the player's options are; presentation is entirely yours. That is deliberate — your dialogue UI is part of your game's identity, and no authoring tool should own it.

Objectives do not complete themselves. The runtime activates an objective and tracks its state, but nothing in a graph can know that the player killed five boars. Your game decides that and calls in to say so. The graph reacts to the call.

Event listeners

Most of a quest is a spine that moves forward. An event listener is the exception: it sits outside that spine, watching a condition, and interrupts the flow when the condition becomes true — runs its own branch, then hands control back where it was.

Listeners are re-checked whenever a variable is written. That is the whole trigger mechanism, and it explains a rule that otherwise looks arbitrary: a listener's condition cannot call a function. The runtime has no way to notice that your inventory changed, so a listener waiting on hasInventoryItem() would simply never fire. Mirror the state you care about into a variable and listen on that instead.

How your game and your story talk

Which brings us to the last piece, and the one worth being clearest about: there are two channels between a quest or dialogue and the game running it.

Dialogues and quests and your game both read and write variables, while functions are declared in Narratyr, called by graphs, and answered by your game's function host

Variables are shared world state. A variable is a name and a value that the runtime holds, and both sides read and write it. A dialogue can set MetTheJarl; a quest three graphs away can gate on it; your game can set it directly from code. Nothing distinguishes a variable your story owns from one your game owns — only the name matters, so a quest condition can read your game's own stats without you re-authoring them. Variables are also useful for saved games -- Narratyr's entire state is designed to be easily saved and restored.

Functions are calls into your game. Narratyr only ever holds the declaration — a name, its parameters, its return type — in the way a header file does. It never sees an implementation and does not want one. When a condition or instruction calls hasInventoryItem("Sword"), the runtime dispatches to a function host object your game registers at startup, and your code answers. The exporter generates that host with one method per function you declared, so adding a function in Narratyr and re-exporting leaves a correctly-typed method waiting to be filled in.

Which one to use

  • Use a variable for world state that both the game and Narratyr needs to know about. Player decisions, locations visited, items acquired, etc. Also use variables for reactive state -- if multiple things need to react to a state change, it likely belongs in Narratyr variable.
  • Use a function for a one-off action or a question about state your game already owns — granting an item, playing a cutscene, asking about inventory.

Where to go next

  • Exporting — the export operations in practice, and which engines are supported
  • Unreal Engine integration — the same picture with real class names, buttons, and file paths
  • Variables and Functions — the two channels in detail
  • Quests — event listeners, objectives, and the node types a flow player walks