Displaying Dialogue
This guide covers how to read dialogue state from the StoryFlow component and display it in your game UI. You will learn about the dialogue state class, three approaches for building your dialogue UI, and how variable interpolation works.
The Dialogue State
Every time the runtime reaches a dialogue node, the component builds a StoryFlowDialogueState object containing all the information you need to render the dialogue. This object is passed to event subscribers and is available on demand via GetCurrentDialogue().
[Serializable]
public class StoryFlowDialogueState
{
// The ID of the current dialogue node
public string NodeId;
// Resolved title text with variable interpolation applied
public string Title;
// Resolved dialogue body text with variable interpolation applied
public string Text;
// Dialogue image (Sprite) - persists between nodes unless explicitly changed
public Sprite Image;
// Audio clip assigned to this dialogue node
public AudioClip Audio;
// The speaking character (name, portrait, per-character variables)
public StoryFlowCharacterData Character;
// Non-interactive text segments displayed alongside the dialogue
public List<StoryFlowTextBlock> TextBlocks;
// Clickable options the player can choose from
public List<StoryFlowOption> Options;
// Whether this state represents an active, valid dialogue
public bool IsValid;
// True when dialogue has a connected output but no options.
// Use this to show a "Continue" button.
public bool CanAdvance;
// Whether the dialogue audio should loop continuously
public bool AudioLoop;
// Whether previous audio should be stopped when this node has no audio
public bool AudioReset;
// Whether the dialogue should auto-advance when audio finishes playing
public bool AudioAdvanceOnEnd;
// Whether the player can skip past audio-gated advances
public bool AudioAllowSkip;
}
The Character field uses the StoryFlowCharacterData class:
[Serializable]
public class StoryFlowCharacterData
{
public string Name;
public string ImageAssetKey;
public Sprite Image;
public Dictionary<string, StoryFlowVariant> Variables;
public List<StoryFlowVariable> VariablesList;
}
Each entry in Options is a StoryFlowOption:
- Id (
string) - Unique identifier for the option, used when callingSelectOption() - Text (
string) - Display text with variable interpolation already applied - IsOnceOnly (
bool) - Whether the option disappears after being selected - IsSelected (
bool) - Reserved field. Currently not populated by the runtime (alwaysfalse). - InputType (
string) - The input type if this is a typed input option (e.g."string","integer","boolean"), or empty for standard buttons - DefaultValue (
string) - The default value for input options
Each entry in TextBlocks is a StoryFlowTextBlock:
- Id (
string) - Unique identifier for the text block - Text (
string) - Display text with variable interpolation already applied
Image Persistence
The Image field persists across dialogue nodes. If a previous dialogue set an image and the current dialogue does not specify one, the previous image remains. This lets you set a background image once and have it carry through a sequence of dialogues without repeating it on every node.
Building Your UI
There are three approaches to building your dialogue UI in Unity, ranging from zero code to full custom control.
Using the Default UI
If no DialogueUI is assigned to the StoryFlowComponent, a built-in fallback UI is automatically created at runtime. This means you can start testing dialogue immediately without any UI setup at all - just add a StoryFlowComponent, assign a script, and call StartDialogue().
The auto-created fallback UI renders dialogue text, character names, option buttons, and a continue button using a basic uGUI layout. It is intended for rapid prototyping and testing.
Explicit Default UI
You can also use StoryFlowDefaultDialogueUI explicitly if you want more control over the layout. This is a uGUI-based MonoBehaviour that extends StoryFlowDialogueUI and lets you wire up your own panel, text fields, and button prefabs in the Inspector. Drag it into the DialogueUI field on your StoryFlowComponent to use it instead of the auto-created fallback.
StoryFlowDefaultDialogueUI Inspector references:
- dialoguePanel - Root
GameObjectthat is shown/hidden when dialogue starts/ends - titleText -
TextMeshProUGUIfor the dialogue title (optional) - bodyText -
TextMeshProUGUIfor the dialogue body text - characterNameText -
TextMeshProUGUIfor the speaker's name (optional) - characterPortrait -
Imagefor the speaker's portrait (optional) - dialogueImage -
Imagefor the dialogue-specific image (optional) - backgroundImage -
Imagefor the persistent background image (optional) - optionsContainer -
Transformwhere option buttons are spawned as children - optionButtonPrefab -
GameObjectprefab with aButtoncomponent and aTextMeshProUGUIchild - continueButton -
Buttonshown when dialogue has no options (callsAdvanceDialogue())
Prototype Only
Both the auto-created fallback and StoryFlowDefaultDialogueUI are intended as quick-start prototypes. For production games, extend StoryFlowDialogueUI with your own visuals, animations, and layout (see below).
Custom UI with Base Class
The plugin provides StoryFlowDialogueUI, an abstract MonoBehaviour base class designed to be extended. When you assign your subclass to the component's DialogueUI field, it automatically subscribes to dialogue events and manages the binding lifecycle.
Override these methods to build your UI:
HandleDialogueUpdated(StoryFlowDialogueState state)- Called every time the dialogue state changes. Rebuild your text, options, and character display here.OnDialogueStarted()- Called when dialogue execution begins. Show your panel or play an intro animation.OnDialogueEnded()- Called when dialogue finishes. Hide your panel or play an outro animation.OnVariableChanged(StoryFlowVariable variable, bool isGlobal)- Called when a variable changes. Useful for updating HUD elements.OnBackgroundImageChanged(Sprite backgroundImage)- Called when a SetBackgroundImage node fires.
Built-in convenience methods (call from your subclass):
SelectOption(string optionId)- Submit a player choice to the component.AdvanceDialogue()- Advance past a narrative-only dialogue (whenCanAdvanceis true).InputChanged(string optionId, string value)- Send a typed input value change.GetCurrentDialogue()- Returns the currentStoryFlowDialogueStateat any time.IsDialogueActive()- Returns whether a dialogue is currently running.GetLocalizedString(string key)- Look up a localized string from the project's string tables.
using StoryFlow.Data;
using StoryFlow.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class MyDialogueUI : StoryFlowDialogueUI
{
[SerializeField] private GameObject dialoguePanel;
[SerializeField] private TextMeshProUGUI titleText;
[SerializeField] private TextMeshProUGUI bodyText;
[SerializeField] private TextMeshProUGUI characterNameText;
[SerializeField] private Image characterPortrait;
[SerializeField] private Transform optionsContainer;
[SerializeField] private GameObject optionButtonPrefab;
[SerializeField] private Button continueButton;
private void Awake()
{
dialoguePanel.SetActive(false);
continueButton.onClick.AddListener(() => AdvanceDialogue());
}
public override void OnDialogueStarted()
{
dialoguePanel.SetActive(true);
}
public override void HandleDialogueUpdated(StoryFlowDialogueState state)
{
// Display title and body text
titleText.gameObject.SetActive(!string.IsNullOrEmpty(state.Title));
titleText.text = state.Title ?? "";
bodyText.text = state.Text ?? "";
// Display character info
bool hasCharacter = state.Character != null
&& !string.IsNullOrEmpty(state.Character.Name);
characterNameText.gameObject.SetActive(hasCharacter);
if (hasCharacter)
characterNameText.text = state.Character.Name;
if (characterPortrait != null)
{
bool hasPortrait = hasCharacter && state.Character.Image != null;
characterPortrait.gameObject.SetActive(hasPortrait);
if (hasPortrait)
characterPortrait.sprite = state.Character.Image;
}
// Show continue button or choice options
continueButton.gameObject.SetActive(
state.CanAdvance && state.Options.Count == 0);
// Clear and rebuild option buttons
foreach (Transform child in optionsContainer)
Destroy(child.gameObject);
foreach (var option in state.Options)
{
var buttonObj = Instantiate(optionButtonPrefab, optionsContainer);
var label = buttonObj.GetComponentInChildren<TextMeshProUGUI>();
label.text = option.Text;
string capturedId = option.Id;
buttonObj.GetComponent<Button>().onClick.AddListener(
() => SelectOption(capturedId));
}
}
public override void OnDialogueEnded()
{
dialoguePanel.SetActive(false);
}
} Custom UI with Events
If you need full control or want to integrate StoryFlow with an existing UI framework, skip the base class entirely and subscribe directly to the component's C# events.
using StoryFlow;
using StoryFlow.Data;
using UnityEngine;
public class DialogueUIController : MonoBehaviour
{
[SerializeField] private StoryFlowComponent storyFlow;
[SerializeField] private MyCustomDialoguePanel dialoguePanel;
private void OnEnable()
{
storyFlow.OnDialogueStarted += OnDialogueStarted;
storyFlow.OnDialogueUpdated += OnDialogueUpdated;
storyFlow.OnDialogueEnded += OnDialogueEnded;
}
private void OnDisable()
{
storyFlow.OnDialogueStarted -= OnDialogueStarted;
storyFlow.OnDialogueUpdated -= OnDialogueUpdated;
storyFlow.OnDialogueEnded -= OnDialogueEnded;
}
private void OnDialogueStarted()
{
dialoguePanel.Show();
}
private void OnDialogueUpdated(StoryFlowDialogueState state)
{
// Manually update your panel
dialoguePanel.SetTitle(state.Title);
dialoguePanel.SetBody(state.Text);
dialoguePanel.SetCharacter(state.Character?.Name, state.Character?.Image);
dialoguePanel.SetOptions(state.Options);
dialoguePanel.SetContinueVisible(
state.CanAdvance && state.Options.Count == 0);
}
private void OnDialogueEnded()
{
dialoguePanel.Hide();
}
// Called by your UI when the player clicks an option button
public void HandleOptionSelected(string optionId)
{
storyFlow.SelectOption(optionId);
}
// Called by your UI when the player clicks "Continue"
public void HandleContinue()
{
storyFlow.AdvanceDialogue();
}
} Key points for any approach:
- Display
TitleandTextfor the dialogue content - Display
Character.NameandCharacter.Imagefor speaker identification - Iterate the
Optionslist to create choice buttons, and callSelectOption(option.Id)when a button is clicked - Show a "Continue" button when
CanAdvanceistrueandOptionsis empty, and callAdvanceDialogue()on click - Display
Imagefor scene or background images
Variable Interpolation
Dialogue text in StoryFlow supports variable interpolation using the {varname} syntax. When the runtime builds the dialogue state, it automatically replaces these placeholders with the current variable values. All interpolation is resolved before the state reaches your UI - the Title, Text, and option text fields already contain the final display strings.
Supported interpolation patterns:
{varname}- Inserts the value of a project or script variable{Character.Name}- Inserts the current character's name{Character.VarName}- Inserts a per-character variable value
Automatic Re-rendering on Variable Changes
When a Set* node (setBool, setInt, setString, etc.) runs during an active dialogue and has no outgoing edge, the runtime automatically returns to the current dialogue node and re-renders it with the updated variable values. This means the OnDialogueUpdated event fires again with a new StoryFlowDialogueState containing the freshly interpolated text. Your UI code does not need to handle this specially - simply rebuild the display every time OnDialogueUpdated fires, and variable changes will appear seamlessly.
This re-rendering behavior enables powerful patterns. For example, a dialogue option can trigger a setBool node that changes a flag, and the dialogue text updates immediately to reflect the new state - all without leaving the current dialogue node.
Text Blocks
Text blocks are non-interactive text segments displayed alongside the main dialogue content. Each text block has an Id and a Text field, and they are available in the TextBlocks list of StoryFlowDialogueState.
Unlike options, text blocks are not clickable. They are used for supplementary information, narrator descriptions, stage directions, or any additional text that accompanies the dialogue but does not require player interaction.
// Display text blocks in your dialogue UI
private void DisplayTextBlocks(List<StoryFlowTextBlock> textBlocks)
{
// Clear previous text blocks
foreach (Transform child in textBlocksContainer)
Destroy(child.gameObject);
foreach (var block in textBlocks)
{
var textObj = Instantiate(textBlockPrefab, textBlocksContainer);
var label = textObj.GetComponentInChildren<TextMeshProUGUI>();
label.text = block.Text;
}
}
Display text blocks in your UI below the main dialogue text, or in a separate panel depending on your design. Since text blocks share a similar structure to options (both have an Id and Text), you can use a consistent rendering approach for both, differentiating only by interactivity.