Dialogue Tags
A dialogue node can carry tags, and when the runtime enters that node the component raises an event for each one. This gives you a place to hang presentation cues that belong next to a line rather than in the node graph: a camera shake, a facial expression, a lighting change, a sound sting. Added in v1.2.2.
What a Tag Is
A tag is an arbitrary string attached to a dialogue node. The runtime attaches no meaning to it. The plugin passes tags through exactly as authored and raises an event so your game decides what each one does.
Tags are authored per dialogue node in the StoryFlow Editor, in editor version 1.6.0 and newer. A node can carry any number of tags, and the order you author them is preserved all the way through to the event. See the Dialogue Editor documentation for the authoring side.
On the Unreal side the tags of the current line live on FStoryFlowDialogueState:
// Presentation tags authored on this dialogue node (empty when untagged)
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "StoryFlow")
TArray<FString> Tags; The Tag Event
OnDialogueTagReached is a BlueprintAssignable multicast delegate on UStoryFlowComponent under the StoryFlow | Events category. It carries a single FString parameter, the tag itself.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnDialogueTagReached, const FString&, Tag);
/** Called once per tag, in authored order, when a tagged dialogue node is entered */
UPROPERTY(BlueprintAssignable, Category = "StoryFlow|Events")
FOnDialogueTagReached OnDialogueTagReached; In Blueprint
- Select the StoryFlow Component in your actor's Blueprint.
- In the Details panel, scroll to the Events section.
- Click the + button next to On Dialogue Tag Reached.
- The generated event node has a single
Tagstring pin. Switch on it, or compare it against the tags you author.
In C++
// In your actor's BeginPlay
void AMyDialogueActor::BeginPlay()
{
Super::BeginPlay();
if (UStoryFlowComponent* StoryFlow = FindComponentByClass<UStoryFlowComponent>())
{
StoryFlow->OnDialogueTagReached.AddDynamic(
this, &AMyDialogueActor::HandleDialogueTag);
}
}
// Handler signature - must be a UFUNCTION for AddDynamic
void AMyDialogueActor::HandleDialogueTag(const FString& Tag)
{
if (Tag == TEXT("shake"))
{
ShakeCamera();
}
else if (Tag == TEXT("angry"))
{
PlayFacialAnimation(TEXT("angry"));
}
} Firing Semantics
When the tags fire is as much part of the contract as what they carry:
- Once per tag - a node with three tags raises three separate events, not one event carrying a list.
- In authored order - the tags fire in the order they appear on the node.
- Immediately after
OnDialogueUpdated- the dialogue state for the tagged line has already been built and broadcast by the time the first tag arrives, so a handler can act on the line that is on screen right now. - Only when the node is entered through an edge - tags mark reaching a line, not rendering it.
That last rule is what keeps a tag from firing twice for the same line. A dialogue node is re-rendered in several situations that are not a fresh entry, and none of them re-fire tags:
- Returning from a Set* node that has no outgoing edge. The line re-renders with the new variable values and
OnDialogueUpdatedfires again, but no tags fire. - Live variable re-interpolation, when a Blueprint or C++ setter changes a variable while the line is on screen.
ResumeDialogue, which re-broadcasts the currentOnDialogueUpdatedand nothing else.
Revisiting the same node later through an edge is a fresh entry, so its tags fire again. A tag on a line inside a loop fires once per pass.
Tags Are Cues, Not State
Because a tag fires on entry and never again for that visit, treat it as a trigger rather than a condition. If your UI needs to know how the current line is tagged at an arbitrary moment (after a re-render, or when a widget is constructed mid-dialogue), read Tags off the dialogue state instead. Both surfaces describe the same line.
Advancing From Inside a Tag Handler
The tags of a line are snapshotted before they are broadcast. If a handler synchronously advances, selects an option, stops or restarts the dialogue, the remaining tags of the line that was entered still fire in full, which means they can interleave with the next line's tags. If ordering across lines matters to you, defer the advance (a timer, a latent node) instead of calling it from the handler.
Reading Tags From State
You do not have to bind the event to use tags. The current line's tags are on the dialogue state, and there is a direct getter for them:
| Surface | Type | Description |
|---|---|---|
State.Tags | TArray<FString> | Field on FStoryFlowDialogueState, so every OnDialogueUpdated payload already carries the tags of the line it describes. Empty for an untagged line. |
GetCurrentDialogueTags() | TArray<FString> | BlueprintPure on the component. Returns the same array as GetCurrentDialogue().Tags without breaking the whole state struct. Empty when no dialogue is active. |
// From a widget rebuilding itself on every update
void UMyDialogueWidget::OnDialogueUpdated_Implementation(
const FStoryFlowDialogueState& State)
{
DialogueText->SetText(FText::FromString(State.Text));
// Tags travel with the state, so a re-render styles the line correctly
// even though no tag event fired for it
const bool bWhisper = State.Tags.Contains(TEXT("whisper"));
DialogueText->SetFont(bWhisper ? WhisperFont : NormalFont);
}
// Or from anywhere, without binding to anything
TArray<FString> Tags = StoryFlowComponent->GetCurrentDialogueTags();
if (Tags.Contains(TEXT("whisper")))
{
LowerAmbientVolume();
} Backward Compatibility
Tags are an optional field on a dialogue node. A node with no tags produces an empty Tags array and never raises OnDialogueTagReached, and so does any script exported by an editor version older than 1.6.0. Nothing else about dialogue execution changed, so an existing project keeps behaving exactly as it did without a re-export and without a code change.
Tags Pass Through Untouched
Tags are plain strings. The importer reads a tags array off a dialogue node and hands each entry to your handler as authored, with no normalization, casing rule or reserved prefix. Comparisons in your code are exact string comparisons, so pick a convention when you start tagging and keep to it. Tags require a project exported from StoryFlow Editor 1.6.0 or newer, since that is the version that began writing the field.