Skip to content

Closed captions

A lot of dialogue carries non-spoken cues: "Oh dear. [sigh] What now?", "[whispering] Over here.", a line that is only a sound effect. With closed captions on (the default) the player sees all of it. A player who can hear the audio may want them off, and Patterplay then removes those cues at runtime, identically on every engine.

This is a pure text feature: it doesn’t depend on the audio system, voiced mode, or anything else.

With captions off, the runtime removes each bracketed cue from a line. A line that is only a cue becomes a silent line: no text, but its event still fires so its audio still plays. Captions on Captions off ANNA Oh dear. [sigh] What now? ANNA Oh dear. What now? SFX Thunder rumbles. silent line, audio still plays Each cue is stripped and the spacing closed up. A cue-only line goes silent, but its audio still plays.

Write the cues inline in your dialogue, wrapped in the project’s caption delimiters: [ and ] by default:

ANNA. Oh dear. [sigh] What now?

Square brackets are the default because they match the closed-captioning convention for non-speech cues, and because parentheses are already spoken for: a ( at the start of a line opens a performance direction in the editor, so it can’t also start a caption there.

Configure it in Project Settings ▸ Closed Captions:

  • Open / Close: the delimiter pair that wraps a cue. Default [ and ]; you can use any pair, even the same token both sides (e.g. *…*). Avoid (: it opens a direction at the start of a line.
  • Caption character: a cast member (default SFX) whose whole lines are treated as pure captions. A line spoken by this character is removed entirely when captions are off: delimiters or not , so you can write sound-only lines like SFX: Thunder rumbles in the distance.

Defaults apply even if you never open the tab: [ / ] and an SFX character.

What the runtime does when captions are off

Section titled “What the runtime does when captions are off”

For each dialogue line (it never touches narration, choice text, or anything else):

  1. If the line is spoken by the caption character, the whole line is removed.
  2. Otherwise every open…close cue is removed, and the surrounding whitespace is collapsed: "Oh dear. [sigh] What now?""Oh dear. What now?".

If that leaves the line empty (the whole line was a cue), it becomes a silent line: the dialogue event still fires (so its audio still plays and visit-tracking still counts) but it carries no text and no speaker, so nothing is captioned. Lines with no cue are returned untouched.

Captions are a presentation setting, like the language: toggling them never changes save state, the story position, or which lines play. Audio is keyed off the line’s id, so a silent line’s voice take plays exactly as before.

The rest of this page is for your developer. Captions default to on. Flip them at construction or live, on any runtime:

// JavaScript / TypeScript (@patterkit/runtime or patterplay.min.js)
const engine = new Patterplay.Engine(bundle, { closedCaptions: false }); // start off
engine.setClosedCaptions(true); // or toggle live, e.g. from a settings menu
engine.closedCaptions; // current state
Construct offToggle liveRead
JavaScriptnew Engine(b, { closedCaptions: false })engine.setClosedCaptions(on)engine.closedCaptions
Unity (C#)new Engine(b, new EngineOptions { ClosedCaptions = false })engine.SetClosedCaptions(on)engine.ClosedCaptions
Unreal (C++)EngineOptions{ closedCaptions=false }engine.setClosedCaptions(on)engine.closedCaptions()
Godot (GDScript)PatterEngine.new(b, { "closed_captions": false })engine.set_closed_captions(on)engine.closed_captions()

Already-emitted lines aren’t retro-edited (that’s the host’s call); the change applies to the next line.

In an IDs-only build the runtime emits beat ids and your game looks the text up itself, so it also applies the caption rule itself. Each flow exposes the same transform the embedded runtime uses: call it (after interpolate) when your captions are off:

let text = myLookup(step.id);
text = flow.interpolate(text);
if (!captionsOn) text = flow.stripCaptions(text); // remove cues with the project's delimiters

This stripping is covered by the cross-runtime test suite, so every engine strips identically.

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