Building a Custom UI
ConvoCore includes a ready-made sample UI in the Samples package that demonstrates all of these patterns in a working scene. If you prefer a hands-on starting point over building from scratch, import the Sample UI first and read its code alongside this guide.
This page walks through creating a complete, working dialogue UI for ConvoCore from scratch. By the end you will have a UI that displays the speaker's name and dialogue text, handles player input to advance lines, and presents branching choices.
Prerequisites
- ConvoCore is installed and a
ConvoCoreConversationDataasset exists with parsed dialogue lines. - TextMeshPro is installed (Window → Package Manager → TextMeshPro). The code examples on this page use TMP - see the note below if you plan to use a different text system.
- A scene is open with a
ConvoCorecomponent on a GameObject.
The examples on this page use TextMeshPro (TMP_Text, TMP_Dropdown, etc.). If you have not installed it, go to Window → Package Manager, find TextMeshPro, and click Install.
TextMeshPro is not a hard dependency of ConvoCore - the framework has no TMP references. You can build your UI using standard Unity UI Text, UI Toolkit, or any other system. The TMP_Text references in the code below are purely a choice for the examples.
Step 1: Create the MonoBehaviour
Create a new C# script named MyDialogueUI.cs. Replace its contents with the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using WolfstagInteractive.ConvoCore;
using WolfstagInteractive.ConvoCore.UI;
public class MyDialogueUI : ConvoCoreUIFoundation
{
[SerializeField] private GameObject _dialoguePanel;
[SerializeField] private TMP_Text _speakerNameText;
[SerializeField] private TMP_Text _dialogueText;
[SerializeField] private Button _advanceButton;
[SerializeField] private Transform _choiceContainer;
[SerializeField] private Button _choiceButtonPrefab;
private bool _playerAdvanced;
private void Awake()
{
_advanceButton.onClick.AddListener(OnAdvanceClicked);
}
protected override void InitializeUI(ConvoCore runner)
{
_dialoguePanel.SetActive(true);
_playerAdvanced = false;
}
protected override void UpdateDialogueUI(
DialogueLineInfo lineInfo,
string localizedText,
string speakerName,
CharacterRepresentationBase representation,
ConvoCoreCharacterProfileBaseData primaryProfile)
{
_speakerNameText.text = speakerName;
_dialogueText.text = localizedText;
// Apply the character's name color if the profile provides one.
if (primaryProfile != null)
_speakerNameText.color = primaryProfile.CharacterNameColor;
_playerAdvanced = false;
}
protected override IEnumerator WaitForUserInput()
{
_playerAdvanced = false;
yield return new WaitUntil(() => _playerAdvanced);
}
protected override IEnumerator PresentChoices(
List<ChoiceOption> options,
List<string> localizedLabels,
ChoiceResult result)
{
result.SelectedIndex = -1;
// Hide the advance button while choices are shown.
_advanceButton.gameObject.SetActive(false);
// Instantiate one button per choice.
for (int i = 0; i < localizedLabels.Count; i++)
{
int capturedIndex = i;
Button btn = Instantiate(_choiceButtonPrefab, _choiceContainer);
btn.GetComponentInChildren<TMP_Text>().text = localizedLabels[i];
btn.onClick.AddListener(() => result.SelectedIndex = capturedIndex);
}
// Wait until the player selects a choice.
yield return new WaitUntil(() => result.SelectedIndex >= 0);
// Clean up choice buttons.
foreach (Transform child in _choiceContainer)
Destroy(child.gameObject);
_advanceButton.gameObject.SetActive(true);
}
protected override void HideDialogue()
{
_dialoguePanel.SetActive(false);
}
private void OnAdvanceClicked()
{
_playerAdvanced = true;
RequestAdvance?.Invoke();
}
}
Step 2: Build the UI Hierarchy
In your scene, create a Canvas (right-click in the Hierarchy → UI → Canvas). Inside it, build the following structure:
Canvas
└── DialoguePanel (Panel image, anchored to bottom of screen)
├── SpeakerNameText (TMP_Text)
├── DialogueText (TMP_Text, set to wrap, auto-size off)
├── AdvanceButton (Button with a TMP_Text child labeled "Continue")
└── ChoiceContainer (empty GameObject, add a VerticalLayoutGroup component)
Suggested layout for the DialoguePanel:
- Anchor: stretch horizontally, pin to bottom
- Height: approximately 200–250 px
- Add a background Image component with a semi-transparent dark color
ChoiceContainer settings:
- Add a
VerticalLayoutGroupcomponent - Enable Control Child Size (Width and Height)
- Enable Child Force Expand (Width)
- Set spacing to 8
Step 3: Create a Choice Button Prefab
Create a simple prefab for choice buttons:
- In the Hierarchy, add a Button (right-click → UI → Button - TextMeshPro).
- Set the button's minimum height to 40 px via a
LayoutElementcomponent. - Rename the TMP_Text child to
Label. - Drag the button from the Hierarchy into the Project panel to create a prefab.
- Delete the instance from the Hierarchy.
Style the choice button prefab with a visible background and hover highlight so players can clearly see their options. The VerticalLayoutGroup on ChoiceContainer handles spacing and sizing automatically.
Step 4: Wire It Up in the Inspector
- Add
MyDialogueUIto theDialoguePanelGameObject using Add Component. - Fill in all serialized fields:
- Dialogue Panel → the
DialoguePanelGameObject - Speaker Name Text → the
SpeakerNameTextTMP_Text - Dialogue Text → the
DialogueTextTMP_Text - Advance Button → the
AdvanceButtonButton - Choice Container → the
ChoiceContainerTransform - Choice Button Prefab → the Button prefab created in Step 3
- Dialogue Panel → the
- Select the GameObject that has the
ConvoCorecomponent. - Drag the
DialoguePanelGameObject (which now hasMyDialogueUIon it) into the Conversation UI field on theConvoCorecomponent.
Step 5: Hide the Panel at Start
The dialogue panel should be hidden until a conversation begins. Either:
- Uncheck the
DialoguePanelGameObject's active checkbox in the Hierarchy, or - Add
_dialoguePanel.SetActive(false)to anAwake()orStart()method in your UI script
InitializeUI() will re-enable it when a conversation starts.
Step 6: Add Keyboard Input (Optional)
To support keyboard or mouse-click advancement in addition to the button:
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
{
// Only advance if a choice is not currently being presented.
if (_choiceContainer.childCount == 0)
{
_playerAdvanced = true;
RequestAdvance?.Invoke();
}
}
}
Add this method to MyDialogueUI. The guard condition (childCount == 0) prevents keyboard input from interfering while choice buttons are visible.
For keyboard input, prefer GetKeyDown over GetKey; GetKey fires every frame and would skip lines too fast.
Step 7: Test It
Press Play. Trigger the conversation (via StartConversation() or the starter script from the Quick Start guide). You should see:
- The dialogue panel appear.
- The speaker's name and first line of text display.
- Clicking the advance button (or pressing Space) moves to the next line.
- At branch points, choice buttons appear. Clicking one selects that branch.
- After the last line, the panel disappears.
Troubleshooting
Text appears but never updates after the first line: Check that UpdateDialogueUI() is declared with the override keyword. Without it, Unity will not call your implementation; it will call the empty base method (which does nothing) silently.
Conversation completes instantly with no text visible: WaitForUserInput() is returning immediately. Make sure it sets _playerAdvanced = false at the top and yields on WaitUntil(() => _playerAdvanced). A common mistake is missing the override keyword, so the empty base method (it does nothing) runs instead.
Choice buttons appear but clicking them does nothing: Check that the capturedIndex variable is captured inside the loop with a local copy. Without int capturedIndex = i, all lambdas reference the same i variable and will all read the post-loop value.
No text at all (panel is visible but blank): Check the Console for YAML parse errors. Open the ConvoCoreConversationData asset and confirm the YAML was validated successfully (right-click → Force Validate Dialogue Lines).
Next Steps
| I want to… | Go here |
|---|---|
| Add a scrollable transcript of past lines | Dialogue History → |
| Display character portraits alongside the text | Character Representations → |
| Handle language switching in the UI | Localization → |
| Save and restore conversation progress | Save System → |