Phone Chat

This sample shows dialogue as a conversation in a messaging app. It doesn’t use the Line Presenter or the Options Presenter. One custom Presenter, ChatDialogueView, handles both lines and options, and adds each one to a scrolling list of message bubbles.

When you run it, the conversation starts by itself. Each message first appears as a bubble with three bouncing dots, as if the sender were typing, and then the dots are replaced by the text. Messages from A are green and on the right. Messages from B are blue and on the left. Partway through, three replies appear at the bottom of the screen, and the conversation waits until you pick one.

PLACEHOLDER — Screenshot needed
The Phone Chat sample running: the phone-shaped scroll view with green bubbles from A on the right (‘hey i made this chat demo’, ‘it’s pretty cool’), blue bubbles from B on the left (’lmao nice’, ‘does it support options’), and A’s ’lemme see’ at the bottom.

Running it

In the Project window, open the Phone Chat sample’s folder, open the PhoneChat scene, and press Play.

The lines move on by themselves. When the replies appear, click one to send it.

How it works

PhoneChat
Main Camera CameraAudio Listener
Canvas CanvasCanvas ScalerGraphic Raycaster
Scroll View ImageScroll RectShadow
Options View
Background Image
Viewport ImageMask
Content Vertical Layout GroupContent Size Fitter
Options Container Vertical Layout GroupContent Size Fitter
EventSystem Event SystemEnsure Input Module Available
Dialogue System Dialogue Runner
Chat Dialogue View Chat Dialogue View
Sample Renderer Detector Sample Render Detector
Force TMP Word Wrap Force Text Wrap On Samples
The PhoneChat scene. Chat Dialogue View is the Dialogue Runner’s only Presenter.

On the Dialogue Runner, Start Automatically is on, so the dialogue starts at the Start node when the scene loads. Run Selected Option As Line is on as well, so the reply you pick appears as a message. See Dialogue Runner.

There’s no Line Advancer in this scene, and nothing for the player to click to continue. The Presenter moves the conversation on by itself.

The dialogue

title: Start
---
<<wait 0.5>>
A: hey i made this chat demo
A: it's pretty cool
B: lmao nice
B: does it support options
A: lemme see
-> A: yep
-> A: uh huh
-> A: think so
B: nice, i bet it also supports wrapping text over multiple lines
<<wait 3>>
A: show off
<<wait 1>>
System: Blue has left the chat
===
Line 3 The built-in <<wait>> Command pauses before the first message.
Lines 4–5 Lines from A get a green bubble on the right.
Lines 6–7 Lines from B get a blue bubble on the left.
Lines 9–11 The replies are shown without the A:. The one you pick is then shown as a line from A, because Run Selected Option As Line is on.
Line 12 A long message, which wraps onto more than one line in its bubble.
Line 16 Lines from System use their own bubble, with grey, centred text and no typing dots.
PhoneChat.yarn.

Choosing a bubble for each character

The Characters property on Chat Dialogue View, under Prefabs, is a dictionary from character names to bubble prefabs:

CharacterPrefab
AMessageBubbleA
BMessageBubbleB
SystemMessageBubbleSystem
The Characters dictionary on Chat Dialogue View.

Lines from any other character, or with no character, use Default Bubble Prefab, which is set to MessageBubble.

MessageBubble is the bubble the other three are built from. The A, B and System bubbles are prefab variants of it, so they share its layout and only change what’s different.

MessageBubble Chat Dialogue View BubbleUse Size Of Text
Content Content Size FitterUse Size Of Text
Background ImageLayout Element
Text TextMeshPro - Text (UI)
Typing Indicator Animator
Dot 1 Image
Dot 2 Image
Dot 3 Image
The MessageBubble prefab.
  • MessageBubble and MessageBubbleA use the Bubble Green sprite for Background, and line Content up against the right-hand edge.
  • MessageBubbleB uses the Bubble Blue sprite, lines Content up against the left-hand edge, and flips Background so the bubble’s tail points the other way.
  • MessageBubbleSystem clears the bubble’s Typing Indicator, and makes the text grey and centred.

Typing Indicator has an Animator that plays the looping Typing animation, which moves each dot up and back down in turn.

The bubble’s root and its Content both have a Use Size Of Text component, from the sample’s UseSizeOfText.cs. It’s a layout element that measures the text inside it, and reports that as the size it needs. On the root, that sets the height of the row. On Content, it sets the width of the bubble, so short messages get small bubbles, and longer ones wrap at the width of the list.

Showing a line

ChatDialogueView extends DialoguePresenterBase and overrides RunLineAsync() and RunOptionsAsync(). To see how these fit together, read Building Your Own Presenter.

