How to Write Sequences

Cutscene sequences are defined in simple, text-based commands. Since they're text, these sequences are very compact, and they're quick and easy for dialogue authors to add as they write dialogue, even in external authoring programs such as Chat Mapper and articy:draft. A large library of sequencer commands is already included, and you can easily add your own.

To jump right in, you can skip down to Simple Example: Audio.

If you don't want to specify your sequences in text strings, you can use a scrubbing sequence editor such as Well Fired's uSequencer and simply call the uSequencer sequence by using the provided uSeq() command.

Sequencer Command Syntax

The format of a sequencer command make look intimidating at first, but don't worry! It boils down to a couple very simple patterns. You'll get the hang of it very quickly.

The general format of a sequencer command is:

[required] command ( [parameters] ) [@seconds]

or:

[required] command ( [parameters] ) [@Message(message)]

(The square brackets around parts such as required mean it's optional.)

You can also add this optional part to the end:

->Message(endmessage)

The parts mean:

Part Description
required This optional keyword specifies that the command must run even if the player cancels the sequence
command The command to play (e.g., "Camera" in Camera(Closeup))
parameters The parameters for the command (e.g,. "Closeup" in Camera(Closeup))
@seconds The optional time at which to play the command; otherwise it plays immediately
@Message(message) The message to wait for before playing the command; otherwise it plays immediately
->Message(endmessage) A message to send to the sequencer when the command completes

Built-in Keywords

The following keywords are available as parameters in dialogue entries:

Keyword Description
speaker The actor in the current dialogue entry, or the assigned speaker in a non-conversation sequence
listener The conversant in the current dialogue entry, or the assigned listener in a non-conversation sequence
entrytag A unique identifier for a dialogue entry (see How to Use Entrytags or How to Add Lipsync for example use)
entrytaglocal The dialogue entry's localized entrytag for the current language
{{end}} A numeric value based on the length of the current dialogue entry's subtitle text
{{default}} Gets replaced with the Dialogue Manager's Default Sequence. Use this to include the Default Sequence in your dialogue entries' custom sequences

Certain commands also have special, command-specific keywords, too. Those keywords are described in the documentation for the command.

Example:

{{default}};
SetActive(Fireworks,true)@{{end}}

Shortcuts

You can register your own shortcuts using a Sequencer Shortcuts component or the Sequencer.RegisterShortcut method. A shortcut is a string that's an alias for one or more sequencer commands. If you include the shortcut in a sequence, wrapped in double-braces, it will be replaced by its sequencer commands.

Keyword Description
{{shortcut}} Replaced by the shortcut's value

Example:

// Register shortcut in code (could alternatively use Sequencer Shortcuts component):
Sequencer.RegisterShortcut("talk", "AnimatorPlayWait(moveMouth); AudioWait(babble)");

Sequence:

Camera(Closeup);
{{talk}}

Markup Tags

Sequences may also contain [var=varName] and [lua(code)] markup tags. For more information about markup tags, see Dialogue Markup Tags.

Example:

Delay([var=customizableDelay])

Other Notes

Very often the player is already assigned as the speaker of the conversation or sequence. In this case, you can use the special keyword 'speaker' to refer to the player even if its name does have parentheses:

LookAt(speaker)

If the sequence runs during an NPC line, the player may be 'listener' in this case.

Syntax Examples

Fade(in)
Camera(Closeup, listener)
Animation(jump)@2.5
AudioWait(whisper)->Message(AllDone)
required AnimatorPlay(idle)@Message(AllDone)

(sequencer code above)

Multiple Sequencer Commands

String together multiple commands by putting a semicolon (;) between them:

Audio(waltz); Animation(dance); required Animation(idle)@2

(sequencer code above)

Simple Example: Audio

Here's a simple example of adding audio to your conversations:

  1. Create a folder named Resources (use exact spelling and capitalization) in your project. You can have any number of Resources folders in your project anywhere under Assets.
  2. Put audio files (.mp3, .ogg, etc.) in the Resources folder. You can optionally organize them under subfolders. For example, let's say you've created a subfolder under Resources named Adam, and you've put an audio file hello.mp3 in this subfolder.
  3. In your conversation's dialogue entry, set the Sequence field to:

    AudioWait(Adam/hello)

  4. Optional: Add an Audio Source component to the character GameObject that speaks this line. This allows you to configure the audio source to your liking. Otherwise the Dialogue System will add a default audio source. If you want to play the audio file through a different GameObject's Audio Source, provide its name as a second argument. For example, say you want to play it through a GameObject named Loudspeaker:
    <code><pre>AudioWait(Adam/hello, Loudspeaker)</pre></code>
    

When Do Commands Run?

Commands are not sequential. Every command will try to run at the start of the sequence unless it uses the @seconds or @Message(message). If you use either of these two syntaxes, the command will wait in a queue until the condition is met (i.e., the time mark has arrived, or the sequencer received the specified message).

In addition, many commands will start some kind of activity in Unity and then exit, leaving the activity running in the background.

For example, the Audio() command will start an audio clip playing on an audio source and then exit immediately. These commands usually have an equivalent command that will wait until the activity is done. For audio, the AudioWait() command waits until the audio clip is done before exiting.

A sequence is not done until all of its commands are done. The duration of the sequence Audio(whisper) is 0 seconds. The duration of AudioWait(whisper) is the length of the audio clip whisper.


Example 1: Fight Sequence

This is an example fight sequence involving animation, sound, and camera work.

Camera(Closeup); Animation(Punch); Camera(Wide,listener,1)@2; 
Audio(Oof,listener)@2.5; required Animation(Crumple,listener)@2.5

The sequence does this:

  1. Immediately cuts to a closeup of the speaker
  2. Immediately plays the speaker's Punch animation
  3. At the 2-second mark, smoothly pans the camera to a wide shot of the listener over the course of 1 second
  4. At the 2.5-second mark, plays the "Oof!" audio clip on the listener
  5. At the 2.5-second mark, plays the listener's Crumple animation (even if the player cancels the sequence)

Note that we specified that the crumple animation is required; this ensures that the listener ends up in the correct position (crumpled on the ground) even if the player cancels (skips) the cutscene. Very often, the last Camera() command in a sequence will be marked required to make sure the camera is set up for the next line of dialogue.


Example 2: Waiting for Messages

This example uses the @Message() syntax to keep a command queued until the sequencer receives a message.

SendMessage(Bomb, StartCountdown); 
SetActive(Bomb, false)@Message(Kaboom); SetActive(Explosion)@Message(Kaboom)

(sequencer code above)

The sequence does this:

  1. Immediately sends the message "StartCountdown" to the object named Bomb. Assume that the Bomb has a method named StartCountdown() that counts down and then sends the message "Kaboom" as shown in the code below.
  2. When the sequencer receives "Kaboom" it:
    • Sets the Bomb GameObject inactive (i.e., hides it).
    • Sets the Explosion particle effect GameObject active.

You could use this script on Bomb:

void StartCountdown() {
StartCoroutine(CountDownAndExplode(5));
}
IEnumerator CountDownAndExplode(float seconds) {
yield return new WaitForSeconds(5);
Sequencer.Message("Kaboom");
}

(C# code above)

Note that the CountDownAndExplode() method above uses a static method Sequencer.Message() to send a message to the sequencer(s) on the Dialogue Manager GameObject.


Example 3: Sending Messages

This example uses the ->Message() syntax, which sends a message when a command completes, and the @Message() syntax, which waits until the sequencer receives a message.

Animation(Punch)->Message(Punched);
required Animation(Crumple,listener)@Message(Punched)

(sequencer code above)

The sequence above plays the speaker's Punch animation. At the end of the animation, it sends the message "Punched" to the sequencer.

The second Animation() command is configured to wait for the "Punched" message. When it receives this message, it plays the listener's Crumple animation.


How to Use Asset Bundles

If you use Unity Pro/Unity 5+ and Asset Bundles, your sequences can load assets from asset bundles in addition to Resources folders. You must first register your loaded asset bundles with the Dialogue System using PixelCrushers.DialogueSystem.DialogueManager.RegisterAssetBundle.

The following code registers an asset bundle with the Dialogue System, then runs a sequence to play an audio clip named "alarmSound" through a GameObject named "Loudspeaker". The Dialogue System will attempt to load "alarmSound" from all registered asset bundles, then from Resources folders.

DialogueManager.RegisterAssetBundle(myAssetBundle);
DialogueManager.PlaySequence("Audio(alarmSound,Loudspeaker)");

(C# code above)

Before freeing a registered asset bundle, make sure to unregister it from the Dialogue System:

DialogueManager.UnregisterAssetBundle(myAssetBundle);

(C# code above)


<< Sequences | Sequencer Command Reference >>