Typewriter and Effects
The Line Presenter uses a typewriter to put each line’s text on screen. You can use one of the typewriters that come with Yarn Spinner or write your own. The fade in Effects works from any script.
The built-in typewriters
The Line Presenter’s Typewriter Style chooses one of three built-in typewriters: Instant, By Letter or By Word. The Line Presenter page explains what each one does and how to set its speed.
Whichever you choose, when the player presses the Line Advancer’s Hurry Up Line input while a line is still typing out, the rest of the line appears at once. See Line Advancer.
The By Letter and By Word typewriters use Unity’s game time, so they stop typing while Time.timeScale is 0.
Pausing partway through a line
To make the typewriter stop for a moment in the middle of a line, put [pause/] where you want it to stop:
Rosa: The lamp went out, and then [pause/]I heard footsteps.
Rosa: Somebody was on the stairs. [pause=500/]Somebody heavy.Put the space before [pause/], not after it. A tag like this one removes the space that follows it, so then[pause/] I would show as “thenI”.
On its own, [pause/] waits for 1 second. Give it a whole number to wait that many milliseconds, like [pause=500/], or a decimal to wait that many seconds, like [pause=0.5/].
Pauses work with all three built-in typewriters, including Instant, which shows the text up to the pause, waits, and then shows the rest. If the player hurries up the line during a pause, the pause ends and the rest of the line appears.
To make your own markup that does something while a line types out, see Markup.
Writing your own typewriter
If none of the built-in typewriters does what you want, you can write your own. A typewriter is a class that implements IAsyncTypewriter. To choose it in the Inspector, make it a MonoBehaviour.
This one types a line out letter by letter, and waits a little longer after each full stop, comma, exclamation mark and question mark:
using System;
using System.Collections.Generic;
using System.Threading;
using TMPro;
using UnityEngine;
using Yarn.Markup;
using Yarn.Unity;
public class PunctuationTypewriter : MonoBehaviour, IAsyncTypewriter
{
[SerializeField] float charactersPerSecond = 40f;
[SerializeField] float punctuationPause = 0.3f;
public TMP_Text TextElement { get; set; }
public List<IActionMarkupHandler> ActionMarkupHandlers { get; } = new();
public void PrepareForContent(MarkupParseResult line)
{
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
foreach (var handler in ActionMarkupHandlers)
{
handler.OnPrepareForLine(line, TextElement);
}
}
public async YarnTask RunTypewriter(MarkupParseResult line, CancellationToken token)
{
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineDisplayBegin(line, TextElement);
}
var textInfo = TextElement.GetTextInfo(line.Text);
for (int i = 0; i < textInfo.characterCount; i++)
{
foreach (var handler in ActionMarkupHandlers)
{
await handler.OnCharacterWillAppear(i, line, token).SuppressCancellationThrow();
}
TextElement.maxVisibleCharacters = i + 1;
if (token.IsCancellationRequested)
{
continue;
}
var delay = 1f / charactersPerSecond;
if (".,!?".IndexOf(textInfo.characterInfo[i].character) >= 0)
{
delay += punctuationPause;
}
await YarnTask.Delay(TimeSpan.FromSeconds(delay), token).SuppressCancellationThrow();
}
TextElement.maxVisibleCharacters = textInfo.characterCount;
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineDisplayComplete();
}
}
public void ContentWillDismiss()
{
foreach (var handler in ActionMarkupHandlers)
{
handler.OnLineWillDismiss();
}
}
public void ContentDidDismiss()
{
TextElement.maxVisibleCharacters = 0;
}
}MonoBehaviour that implements IAsyncTypewriter, so you can add it to a GameObject and assign it to the Line Presenter.TextElement is the Line Presenter’s Line Text, which it sets when the scene starts.[pause] handler and its Event Handlers to this list, and so does a Line Advancer whose Presenter is this Line Presenter.PrepareForContent before the panel fades in. Put the text in the text field, and hide all of it to start with.RunTypewriter once for each line, and treats the line as finished when it returns. token tells you when the player wants to hurry.GetTextInfo gives the characters TextMeshPro will draw, without any markup tags, so each position matches a character on screen.[pause] waits.token, which ends the wait early.ContentWillDismiss is called before the panel fades out, and ContentDidDismiss after it has gone.To use it:
- Add the
PunctuationTypewritercomponent to a GameObject in your scene. - Select the Line Presenter, and set Typewriter Style to Custom.
- Drag the GameObject into Custom Typewriter, which appears under Typewriter Style.
Its settings, Characters Per Second and Punctuation Pause in this example, are on the PunctuationTypewriter component, where you can change them.
If Typewriter Style is Custom and Custom Typewriter is empty, the Line Presenter shows each line all at once and logs a warning.
ActionMarkupHandlers the way the example does. Without them, [pause] and the Line Presenter’s Event Handlers don’t work, and the Line Advancer never learns that the line has finished, so pressing Hurry Up Line a second time doesn’t move on. The built-in typewriters, in Runtime/Views/Typewriter/ in the Yarn Spinner package, show how.Effects
Effects has a fade you can call from any script, without adding anything to your scene.
| Method | What it does |
|---|---|
FadeAlphaAsync(canvasGroup, from, to, duration, token) | Fades a Canvas Group’s Alpha between any two levels of transparency, from 0 to 1. Returns a YarnTask you can await. |
FadeAlpha(canvasGroup, from, to, duration, token) | The same fade, as a coroutine to use with StartCoroutine(). |
If token is cancelled, the fade jumps straight to to. If duration is 0, the fade happens at once. The Line Presenter and Options Presenter use FadeAlphaAsync for their own fades.
A Command can return the fade’s YarnTask, and the dialogue waits for it to finish before it carries on. Commands run between lines, when the Line Presenter has hidden its panel, so use them for things that stay on screen, such as a title card:
using System.Threading;
using UnityEngine;
using Yarn.Unity;
public class TitleCard : MonoBehaviour
{
[SerializeField] CanvasGroup canvasGroup;
[YarnCommand("fade_out")]
public YarnTask FadeOut(float duration = 1f)
{
return Effects.FadeAlphaAsync(canvasGroup, 1, 0, duration, CancellationToken.None);
}
}fade_out Command, with an optional duration in seconds.<<fade_out TitleCard 2>>
Rosa: Are you here about the lamp?The first value after the Command’s name, TitleCard, is the name of the GameObject with the TitleCard component on it. See Commands.