Docs / Gameplay and AI

Dialogue and subtitles

Conversations with branching answers, and subtitles for speech and sounds:

  • a dialogue is a .tdialogue file: speakers, and nodes of steps (lines, answers, variables, events, jumps);
  • a Dialogue Runner plays one in the game: each line as a subtitle with its voice recording, the answers as buttons, events handed to your code;
  • Subtitles draws lines along the bottom of the screen, from dialogues, from any sound with a subtitle file beside it, or from code, with settings for the player.

Writing a dialogue

Project > + > Dialogue makes a file and opens it in the Dialogue tab (double-click a .tdialogue file to open it again; Window > Dialogue shows the tab).

  • Nodes (left): the parts of the conversation. Add one by typing a name and pressing Add; right-click one to rename, move or delete it.
  • Speakers (left): an id used in the file, the name shown with their lines, its colour, and optionally the name of the object in the scene whose Audio Source plays their recordings.
  • Steps (middle): the selected node’s steps, run from the top. Add Step adds one; each has a kind, its own fields and an only if condition (empty: always). ^ moves a step up and x removes it.
  • Preview (right): Play from runs the conversation from the selected node, with its answers as buttons and its events and variables listed below, so you can try every branch without starting the game. Clear Variables forgets what the preview set.

Save writes the file (closing the tab saves too). A game that is playing reads the new text the next time a conversation starts. Problems (a jump to a node that does not exist, a speaker nobody declared) are shown in the tab’s toolbar and in the Console.

Step Does
Say A speaker (or the narrator) says a line. Optional: a Voice recording, and a time in seconds for lines that move on by themselves.
Choice An answer the player may pick, and the node it leads to. Choices next to each other are offered together; an answer leading nowhere (carry on) continues with the step after the group.
Set / Add Sets a variable to a number, or adds to it.
Event Tells the game something happened, with a name and a number: open a gate, give an item, start a quest.
Goto Carries on at another node.
End Ends the conversation. Reaching the bottom of a node ends it too.

Conditions test variables: metGuard (not 0), !metGuard (0), gold >= 10 (also ==, !=, <, <=, >), joined with and / or. An answer whose condition fails is left out; a group with no answers left is skipped.

The file

The file is plain text, so it can also be written by hand, generated by a tool or reviewed in a pull request. One step to a line; lines starting with # are comments (the Dialogue tab does not keep comments when it saves).

speaker guard "Gate Guard" color 1 0.8 0.3 object "Guard"

node start
	say guard "Halt! Who goes there?" audio "Voice/halt.ogg"
	choice "A friend." friend
	choice "Let me through, or else." threat if courage >= 2
	choice "Never mind."
	say - "You walk away."
	end
node friend
	set metGuard 1
	say guard "Pass, friend." time 2.5
	event OpenGate
node threat
	add courage -1
	say guard "Ha! Try it."
	goto start

say - is the narrator (no name shown). Any step may end with if <condition>.

Playing it: the Dialogue Runner

Add a Dialogue Runner (under Audio) to the character who talks, and pick its Dialogue. The Inspector has Edit Dialogue and a drop-down of the file’s nodes for Start Node; while the game plays it shows the line or answers and can move the conversation on.

Setting
Play On Start Starts the conversation when the scene starts. Otherwise call Play() (when the player walks up and presses a key, say).
Auto Advance Lines move on by themselves: after their recording ends, after their time, or after Reading Speed characters a second (at least 1.5 s). Off (the default), the player moves them on.
Accept Input Space, Enter, E, the left mouse button or a controller’s bottom button moves lines on; number keys, the arrows (or W and S) and the controller’s d-pad pick answers. A project input action named Dialogue Advance replaces the keys for moving on. Off: the game calls Advance() and Choose(i) itself.
Show Choices / Show Lines Draws the answers as buttons and the lines as subtitles. Turn them off to draw your own from Line, Speaker, Choices and Selected.
Shared, Only Owner Chooses See Networking.
let talk = GetComponent<DialogueRunner>();
talk.OnEvent.Add(new (name, value) =>
{
	if (name == "OpenGate")
		gate.Open();
});
talk.OnEnded.Add(new () => player.controlsEnabled = true);
player.controlsEnabled = false;   // the keys that move lines on should not also jump or shoot
talk.Play();                      // or Play("shop")

OnLine(step), OnChoices() and OnEnded() are there for a game’s own interface and camera. Stop() ends a conversation. A line’s recording plays from its speaker’s object when that has an Audio Source, otherwise from the runner’s own object (an Audio Source is added for it when there is none, playing in both ears, in the Voice volume group).

Variables belong to the game, not to one conversation: every dialogue reads and writes the same ones, and so can your code (DialogueVariables.Get("gold"), Set, Add, Clear). A Dialogue Runner keeps them in the game’s save (SaveSystem), with where its conversation was; after loading, the conversation carries on from that line. DialogueConversation is the conversation logic on its own, without a component, for tools and tests.

Subtitles

Lines from a Dialogue Runner are shown by themselves. So is any sound an Audio Source plays that has a subtitle file beside it, with the same name ending .srt (Voice/intro.ogg and Voice/intro.srt): the cue matching the time into the sound is shown while it plays, as long as the listener is close enough to hear it (within its Max Distance). Untick Subtitles on an Audio Source to stop that for it. .srt is the SubRip format most subtitle tools write:

1
00:00:00,500 --> 00:00:02,000
CAPTAIN: Hold the line!

2
00:00:02,500 --> 00:00:04,000
[explosion]

NAME: text names the speaker; text in [square brackets] is a caption, a description of a sound, shown only when captions are on.

From code:

let id = Subtitles.Show("Radio", "Bravo team, move in.", 3);   // for three seconds (0: until hidden)
Subtitles.Caption("[distant explosion]", 2);
Subtitles.Hide(id);

The player’s settings, for a game’s options menu (save them with its other settings): Subtitles.Enabled, Captions (off by default), SpeakerNames, Size (1.25 times the interface’s text by default), Background (how dark the box behind the text is), MaxLines and Font. Subtitles.Showing lists what is up, for a game that draws its own.

Networking

By default each machine runs its own conversations: right for a single-player game, or for talk that only one player hears.

Tick Shared for one conversation everyone in the session sees, as in a co-op game. The server runs it: every player sees the same lines and answers, players who join late see where it is, and any player can move it on or pick an answer (only the object’s owner, with Only Owner Chooses). A request about a line that has already gone is ignored, so two players pressing at once do not skip a line. The object gets a Network Identity by itself when the session starts. Events and variables happen on the server, where the game decides what they do (OnEvent fires only there); OnLine, OnChoices and OnEnded fire on every machine, for each one’s interface. Voice recordings play on every machine: from an Audio Source the network already shares, the server starts it and the others hear it from there.

Subtitles from sound files follow the sound, so they reach every player who hears it. Subtitles.Show is local: call it on each machine that should see the line.

Limits

  • Text is not translated yet: one language per file.
  • The Dialogue tab is a list of steps, not a graph, and has no undo; the file is text, so version control has its history.
  • A conversation moves on one line at a time; two Dialogue Runners can play at once, and their lines share the subtitle area.

Tests

This guide is docs/dialogue.md in the engine’s repository. Spotted a mistake? Open an issue.