Inline Events

This sample uses markup to run game code in the middle of a line. When the typewriter reaches a [move/] tag, the player walks to a marker in the scene, and the rest of the line waits until they get there. When it reaches an [emotion/] tag, the speaker’s face and colour change.

When you run it, you control the player in a small arena. Walk up to Alice and talk to her. Partway through one of the player’s lines, the player walks across the arena. Later, Alice turns red and frowns in the middle of a word, and goes back to blue when she calms down.

PLACEHOLDER — Video needed
The Inline Events sample in Play mode: the player talks to Alice, walks across the arena partway through the line “So what, just part way through I stop talking… …and move on over?”, and later Alice turns red and frowns partway through her line.

Running it

Open the Inline Events scene in the Inline Events sample’s folder and press Play.

  • Move with W, A, S and D, or the arrow keys.
  • Walk up to Alice and press Space to talk to her.
  • Press Space, or click the button in the dialogue box, to continue.

How it works

The scene has two event handler GameObjects, Movement and Emote, under Event Handlers. Movement has a Move Event component, and Emote has an Emotion Event. Both components are subclasses of ActionMarkupHandler, and both are in the Line Presenter’s Event Handlers list, so the Line Presenter calls them for every line it shows. See Running code partway through a line for how event handlers work.

Inline Events
Basic Arena
Player Character ControllerSimple CharacterCharacter AppearanceMouth View
Alice Character ControllerSimple CharacterCharacter AppearanceMouth ViewDialogue Interactable
Marker Locations
near
far
Dialogue System Dialogue RunnerIn Memory Variable StorageLink Markup Handler
Canvas CanvasCanvas ScalerGraphic Raycaster
Line Presenter Content Size FitterVertical Layout GroupCanvas GroupLine Presenter
Options Presenter Vertical Layout GroupContent Size FitterCanvas GroupOptions Presenter
Line Advancer Line Advancer
Event Handlers
Emote Emotion Event
Movement Move Event
The main parts of the scene. The light and the camera are left out.

near and far are empty GameObjects that mark the two places the player walks to.

The Line Presenter’s Event Handlers list, in its Typewriter section, has three entries: the Line Presenter Button Handler that comes with the Dialogue System prefab, then Emote and Movement.

PLACEHOLDER — Screenshot needed
The Line Presenter’s Inspector, with the Typewriter section open and the Event Handlers list showing Button Container, Emote and Movement.

Moving partway through a line

These lines are in InlineExample.yarn:

Player: So what, just part way through I stop talking... [move name="far" /] ...and move on over?
Alice: Yeah you did

Player: hang on a second
<<move near>>
Player: is it just for movement?
Moving the player with markup, then with a Command.

The first line has a [move name="far" /] tag in it. When the typewriter reaches the tag, the player walks to the far GameObject. The rest of the line types out once they arrive.

The <<move near>> Command walks the player back to the near GameObject. It runs between lines, so the dialogue waits for the walk before the next line appears. Move Event adds this Command too, in its Start(), with AddCommandHandler<string>("move", MoveCharacter), so the same script handles both. See Commands.

Move Event finds every [move] tag in a line before any of the line is shown:

public override void OnPrepareForLine(MarkupParseResult line, TMP_Text text)
{
    foreach (var attribute in line.Attributes)
    {
        if (attribute.Name == "move")
        {
            if (attribute.TryGetProperty("name", out string? namedPos))
            {
                var position = GameObject.Find(namedPos);
                if (position != null)
                {
                    movements[attribute.Position] = position.transform.position;
                }
            }
        }
    }
}
Lines 3–5 Look through the line’s markup for move tags.
Line 7 Read the tag’s name value, like far.
Line 9 Find the GameObject with that name in the scene.
Lines 10–13 Remember where it is, keyed by the tag’s position in the line.
OnPrepareForLine in MoveEvent.cs.

Then, as each character is about to appear, it checks whether there’s a walk at that position:

