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.
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.
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.
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?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;
}
}
}
}
}move tags.name value, like far.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);
}
}[move] tag at this character, and Move Event’s Player Character is set.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.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;
}Alice.Alice in the scene.emotion tag’s value, keyed by its position in the line.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.
The links
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.