Skip to content

Godot

Patterplay for Godot is the pure-GDScript Patterplay runtime: no web view, no native extension to compile. It loads a .patterc bundle and plays it directly, held to the same shared test suite as every other engine.

Verified on Godot 4.7. The runtime uses only plain GDScript (no scene-tree types), so it also runs headless.

Drop the addons/patterplay/ folder into your project’s addons/ directory (download it from the play-godot-v* Release: see the downloads page) and enable the plugin in Project ▸ Project Settings ▸ Plugins. The runtime works with or without the editor plugin enabled; enabling it just registers the helper classes.

Load the bundle text, build an engine, open a flow, and advance it. The play loop in GDScript: steps come back as plain dictionaries:

var json := FileAccess.get_file_as_string("res://story.patterc")
var bundle = PatterBundle.load_from_string(json)
var engine := PatterEngine.new(bundle)
var flow := engine.open_flow("main", "intro") # ("flow id", starting scene/block)
while true:
var step := flow.advance() # { "type": ..., "text": ..., "options": ... }
match step["type"]:
"line": print("%s: %s" % [step.get("characterName", ""), step["text"]])
"text": print(step["text"])
"gameEvent": pass # fire step.gameData cues
"choice": flow.choose(step["options"][0]["id"]) # your UI picks
"end": break

Render each step into your own dialogue UI. On a "choice", present step["options"] (each has prompt text and an eligible flag) and call flow.choose(id) with the player’s pick.

Two demos ship inside the addon, under addons/patterplay/demo/ (delete the folder freely): a headless play-through demo (demo.gd, the smallest possible integration) and the Tour scene (tour.tscn), which plays the full interactive Patter tour with clickable choice buttons. The tour also shows per-line audio resolution via PatterAudio; audio files are not bundled (playback is your platform call), so point its audio base at a Patter audio folder to hear it, or leave it unset to play silently.

The game can also decide where the story goes. run_flow plays an address in one call, which is all a bark needs:

# Reuses the "guard-42" flow, so its shuffles and once-each lists keep their place
var lines: Array = engine.run_flow("guard-42", "npc-barks", "greet")
for line in lines:
print("%s: %s" % [line.get("characterName", ""), line["text"]])
# Or move a flow you are already driving, exactly as an authored jump would
var moved: bool = flow.goto("throne-room", "audience") # false = did not resolve, cursor unmoved

Give each independent speaker its own flow name. Full rules, and why open_flow behaves differently: Host navigation.

A Godot game runs in its own process, so the live state inspector ships as an in-game overlay, PatterStatePanel, it watches and edits a running engine’s @patter properties and saves / loads the whole run:

PatterDebug.register(engine) # right after you build the engine
var panel := PatterStatePanel.new() # auto-discovers registered engines
add_child(panel) # (or set panel.engine = my_engine)

Each property gets a type-aware editor (bool / number / string / enum / flags) with a reset-to-default button; values live-refresh without clobbering the field you’re editing. The panel is a debug tool: in a release export (OS.is_debug_build() false) it stays hidden and builds nothing, so it is safe to leave in a scene that ships.

PatterDebugLink streams the running story position back to Patterpad so the editor follows the cursor like a debugger. It only opens the link in a debug build, so it is inert in a release export:

var link := PatterDebugLink.new(engine.build_id(), "My Game")
add_child(link)
link.flow_opened("main")
# ...after each advance()/choose():
link.observe("main", flow.current_scene(), step.get("id", ""), step["type"])

Live refresh & debug

The engine serialises the whole run: every flow’s position, the shared @patter / @scene state, visit counts, and the seeded random generator: to a JSON .patterstate blob, so a save round-trips through its own save format (semantically equivalent to the other engines, not byte-identical across them). → Save/load & Game Data

Nothing to configure. From Patterplay 0.4.5 the plugin puts your .patterc into the export itself, so an exported build has its story on every platform - desktop, mobile and web. Export as you would any Godot project.

That is worth stating plainly because it used to be a trap. Godot packs the files it recognises as resources and can silently drop everything else, so a game ran perfectly in the editor, which reads loose project files, and shipped without its story. If you followed an older version of this page and added *.patterc to your export filters, you can leave it: it is harmless, and it still covers you if the plugin is ever disabled.

Other loose files are still yours to handle. The plugin knows about bundles and nothing else, so if you use Audio Folders add patteraudio.json under Project ▸ Export… ▸ your preset ▸ Resources ▸ “Filters to export non-resource files/folders” - the audio files themselves are imported resources and export fine, the manifest is plain JSON and is not. The same goes for any other data file you read at runtime, like a *.json save template.

To sanity-check a build before you ship it, in the EXPORTED game rather than the editor:

print(FileAccess.file_exists("res://story.patterc")) # must print true

MIT-licensed open source · Made by · patterkit.dev