public override async YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken)
{
    if (playerCharacter != null && movements.TryGetValue(currentCharacterIndex, out var position))
    {
        await playerCharacter.MoveTo(position, cancellationToken);
    }
}
Line 3 Only do something if there’s a [move] tag at this character, and Move Event’s Player Character is set.
Line 5 Walk the player there. The typewriter waits until this finishes before it shows the next character.
OnCharacterWillAppear in MoveEvent.cs.

The cancellationToken is cancelled when the player asks the line to hurry up. If you press Space during the walk, the player stops walking, and the rest of the line appears at once. When the line has finished appearing, OnLineDisplayComplete() clears the stored walks.

Changing emotion partway through a line

These lines are in the same Yarn Script:

Alice: Yes... which I would have shown had [b]you[emotion="angry" /] [/b] not interrupted me.
Player: oh sorry
Alice: [i]sigh[/i] [emotion="neutral" /]it's ok.
Changing Alice’s emotion with markup.

When the typewriter gets past “you”, Alice gets angry. Two lines later, she goes back to neutral after “sigh”.

[emotion="angry" /] is the short form of a tag with one value. The value is stored under the tag’s own name, so the script reads it with TryGetProperty("emotion", ...).

Emotion Event changes whoever is speaking the line. It gets the speaker’s name from the line’s character attribute, then finds a GameObject with that name in the scene:

if (!line.TryGetAttributeWithName("character", out var character))
{
    Debug.LogWarning("line has no character");
    return;
}
if (!character.Properties.TryGetValue("name", out var name))
{
    Debug.LogWarning("character has no name");
    return;
}

var emoter = GameObject.Find(name.StringValue);
if (emoter == null)
{
    Debug.LogWarning($"scene has no one called {name.StringValue}");
    return;
}

target = emoter.GetComponent<SimpleCharacter>();
if (target == null)
{
    Debug.Log($"{name.StringValue} is not a SimpleCharacterAnimation");
    return;
}

emotions = new();

foreach (var attribute in line.Attributes)
{
    if (attribute.Name != "emotion")
    {
        continue;
    }

    if (!attribute.TryGetProperty("emotion", out string? emotionKey))
    {
        continue;
    }
    emotions[attribute.Position] = emotionKey;
}
Lines 1–10 The name before the colon in the line, like Alice.
Line 12 Find the GameObject called Alice in the scene.
Line 19 Keep its Simple Character, which sets its face.
Lines 28–40 Remember each emotion tag’s value, keyed by its position in the line.
Part of OnPrepareForLine in EmotionEvent.cs.

When the typewriter reaches an emotion tag, Emotion Event calls SetFacialExpression() on the Simple Character with the tag’s value. The faces it can show are in the Simple Character’s Facial Expressions: angry, surprised, sus and neutral. Each one names an animation state on the character’s Animator.

It also changes the character’s colours through their Character Appearance. For angry, it uses red and orange, then waits 0.3 seconds, so the typewriter pauses for a moment and the change is easy to see. For any other emotion, it sets Alice’s usual blues and carries on.

Alice’s line about inline event markup has a [link] to the old documentation for this sample. Her last line links “Move Event” and “Emotion Event” to MoveEvent.cs and EmotionEvent.cs, using each script’s GUID. The Link Markup Handler on the Dialogue System handles the clicks. It opens a web address in your browser, and for a GUID, it opens the asset in the Editor. See the Welcome sample.

Things to try

  • In InlineExample.yarn, move [emotion="angry" /] to the start of Alice’s line, so she gets angry before she says anything.
  • Change one of Alice’s emotions to [emotion="surprised" /].
  • Delete the <<move near>> line, and put [move name="near" /] in the middle of “is it just for movement?”. The player walks back partway through that line instead of before it.
  • Add an empty GameObject somewhere else in the arena, give it a name, and use that name in a [move] tag.
Next step Basic Saliency Characters who say different things depending on the day and the time of day, using node groups and line groups.