Handling Choices
Dialogue nodes can present the player with multiple choices, typed input fields, and conditionally visible options. This guide covers how to read, display, and respond to every kind of player interaction in a StoryFlow dialogue.
Options Overview
When a dialogue node has options, they are delivered through the StoryFlowDialogueState that your UI receives on every dialogue update. Each option is represented by a StoryFlowOption with the following fields:
- Id (
string) - A unique identifier for this option, used when callingSelectOption() - Text (
string) - The display text, already fully resolved with variable interpolation - IsOnceOnly (
bool) - Whether this option disappears after being selected - IsSelected (
bool) - Reserved field for tracking whether this option has been previously selected. Currently not populated by the runtime (alwaysfalse). - InputType (
string) - The input type for typed inputs ("string","integer","float","boolean","enum"), or empty for standard button options - DefaultValue (
string) - The default value for input options
You read options from the Options list on StoryFlowDialogueState:
// Inside your dialogue UI
public void HandleDialogueUpdated(StoryFlowDialogueState state)
{
// Clear previous option buttons
foreach (Transform child in optionsContainer)
Destroy(child.gameObject);
// Create a button for each available option
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(
() => OnOptionClicked(capturedId));
}
} Pre-Filtered Options
The Options list only contains options the player should see right now. Hidden options (those that fail their visibility condition or have already been used as once-only) are automatically excluded by the runtime. Your UI simply iterates the list without any additional filtering.
Selecting Options
When the player clicks a choice, call SelectOption() with the option's Id. You can call this on the component directly or use the convenience method from a StoryFlowDialogueUI subclass:
// Option 1: Call directly on the StoryFlowComponent
storyFlowComponent.SelectOption(optionId);
// Option 2: Call from a StoryFlowDialogueUI subclass
// (internally forwards to the bound component)
SelectOption(optionId);
When SelectOption() is called, the runtime follows the dialogue node's output edge that matches the selected option. Internally, the source handle format is source-{nodeId}-{optionId}, but you never need to construct this yourself - just pass the Id from StoryFlowOption.
Here is a complete example wiring a button click to option selection:
private void OnOptionClicked(string optionId)
{
// Tell the runtime the player picked this option
storyFlowComponent.SelectOption(optionId);
// The component will fire OnDialogueUpdated with the next
// dialogue state, or OnDialogueEnded if the story is over.
} Variable Re-rendering
If an option's edge leads to a Set* node (e.g., setBool, setInt) that has no outgoing edge, the runtime automatically returns to the current dialogue and re-renders it with the updated variable values. This enables live variable interpolation - for example, a toggle option that updates displayed text without leaving the dialogue.
Advancing Narrative Nodes
Not every dialogue presents choices. Narrative-only dialogues display text (and optionally a character, image, or audio) but have no selectable options. You can detect this state and show a "Continue" or "Next" button instead.
A dialogue is narrative-only when:
state.CanAdvanceistruestate.Options.Countis0
public void HandleDialogueUpdated(StoryFlowDialogueState state)
{
// Update speaker name, dialogue text, portrait, etc.
UpdateDialogueDisplay(state);
if (state.Options.Count == 0 && state.CanAdvance)
{
// No options - show a "Continue" button
continueButton.gameObject.SetActive(true);
optionsContainer.gameObject.SetActive(false);
}
else
{
// Has options - show the option buttons
continueButton.gameObject.SetActive(false);
optionsContainer.gameObject.SetActive(true);
PopulateOptions(state.Options);
}
}
private void OnContinueClicked()
{
// Advance uses the dialogue node's header output edge
storyFlowComponent.AdvanceDialogue();
} Once-Only Options
Options can be marked as once-only in the StoryFlow Editor. After a player selects a once-only option, it is permanently hidden from future displays of that dialogue node. This is useful for:
- One-time dialogue branches ("Ask about the artifact" disappears after asking)
- Unlockable conversations that are consumed on use
- First-encounter dialogue that should not repeat
- Exhaustible question lists where the player works through available topics
The runtime tracks used once-only options via the StoryFlowManager's internal UsedOnceOnlyOptions set, where each entry is keyed as nodeId-optionId. You do not need to manage this tracking yourself - it is handled automatically when SelectOption() is called.
// Once-only tracking is automatic. When SelectOption() is called
// for an option where IsOnceOnly is true, the runtime records
// the selection and excludes it from future renders.
// To reset once-only tracking (e.g., for New Game+),
// call ResetAllState which clears globals, characters, AND once-only options:
StoryFlowManager.Instance.ResetAllState(); Once-Only Persistence
Once-only option tracking persists across dialogue sessions within the same game session and survives save/load cycles. When you call StoryFlowManager.Instance.SaveToSlot(), the used once-only options are included in the serialized data. If you need to reset once-only tracking (for example, when starting a new game), call ResetAllState() or clear the set before beginning the new session.
Input Options
Beyond simple clickable choices, the StoryFlow Editor supports typed input fields on dialogue nodes - including string, integer, float, boolean, and enum inputs. These allow players to enter values directly rather than choosing from predefined options.
You can detect input options by checking the InputType field on each StoryFlowOption. When InputType is non-empty and not "button", the option represents a typed input field:
"string"- Show a text input field"integer"- Show a numeric input or slider (whole numbers)"float"- Show a numeric input or slider (decimal numbers)"boolean"- Show a toggle or checkbox"enum"- Show a dropdown
When the player changes an input value, call InputChanged() on the component:
// For a text input field
storyFlowComponent.InputChanged(option.Id, inputField.text);
// From a StoryFlowDialogueUI subclass, use the convenience method:
InputChanged(option.Id, inputField.text);
The InputChanged() method stores the new value, re-interpolates dialogue text, broadcasts an OnDialogueUpdated event, and optionally follows an "on change" edge if one is connected.
Here is a complete example that renders different UI elements based on input type:
using StoryFlow;
using StoryFlow.Data;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public void PopulateOptions(List<StoryFlowOption> options)
{
foreach (Transform child in optionsContainer)
Destroy(child.gameObject);
foreach (var option in options)
{
if (string.IsNullOrEmpty(option.InputType) || option.InputType == "button")
{
// Standard button option
var buttonObj = Instantiate(optionButtonPrefab, optionsContainer);
buttonObj.GetComponentInChildren<TextMeshProUGUI>().text = option.Text;
string capturedId = option.Id;
buttonObj.GetComponent<Button>().onClick.AddListener(
() => storyFlowComponent.SelectOption(capturedId));
}
else if (option.InputType == "boolean")
{
// Toggle option
var toggleObj = Instantiate(togglePrefab, optionsContainer);
toggleObj.GetComponentInChildren<TextMeshProUGUI>().text = option.Text;
var toggle = toggleObj.GetComponent<Toggle>();
toggle.isOn = option.DefaultValue == "true";
string capturedId = option.Id;
toggle.onValueChanged.AddListener(
(value) => storyFlowComponent.InputChanged(
capturedId, value.ToString().ToLower()));
}
else if (option.InputType == "string")
{
// Text input field
var inputObj = Instantiate(textInputPrefab, optionsContainer);
inputObj.GetComponentInChildren<TextMeshProUGUI>().text = option.Text;
var inputField = inputObj.GetComponentInChildren<TMP_InputField>();
inputField.text = option.DefaultValue ?? "";
string capturedId = option.Id;
inputField.onEndEdit.AddListener(
(value) => storyFlowComponent.InputChanged(capturedId, value));
}
// ... handle "integer", "float", "enum" similarly
}
} On Change Flows
Each input option can have an "on change" edge connected in the StoryFlow Editor. When InputChanged() is called and an on-change edge exists, the runtime executes the connected flow (for example, a chain of Set* and logic nodes) without leaving the dialogue. This allows input values to immediately affect variables and re-render dialogue text.
Conditional Visibility
Options in the StoryFlow Editor can have visibility conditions connected to them. These are boolean node chains (using Get, And, Or, Not, and comparison nodes) that determine whether an option should appear at runtime.
The runtime evaluates these conditions automatically every time the dialogue is rendered. Options that fail their visibility check are excluded from the Options list before it reaches your UI.
// You do NOT need to check visibility yourself.
// The Options list is already filtered:
foreach (var option in state.Options)
{
// Every option here has passed its visibility check.
// Hidden options are simply not in the list.
CreateOptionButton(option);
}
// Example scenario:
// - "Buy Sword (50 gold)" only visible when player has >= 50 gold
// - "Enter VIP Room" only visible when hasVIPPass is true
// - "Ask about the map" hidden after it has been selected (once-only)
// All of this is handled before Options reaches your UI. Because visibility conditions are re-evaluated on every render, they respond to variable changes in real time. If a Set* node updates a variable that a visibility condition depends on and then returns to the dialogue, the option list will reflect the new state immediately.
Next Steps
Now that you understand how to handle choices, learn how to work with Variables to create dynamic conditions and persistent state, or explore Characters to display speaker portraits and names alongside your dialogue options.