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.
Install
Section titled “Install”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.
Play a flow
Section titled “Play a flow”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": breakRender 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.
Send the story somewhere
Section titled “Send the story somewhere”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 placevar 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 wouldvar moved: bool = flow.goto("throne-room", "audience") # false = did not resolve, cursor unmovedGive each independent speaker its own flow name. Full rules, and why open_flow behaves
differently: Host navigation.
Live property inspector
Section titled “Live property inspector”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 enginevar panel := PatterStatePanel.new() # auto-discovers registered enginesadd_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.
Follow the live cursor in Patterpad
Section titled “Follow the live cursor in Patterpad”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"])Save and load
Section titled “Save and load”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
Exporting your game
Section titled “Exporting your game”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- The shared model: The play loop.
- Driving the story from the game: Host navigation.
- Reading Game Data/tags, host events, localisation: Save/load & Game Data.
- Why it matches the other engines exactly: Compatibility & conformance.
MIT-licensed open source · Made by Ian Thomas · patterkit.dev