Skip to main content

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.

Choice Options

When a dialogue node has options, they are delivered through the FStoryFlowDialogueState that your UI receives on every dialogue update. Each option is represented by an FStoryFlowDialogueOption struct with two fields:

  • Id (FString) — A unique identifier for this option, used when calling SelectOption
  • Text (FString) — The display text for the option, already fully resolved with variable interpolation

You read options from the Options array on FStoryFlowDialogueState:

C++
// Inside your dialogue widget or HUD class
void UMyDialogueWidget::OnDialogueUpdated(const FStoryFlowDialogueState& State)
{
    // Clear previous option buttons
    OptionsContainer->ClearChildren();

    // Create a button for each available option
    for (const FStoryFlowDialogueOption& Option : State.Options)
    {
        UMyOptionButton* Button = CreateWidget<UMyOptionButton>(this);
        Button->SetOptionText(Option.Text);
        Button->SetOptionId(Option.Id);
        Button->OnClicked.AddDynamic(this, &UMyDialogueWidget::OnOptionClicked);
        OptionsContainer->AddChild(Button);
    }
}

Pre-Filtered Options

The Options array 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. Your UI simply iterates the array without any additional filtering.

Selecting an Option

When the player clicks a choice, call SelectOption with the option's Id. You can call this from either the component or a dialogue widget:

C++
// Option 1: Call directly on the StoryFlow component
StoryFlowComponent->SelectOption(OptionId);

// Option 2: Call from a UStoryFlowDialogueWidget 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 FStoryFlowDialogueOption.

Here is a complete example wiring a button click to option selection:

C++
void UMyDialogueWidget::OnOptionClicked(const FString& OptionId)
{
    // Tell the runtime the player picked this option
    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-Only Dialogues

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:

  • FStoryFlowDialogueState.bCanAdvance is true
  • FStoryFlowDialogueState.Options is empty
C++
void UMyDialogueWidget::OnDialogueUpdated(const FStoryFlowDialogueState& State)
{
    // Update speaker name, dialogue text, portrait, etc.
    UpdateDialogueDisplay(State);

    if (State.Options.Num() == 0 && State.bCanAdvance)
    {
        // No options — show a "Continue" button
        ContinueButton->SetVisibility(ESlateVisibility::Visible);
        OptionsContainer->SetVisibility(ESlateVisibility::Collapsed);
    }
    else
    {
        // Has options — show the option buttons
        ContinueButton->SetVisibility(ESlateVisibility::Collapsed);
        OptionsContainer->SetVisibility(ESlateVisibility::Visible);
        PopulateOptions(State.Options);
    }
}

void UMyDialogueWidget::OnContinueClicked()
{
    // Advance uses the dialogue node's header output edge
    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 UStoryFlowSubsystem::GetUsedOnceOnlyOptions(), a TSet<FString> where each entry is keyed as nodeId-optionId.

C++
// Check if a specific once-only option has been used
UStoryFlowSubsystem* Subsystem = GetGameInstance()->GetSubsystem<UStoryFlowSubsystem>();

FString Key = FString::Printf(TEXT("%s-%s"), *NodeId, *OptionId);
bool bAlreadyUsed = Subsystem->GetUsedOnceOnlyOptions().Contains(Key);

// To reset a specific once-only option (e.g., for New Game+)
Subsystem->GetUsedOnceOnlyOptions().Remove(Key);

// To reset all once-only tracking
Subsystem->GetUsedOnceOnlyOptions().Empty();

Once-Only Persistence

Once-only option tracking persists across dialogue sessions and survives save/load cycles. When you call the save functions on UStoryFlowSubsystem, the UsedOnceOnlyOptions set is included in the serialized state. This means a player who saves after exhausting a dialogue option will not see it again when they load that save. If you need to reset once-only tracking (for example, when starting a new game), 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.

Not Yet Available in Unreal

Typed input options are a feature of the StoryFlow Editor's built-in runtime. Support for input options in the Unreal Engine plugin is planned for a future release. Currently, only standard button-style choice options are supported via SelectOption. If your StoryFlow project uses input options, you will need to implement custom UI handling and map user input to variable changes using the component's SetBoolVariable, SetIntVariable, SetStringVariable, and other typed setter functions.

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 through EvaluateOptionVisibility() every time the dialogue is rendered. Options that fail their visibility check are excluded from the Options array before it reaches your UI.

C++
// You do NOT need to check visibility yourself.
// The Options array is already filtered:
for (const FStoryFlowDialogueOption& Option : State.Options)
{
    // Every option here has passed its visibility check.
    // Hidden options are simply not in the array.
    CreateOptionButton(Option);
}

// Example scenario:
// - "Buy Sword (50 gold)" only visible when player has >= 50 gold
// - "Enter VIP Room" only visible when bHasVIPPass is true
// - "Ask about the map" hidden after it's been selected (once-only)
// All of this is handled before Options reaches your widget.

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.

Need Help?

Join our Discord community to ask questions, share your projects, report bugs, and get support from the team and other users.

Join Discord