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.
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
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
===<<wait>> Command pauses before the first message.A:. The one you pick is then shown as a line from A, because Run Selected Option As Line is on.Choosing a bubble for each character
The Characters property on Chat Dialogue View, under Prefabs, is a dictionary from character names to bubble prefabs:
| Character | Prefab |
|---|---|
A | MessageBubbleA |
B | MessageBubbleB |
System | MessageBubbleSystem |
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.
MessageBubbleandMessageBubbleAuse theBubble Greensprite for Background, and lineContentup against the right-hand edge.MessageBubbleBuses theBubble Bluesprite, linesContentup against the left-hand edge, and flips Background so the bubble’s tail points the other way.MessageBubbleSystemclears 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();
}Options Container, so the replies always stay at the bottom.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;
}Option Button for each option, with the option’s text without the character name.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 Viewand turn off Show Typing Indicators. Each message appears without the typing dots first. - Select
Dialogue Systemand 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, toPhoneChat.yarn. It isn’t in Characters, so it uses the default green bubble on the right.