Create a Phone Chat View
Last updated
Was this helpful?
Last updated
Was this helpful?
The Phone Chat sample demonstrates how to create a Dialogue Presenter that shows conversations in a scrolling view, similar to how messages on a phone look. Lines of dialogue are shown as bubbles in the scrolling view, and are kept on screen after they're delivered, allowing the user to look up and see previous messages. Additionally, as lines appear, they're shown with a "typing" indicator.
In this tutorial, you'll build a Dialogue Presenter that looks like this:
We'll start by creating a new empty Unity project, and adding Yarn Spinner.
Open Unity Hub.
Click New Project, and create a new project. In this tutorial, we'll use the Universal 3D template, though the template itself doesn't really matter.
Next, we'll download the assets needed by this tutorial.
We're ready to start creating our scene!
Now that we've created the project, we'll begin by laying out the UI. We'll start from an empty scene, and build a canvas that will present the Phone Chat system.
Open the File menu, and choose New Scene. Select the Empty template.
Save the new scene somewhere in your project.
In our empty scene, we'll start by creating the camera that you view the scene with.
Create a new camera by opening the GameObject menu and choosing Camera.
Select the new camera, and in the Inspector, right click the Transform, and choose Reset.
Next, we'll create the canvas that the Phone Chat Presenter appears in.
Open the GameObject menu, and choose UI -> Canvas.
Select the new canvas in the Hierarchy, and find the Canvas Scaler component.
Set its UI Scale Mode to "Scale with Screen Size".
Set its Reference Resolution to 1920 by 1080.
We're now ready to start building the UI itself. The most important part of a scrolling chat view is the scroll view, so let's create that!
Open the GameObject menu, and choose UI -> Scroll View.
Select the newly created Scroll View in the Hierarchy.
In the Inspector, find the Transform component.
Set the Pos X and Pos Y values to 0,0.
Set the Width to 600, and the Height to 900.
Our scroll view won't need any scroll-bars, so we'll get rid of them now. We'll also set up the scroll view so that it only scrolls vertically.
Select the Scroll View object in the Hierarchy.
In the Inspector, find the Scroll Rect component.
Select the Horizontal Scrollbar field, and press Backspace to clear it. Do the same thing for the Vertical Scrollbar field.
Turn off the Horizontal checkbox, so that it doesn't scroll horizontally.
Delete the Scrollbar Horizontal and Scrollbar Vertical objects from the scene.
Next, we'll set up the scroll view to use our background image.
In the Inspector, find the Image component.
Change the Color property to fully opaque white.
Change the Source Image property to the BG-Lightmode
sprite.
Because we removed the scroll bars, we'll need to make the viewport of the scroll view fill the available area:
Select the Viewport object in the Hierarchy.
In the Transform component, set the Left, Right, Top and Bottom values all to 0.
We're almost done setting up the scroll view. The next thing to do is to make the content in the scroll view appear at the bottom of the view. We'll do this by setting up the Anchors of the Content view so that it's pinned to the bottom of its parent. This means that as bubbles get added to the Content object, the content object will grow upwards (rather than downwards).
Select the Scroll View -> Viewport -> Content object in the Hierarchy.
Find the Transform component in the Inspector.
Set the Anchors -> Min to X: 0, Y: 0.
Set the Anchors -> Max to X: 1, Y: 0.
Set the Pivot to X: 0.5, Y: 0.
We'll now make all of the content in the Scroll View automatically laid out in a vertical list.
With the Content object still selected, click Add Component at the bottom of the Inspector.
Search for "Vertical Layout Group", and add one to the object.
Set the new Vertical Layout Group's Spacing to -10.
Set its Child Alignment to Lower Center.
Check the Control Child Size -> Width and Height boxes.
Check the Child Force Expand -> Width box, and uncheck the Height box.
This will cause the items in the list to fill the entire width of the list. The height of each of the elements will be determined by the items themselves, using a component we'll add later.
We'll now also make the content update its own total size based on its contents.
Click the Add Component button at the bottom of the Inspector.
Search for "Content Size Fitter" and add one to the object.
Set the Horizontal Fit of the new Content Size Fitter to Unconstrained.
Set the Vertical Fit to Preferred Size.
Next, we'll create the bubble itself.
The goals for the bubble are that the text fits in the available width, and it takes up as much height as it needs given that width. We also want to show a speech bubble image behind the text, and we want that image to only be as wide as the text - it shouldn't take up the full width of the chat view.
This mimics the way that conversations work in popular chat apps: the speech bubble graphic doesn't take up the full width of the row that it exists in.
Select the Content object in the Hierarchy.
Open the GameObject menu, and choose Create Empty Child.
Name the newly created object "Message Bubble".
Because it's a child of the Vertical Layout Group we added earlier, it will automatically take up the full width of the list. We'll also want the bubble to have the correct height, based on the amount of text it has, but because we haven't set up the text component yet, that isn't available. In the meantime, we'll manually set up the bubble to have a fixed height, and we'll replace this later.
Select the Message Bubble object.
Add a new Layout Element component to the object.
Check the Min Height box, and set its value to 200.
We'll now add an object that manages the width of the bubble graphic.
Create a new empty child game object of the Message Bubble.
Name the new object "Message Content".
Add a Layout Element component to it, and set its Min Width to 300.
Add a Content Size Fitter component to it, and set its Horizontal Fit to Min Size.
Next, we'll make the bubble be right-aligned inside its content, and fill the entire height of the row.
With the Message Content object selected, go to the Transform component.
Set the Anchors -> Min to X: 1, Y: 0.
Set the Anchors -> Max to X: 1, Y: 1.
Set the Pivot to X: 1, Y: 0.5.
Set Pos X, Top and Bottom all to 0.
Now we'll add the bubble background image itself. We'll add a new object, set up its anchors so that they fill the parent object, and then add the image.
Add a new empty child object of Message Content.
Name the new object "Background".
In the Transform, set the Anchors -> Min to X: 0, Y:0.
Set the Anchors -> Max to X: 1, Y:1.
Set the Left, Top, Right and Bottom values all to 0.
Add a new Image component.
Set the Source Image of the new component to Bubble Green.
Your view should now look like this:
Now we'll add the text component, which will show the text of the image. The text object's anchors will be set up so that they're pinned to all for edges of the container, making it fill up the parent object.
Add a new empty child object of Message Content. Name it "Text".
Set its Anchors -> Min to X:0, Y:0.
Set its Anchors -> Max to X:1, Y:1.
Set its Left, Top, Right, Bottom all to 0.
Add a TextMeshPro - Text (UI) component to the object.
Set the Font to Montserrat-Regular. (This is one of the assets that you downloaded when you began this tutorial.)
Set the Text to something temporary, like "This is a test".
Scroll down to Extra Settings, and click it to expand it.
Set the Margins to:
Left: 55
Top: 30
Right: 55
Bottom: 35
The bubble should now look like this:
The bubble now has the right graphics, but its size could be improved. We'll now add some code that makes the various parts of the bubble correctly sized. To get the best result, we need to think about the overall layout here:
The Vertical Layout group is controlling the width of the Message Bubble element, and we want to make each Message Bubble control its own height.
Inside the Message Bubble, we also want the visible bubble to be as wide as it needs to be to show the text, but no wider than that (that is, there should be a gap on the side).
To make this work, we'll create a component that calculates the required size of the text box, and use it in two places:
The Message Bubble will use it to calculate its height (with its width being driven by the Vertical Layout Group on Content).
The Message Content will use it to calculate its width (with its height inherited from its parent).
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "UseSizeOfText".
Double-click the new script to open it in your text editor.
Delete the contents of the file and replace it with this:
This code implements a basic layout element, which is something that the layout system can ask questions of about what size it wants to be. - we'll now add code to it to make it derive its size from the text element.
Add the following method to the UseSizeOfText
class:
This method takes information describing the text in a TextMeshPro element, calculates the smallest size it needs to be given its constraints, and asks the layout system to rebuild the layout.
Add the following methods to UseSizeOfText
:
Now that we've built a component that notifies the layout system of the necessary size of the text, we'll use it in our setup.
Select the Message Content object in the Hierarchy.
Remove the Layout Element component from it.
Add a new Use Size Of Text component.
The bubble will resize to fit the width of the text. We now need to make the row take the correct height.
Select the Message Bubble object in the Hierarchy.
Remove the Layout Element component from it.
Add a new Use Size Of Text component.
Your bubble should now look like this:
When you're using a chat app, you see a typing indicator while the person on the other end is typing. Let's set up our chat bubbles so that they can show an animation for a short duration before revealing the text, as though the messages was being "typed out".
The way that the typing animation will look will be three dots, which bounce up and down in sequence.
Our first step will be to set up an object inside the Message Bubble prefab to act as our typing indicator. This object will act as a container for the three dots, so it needs to fill the entire parent object.
Create a new empty game object as a child of Message Content. Name it "Typing Indicator".
In the Inspector for the Transform component, set its Anchor Min to X:0, Y:0.
Set its Anchor Max to X:1, Y:1.
Set its Top, Left, Right, and Bottom all to 0.
Let's now add the dots themselves!
Add three new empty game objects as children of Typing Indicator. Name them Dot 1
, Dot 2
, Dot 3
.
Select all three of the Dot objects you just created.
Add an Image component to all of them.
Drag and drop the Dot sprite from the downloaded assets into the Source Image slot.
Click on the Color value, and change the Alpha value to 128, to make it semi-transparent.
Set the Pos Y for all of them to 0.
Set the Width and Height for all of them to 20.
Now that we've set up all of their common properties, we'll lay each of them out.
Select Dot 1, and set its Pos X to -25.
Select Dot 2, and set its Pos X to 0.
Select Dot 3, and set its Pos X to 25.
Finally, we'll set them up so that they bounce up and down while visible.
Open the Assets menu, and choose Create -> Animation -> Animation Clip.
Name the new animation clip "Typing".
Select the new animation clip, and in the Inspector, turn on Loop Time.
Drag and drop this new clip onto the Typing Indicator object.
This will create a new Animator Controller that uses this clip, and then add an Animation component to the Loading Indicator that uses the controller.
Next, we'll set up the animation itself.
Open the Window menu, and choose Animation -> Animation. (You may want to dock the tab in your Unity editor window, if it isn't already.)
Select the Loading Indicator object.
The Animator window should be showing the Typing animation. You can check by looking at the dropdown menu at the top-left of the pane.
In the Animator window, Click Add Property.
Choose Dot 1 -> Rect Transform -> Anchored Position.
Repeat this process for Dot 2 and Dot 3.
By default, the newly created channels will have a keyframe at frame 0, and another at frame 60.
Select the keyframe at frame 60 and press Ctrl-C.
Move the playhead to frame 30 and press Ctrl-V, to make a copy of it.
We'll now create a new keyframe that represents the high point of each dot's bounce.
Move the playhead to frame 15.
Click the Record button at the top left of the Animation pane.
In the Scene view, drag all three Dot objects up so that their Pos Y is 25.
Click the Record button again to leave recording mode.
Click the Preview Play button in the Animation window and watch as they all bounce up together.
Now that the dots are moving, we'll adjust the timing so that they bounce up in sequence - first one, then two, then three.
Select all of the keyframes for Dot 2 and shift them 30 frames to the right.
Select all of the keyframes for Dot 3 and shift them 60 frames to the right.
Click the Preview Play button again and watch as they bounce up in sequence.
Looking good!
We're done with our basic bubble. Let's now create prefabs for it.
Drag the Message Bubble object from the Hierarchy into the Project tab, creating a Prefab.
This will be our base prefab. We'll create two variants of it: one for each of our two characters.
Delete the Message Bubble object from the scene.
Select the Message Bubble prefab you just created.
Open the Assets menu, and choose Create -> Prefab Variant.
Name the new prefab Message Bubble A.
Repeat the above two steps, creating a new prefab variant called Message Bubble B.
Drag Message Bubble A into the Content object in the hierarchy, creating an instance of it in the scene.
Drag Message Bubble B into the Content object as well.
In our phone chat system, we want Message Bubble A to use a green graphic and be right-aligned, and Message Bubble B to use a blue graphic and be left-aligned. Message Bubble A is already what it needs to be, so we just need to make some changes to Message Bubble B. The only things we need to change are the anchors - Bubble A is pinned to the right side, and we'll need to adjust it so that it's pinned to the left.
Select the Message Content object in Bubble B.
Change its Anchors Min to X: 0, Y:0.
Change its Anchors Max to X: 0, Y:1.
Change its Pivot to X: 0, Y:0.5.
Change its Pos X to 0.
The bubble will now be aligned to the left of the box, but its background image is pointing the wrong way.
Select the Background object in Message Bubble B.
In the Transform component, set its Scale X to -1.
The bubble will now be facing the right way. Lastly, we'll make the bubble use a different colour image.
With the Background object still selected, change the Source Image in its Image component to Bubble Blue.
The two bubbles should now look like this:
We're all done with setting up these bubbles for displaying text, though we'll return to them later at the very end of this tutorial. Let's apply these changes to the prefab overrides.
Select the Message Bubble B object
In the Inspector, click Overrides, and then click Apply All.
This will apply these changes to Bubble B prefab variant (without modifying the A variant or the base prefab).
Delete the Message Bubble A and Message Bubble B objects from the scene.
Now that we've set up the prefabs for the line views, let's set up the UI for options. When the Dialogue Presenter needs to show options, we'll create a list of buttons that contain the text of each option.
We'll start by creating the object that will contain all of the buttons.
Create a new empty object under Content, and call it Options Container.
Add a Vertical Layout Component to it.
Set its Padding Top to 8, and Padding Bottom to 16.
Set its Spacing to 16.
Turn on Control Child Size Width, and Child Force Expand Width.
This will create a new item in the scrolling list that is itself a vertical list. Now that we've built the layout for the options, let's create the button itself.
Create a new empty object under Options Container, and call it Option Button.
In the Transform component in its Inspector, set its Height to 60.
Now we'll add the image for the button. The button image itself will need to fill the entire parent, so we'll set up the anchors to do that.
Add a new empty object under Option Button, and call it Background.
Set its Anchor Min to X: 0, Y: 0
Set its Anchor Max to X: 1, Y: 1.
Set its Top and Bottom to 0, and its Left and Right to 20.
Add an Image component.
Set the Images component's Source Image to Option-NotSelected.
Next, the component that shows the text of the option. Again, this will fill the parent.
Add a new empty object under Option Button, and call it Text.
Set its Anchor Min to X: 0, Y: 0
Set its Anchor Max to X: 1, Y: 1
Set its Top, Bottom, Left and Right all to 0.
Add a TextMeshPro - Text (UI) component to the object.
Set its Vertex Color to black.
Set its Alignment to Centered Middle.
Set its font to Montserrat-Regular.
Click Extra Settings to expand it.
Set its Left and Right margins to 30.
We'll now configure the Text component to try to fit more text on a single line, where needed.
Set Text Wrapping Mode to No Wrap, and set Overflow to Ellipsis.
Turn on Auto Size, and set its Min Size to 24, its Max to 36, and its WD% to 10.
This will cause it to condense the font slightly if it needs the space, and to shrink the font size if it still needs more space. If it still can't fit the text, it will truncate it with an ellipsis (...) rather than overflow onto a new line. With these settings, an option's text can be about 40 characters long before it truncates.
Finally, we'll add a Button component, which will do two things: it'll make the button change its sprite as the user interacts with it, and it will also (eventually) tell the Dialogue Presenter that the user has chosen this option when clicked or tapped. We'll set up the Button component to change the sprite drawn in the Background object based on the current interaction state of the button.
With the Option Button selected, Add a Button component.
Set the Transition to Sprite Swap.
Drag and drop the Background object into the Target Graphic field.
Set Highlighted Sprite to Option-Selected.
Set Pressed Sprite to Option-Pressed.
Set Selected Sprite to Option-Selected.
Your button should now look like this:
We're all done setting up the visuals of the button. The last step is to turn it into a prefab so that we can instantiate copies of it later!
Drag and drop the Option Button into the Project pane to create a prefab.
Delete the original Option Button object from the scene.
We're all set to start wiring this UI up to a Dialogue Presenter!
We'll start from the bottom up - we'll create scripts that manage the contents of the bubbles and the option buttons, and then create the Dialogue Presenter to work with these scripts.
Let's start with the code for the bubbles. This will be a simple script that can be told to show some text, or show a typing indicator.
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "ChatDialoguePresenterBubble".
Double-click the new script to open it, and replace its contents with the following code.
Now we just need to add this script to our bubbles. Because our Message Bubble A and Message Bubble B prefabs are variants, all we need to do is just add it to the base prefab, and it will appear on all of them.
Select the Message Bubble prefab in the Project pane.
Click Open at the top right of the Inspector to open the prefab for editing.
Add a ChatDialoguePresenterBubble
component to the Message Bubble object.
Drag and drop the Typing Indicator into the Typing Indicator field.
Close the prefab and go back to the scene.
Next, we'll create and set up the code for the Option Button. This script needs to both show text to the user, and also notify the rest of the system that the button has been pressed.
Open the Assets menu, and choose Create -> MonoBehaviour Script.
Name the new script "ChatDialoguePresenterOptionsButton".
Double-click the new script to open it, and replace its contents with the following code.
Now that the code's been written, we'll add it to the Option Button prefab.
Select the Option Button prefab in the Project pane.
Click Open at the top right of the Inspector to open the prefab for editing.
Add a Chat Dialogue Presenter Options Button component to the Option Button object.
In the Button component, add a new entry to the On Click event.
Drag and drop the Option Button object from the Hierarchy into the new event's object field.
Change the method from 'No Function' to 'ChatDialoguePresenterOptionsButton -> OnClicked()'.
Close the prefab and go back to the scene.
With this, our bubble and button prefabs are ready for use with the Dialogue Presenter!
We're now finally ready to create the Dialogue Presenter itself!
Open the Create menu, and choose Yarn Spinner -> Dialogue Presenter Script.
Name the new script "ChatDialoguePresenter".
Double click the new script to open it in your text editor.
The default Dialogue Presenter template script contains an empty implementation of all of the necessary methods needed to be a Dialogue Presenter. We'll start adding functionality to it, piece by piece.
Add the following variables to the ChatDialoguePresenter
class.
Our code doesn't need to take any special action when dialogue starts and ends, so we'll replace them with simple methods that just return immediately.
Replace the OnDialogueStartedAsync
and OnDialogueCompleteAsync
methods with the following code:
Now it's time to start building the actual behaviour. We'll implement the RunLineAsync
method, which is called when the Dialogue Runner needs to show a line of dialogue to the user. This method will look at the line, figure out which bubble prefab needs to be used, and then adds a copy of that prefab to the list and makes the bubble show the right content.
Replace the RunLineAsync
method with the following code.
With this done, our Dialogue Presenter is able to show lines of dialogue in the bubble!
We'll now create the Dialogue Runner. Yarn Spinner comes with a simple menu item that creates a basic set of Dialogue Presenter, but because we've already built our own custom UI already, we don't need to use it. Instead, we'll create one from scratch.
Create a new empty object called "Dialogue Runner".
Add a Dialogue Runner component to the object.
Add a new empty game object as a child, and call it "Phone Chat Presenter".
Add a ChatDialoguePresenter component to Phone Chat Presenter.
Drag the Message Bubble prefab into the Default Bubble Prefab field. This will be the prefab that's used whenever there's no specific prefab to use for a character.
Drag the Content object into the Bubble Container field.
While we're here, we'll also set up the prefabs that will be used for options.
Drag the Option Button prefab into the Options Button Prefab field.
Drag the Options Container object into the Options Container field.
We're ready to test! Let's create some dialogue and hook it up.
We'll start by creating a Yarn Project, and a single Yarn Script containing some sample dialogue.
Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Project.
Name the new file "Phone Chat Project".
Open the Assets menu, and choose Create -> Yarn Spinner -> Yarn Script.
Name the new file "Phone Chat".
Next, let's write some dialogue.
Open the Phone Chat script and replace its contents with the following:
Finally, we'll connect this project up to the Dialogue Runner we made in the previous section.
Select the Dialogue Runner object in the Hierarchy.
Drag and drop the Phone Chat Project that you just created into the Yarn Project field.
Turn on Start Automatically. (Leave the Start Node as 'Start'.)
The very last step is to add our Dialogue Presenter to the Dialogue Runner, so that it receives content and shows it to the user.
Add a new entry in the Dialogue Presenters list.
Drag in the Phone Chat Presenter object into the new field.
We're finally ready to see it all in action!
Click the Play button at the top of the Unity window. The conversation will play out!
This looks good, but there's some room for polish. The first thing to note is that all of the lines use the same bubble, and there's no way to tell who's saying what. Remember that we created two prefabs - the green one that we called 'A', and the blue one we called 'B'. Let's set it up so that lines spoken by character 'A' use our 'A' prefab, and likewise for 'B' and the 'B' prefab.
Select the Phone Chat Presenter in the Hierarchy.
In the Inspector, click the +
button next to the Characters field to add an entry to the Characters dictionary.
Set the key of the new entry to "A", and drag the Message Bubble A prefab into the Object slot.
Repeat this process, creating a new entry "B" that uses the Message Bubble B prefab.
Play the game again.
Lines from "B" will use a different-looking bubble!
We've got lines working just fine, but we don't currently have support for options. Let's fix that! We'll start by adding some actual options to our sample dialogue, and then we'll modify our Dialogue Presenter so that it knows what to do when options arrive.
Open the Phone Chat.yarn
, and replace its contents with the following:
Open the ChatDialoguePresenter.cs
file, and replace the RunOptionsAsync
method with the following code:
One more thing before we test it out: when the user taps on an option to select it, we'll run the contents of that option as though it had been a line. (Without this, there's no record of what the player selected, which feels strange.)
Select the Dialogue Runner in the Hierarchy.
In the Dialogue Runner component's Inspector, turn on Run Selected Option As Line.
We're all set to see it in action.
Play the game. After the lines appear, option buttons will run!
We're all done with our Phone Chat Dialogue Presenter! Try writing some more complex conversations, swap out the sprites to create a new theme, create new bubbles, or use this code to create a scrolling log of messages that isn't phone themed!
Follow the steps in to install Yarn Spinner in your project.
Download the , and import them into your project.
Lastly, we need the layout to update whenever the text changes. To do this, we'll make use of the event, which is called immediately before the text in a TextMeshPro component is rendered as a mesh. When this happens, it's likely that our required size has changed, so we tell the layout to update.