public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
    if (bubbleContainer == null)
    {
        Debug.LogWarning($"Can't show line '{line.Text.Text}': no bubble container");
        return;
    }

    var prefab = defaultBubblePrefab;

    if (line.CharacterName != null && characters.TryGetValue(line.CharacterName, out var characterBubble))
    {
        prefab = characterBubble;
    }

    if (prefab == null)
    {
        Debug.LogWarning($"Can't show line '{line.Text.Text}': no default bubble was set");
        return;
    }

    int index;

    if (optionsContainer != null)
    {
        index = optionsContainer.GetSiblingIndex();
    }
    else
    {
        index = bubbleContainer.childCount - 1;
    }

    if (showTypingIndicators && prefab.HasIndicator)
    {
        var typingBubble = Instantiate(prefab, bubbleContainer);
        typingBubble.transform.SetSiblingIndex(index);
        typingBubble.ShowTyping();

        var typingDelay = Mathf.Clamp(
            line.TextWithoutCharacterName.Text.Length * typingDelayPerCharacter,
            minimumTypingDelay,
            maximumTypingDelay);

        await YarnTask.Delay(TimeSpan.FromSeconds(typingDelay), token.HurryUpToken).SuppressCancellationThrow();

        Destroy(typingBubble.gameObject);
    }

    var bubble = Instantiate(prefab, bubbleContainer);
    bubble.transform.SetSiblingIndex(index);
    bubble.ShowText(line.TextWithoutCharacterName.Text);

    await YarnTask.Delay(TimeSpan.FromSeconds(delayAfterLine), token.HurryUpToken).SuppressCancellationThrow();
}
Lines 9–14 Start with the default bubble, and use the character’s own bubble if Characters has one for them.
Lines 22–31 Work out where the new bubble goes: just above Options Container, so the replies always stay at the bottom.
Lines 33–47 If the prefab has a typing indicator, add a bubble showing the dots, wait, and then remove it.
Lines 39–42 How long the dots show for: 0.1 seconds for each character in the message, but never less than 1 second or more than 3.
Lines 49–51 Add a new bubble with the line’s text, without the character name.
Line 53 Wait for Delay After Line, 1 second, and return.
RunLineAsync() in ChatDialogueView.cs.

When RunLineAsync() returns, the Dialogue Runner goes on to the next line. That’s why the conversation moves on without any input. The bubbles stay in the list, so you can scroll back up through the conversation.

The dots and delays are set in the Timing group: Delay After Line, Minimum Typing Delay, Maximum Typing Delay, Typing Delay Per Character and Show Typing Indicators.

Keeping the replies at the bottom

Scroll View only scrolls vertically, and has no scroll bars. Content is pinned to the bottom of Viewport, and its Content Size Fitter makes it as tall as everything in it, so it grows upwards as bubbles are added.

Options Container is the last child of Content, the list of bubbles. Every new bubble is put at the position directly above it, so the replies always appear below the newest message.

public override async YarnTask<DialogueOption?> RunOptionsAsync(DialogueOption[] dialogueOptions, LineCancellationToken cancellationToken)
{
    if (optionsContainer == null)
    {
        Debug.LogWarning($"Can't show options: no bubble container");
        return null;
    }

    if (optionsButtonPrefab == null)
    {
        Debug.LogWarning($"Can't show options: no bubble prefab");
        return null;
    }

    for (int i = 0; i < optionsContainer.childCount; i++)
    {
        Destroy(optionsContainer.GetChild(i).gameObject);
    }

    var completionSource = new YarnTaskCompletionSource<DialogueOption>();

    foreach (var option in dialogueOptions)
    {
        var button = Instantiate(optionsButtonPrefab, optionsContainer);
        button.Text = option.Line.TextWithoutCharacterName.Text;

        button.OnClick = () => completionSource.TrySetResult(option);
    }

    var selectedOption = await completionSource.Task;

    for (int i = 0; i < optionsContainer.childCount; i++)
    {
        Destroy(optionsContainer.GetChild(i).gameObject);
    }

    return selectedOption;
}
Lines 15–18 Clear any buttons left over from last time.
Line 20 A completion source that a button completes when it’s clicked.
Lines 22–28 Add an Option Button for each option, with the option’s text without the character name.
Lines 30–37 Wait for a click, clear the buttons, and return the picked option to the Dialogue Runner.
RunOptionsAsync() in ChatDialogueView.cs.

The Option Button prefab’s Button calls OnClicked() on its Chat Dialogue View Options Button component, which runs the OnClick the Presenter gave it.

The picked option then runs as a line from A, because Run Selected Option As Line is on, so it appears in the conversation as a green bubble.

Things to try

  • Select Chat Dialogue View and turn off Show Typing Indicators. Each message appears without the typing dots first.
  • Select Dialogue System and turn off Run Selected Option As Line. The reply you pick no longer appears as a bubble.
  • Add a line from a new character, such as C: hello, to PhoneChat.yarn. It isn’t in Characters, so it uses the default green bubble on the right.
Next step Options That Timeout A custom Options Presenter that picks an option for the player when a countdown bar runs out.