Audio
StoryFlow supports audio playback tied to dialogue nodes, standalone audio triggers, and audio variables. This guide covers how audio assets are handled, configured, and played back in the Unreal Engine plugin.
Supported Formats
The plugin supports two audio formats for import:
- WAV - The preferred format. WAV files are imported directly as
USoundWaveassets and work withUGameplayStatics::PlaySound2D,UAudioComponent, and all standard Unreal audio APIs. - MP3 - Supported via automatic decoding. MP3 files are decoded to WAV during import using the built-in minimp3 decoder (CC0 licensed). The decoded audio is stored as a
USoundWaveasset, so it behaves identically to a WAV import at runtime.
Why MP3 files are auto-decoded
Unreal Engine natively imports MP3 files as FileMediaSource, not USoundWave. This limits playback options and prevents use with standard audio APIs like PlaySound2D. The plugin's built-in minimp3 decoder bypasses this limitation entirely - your MP3 files are decoded to PCM data at import time and stored as USoundWave assets, giving you full compatibility with the Unreal audio system.
Audio on Dialogue Nodes
Dialogue nodes in the StoryFlow Editor can have audio attached. When a dialogue node has audio, it is available at runtime through the dialogue state:
// Access audio from the current dialogue state
void UMyDialogueWidget::UpdateDialogue(const FStoryFlowDialogueState& State)
{
// State.Audio is a USoundBase* - may be null if no audio is assigned
if (State.Audio)
{
// Audio is available for this dialogue node
// The plugin handles playback automatically via UAudioComponent,
// but you can also access the asset directly for custom handling
}
}
The dialogue state exposes audio as FStoryFlowDialogueState.Audio (USoundBase*). This value is nullptr when no audio is assigned to the dialogue node.
Looping and Reset
Each dialogue node in the StoryFlow Editor has two audio-related settings that control playback behavior:
- audioLoop (
bool) - When enabled, the dialogue's audio loops continuously for as long as the dialogue is active. Internally, the plugin implements looping through theOnAudioFinisheddelegate callback on theUAudioComponent, which re-triggers playback when the sound completes. - audioReset (
bool) - When enabled and the dialogue has no audio assigned, any previously playing dialogue audio is stopped. This gives you explicit control over when audio should be cut - for example, a narrative dialogue node that should play in silence after a voiced line.
Advance on End
Dialogue nodes can be configured to auto-advance when audio finishes playing. This is useful for cinematic or voiced dialogue where you want the conversation to flow automatically without requiring the player to click. Two settings control this behavior:
- audioAdvanceOnEnd (
bool) - When enabled, the dialogue automatically advances to the next node via the header output edge when the audio finishes playing. This setting is only available on dialogue nodes that have no interactive options (no buttons or input elements) and audio looping is disabled. - audioAllowSkip (
bool) - When enabled alongsideaudioAdvanceOnEnd, the player can click to skip the audio and advance early. When disabled, the player must wait for the audio to finish before the dialogue advances. CallingAdvanceDialoguewhile audio is playing will be blocked unlessaudioAllowSkipistrue.
The dialogue state exposes these as FStoryFlowDialogueState.bAudioAdvanceOnEnd and FStoryFlowDialogueState.bAudioAllowSkip, so your widget can adjust its UI accordingly (for example, hiding the "Continue" button when advance-on-end is active and skip is not allowed).
void UMyDialogueWidget::OnDialogueUpdated(const FStoryFlowDialogueState& State)
{
if (State.bCanAdvance && State.bAudioAdvanceOnEnd && !State.bAudioAllowSkip)
{
// Audio will auto-advance — hide the continue button
ContinueButton->SetVisibility(ESlateVisibility::Collapsed);
}
else if (State.bCanAdvance)
{
// Player can click to advance (or skip audio)
ContinueButton->SetVisibility(ESlateVisibility::Visible);
}
} Error handling
If audio fails to play (asset missing or playback error), the advance-on-end state is automatically cleared so the dialogue falls back to normal bCanAdvance click behavior. The player is never stuck.
Fresh Entry vs Return
Understanding fresh entry
Audio only plays on a fresh entry to a dialogue node - that is, when the runtime arrives at the node by following an edge from another node. When a Set* node (such as setBool, setInt, etc.) has no outgoing edge and returns to the dialogue for re-rendering, audio does not re-trigger. This prevents audio from restarting every time a variable changes during an active dialogue.
This distinction is important for interactive dialogues where clicking an option triggers a Set* node that modifies a variable and then returns to the same dialogue to re-render with updated text (variable interpolation). Without fresh entry tracking, the audio would restart on every variable change.
Internally, the plugin tracks this with a bEnteringDialogueViaEdge flag. When the runtime follows an edge to reach a dialogue node, the flag is true and audio plays. When a Set* node returns to the dialogue without an edge, the flag is false and audio playback is skipped.
// Simplified internal logic for dialogue audio playback
void UStoryFlowComponent::ProcessDialogueNode(const FStoryFlowNode& Node)
{
// ... build dialogue state ...
if (bEnteringDialogueViaEdge && DialogueState.Audio)
{
// Fresh entry - play the audio
PlayDialogueAudio(DialogueState.Audio, Node.Data.bAudioLoop);
}
else if (bEnteringDialogueViaEdge && !DialogueState.Audio && Node.Data.bAudioReset)
{
// Fresh entry, no audio, reset enabled - stop any playing audio
StopDialogueAudio();
}
// Returning from a Set* node: do nothing with audio
} Component Audio Settings
The UStoryFlowComponent exposes several audio-related properties in the Details panel under the StoryFlow | Audio category. These settings apply globally to all dialogue audio played by the component.
- bUse3DAudio (
bool, default:false) - When enabled, dialogue audio is played as a 3D spatialized sound attached to the owning actor usingSpawnSoundAttached. When disabled (default), audio plays as a flat 2D sound viaSpawnSound2D. Enable this for in-world dialogue where audio should come from the character's position. - bStopAudioOnDialogueEnd (
bool, default:true) - When enabled, all playing dialogue audio is automatically stopped when the dialogue ends (when the runtime reaches an End node orStopDialogueis called). Disable this if you want audio to continue playing after the dialogue system closes. - DialogueSoundClass (
USoundClass*) - Assigns all dialogue audio to a specific sound class. This allows you to create a dedicated "Dialogue" volume slider in your game's audio settings, separate from music, SFX, and other audio categories. - DialogueVolumeMultiplier (
float, default:1.0, range:0.0 - 2.0) - A volume scaling multiplier applied to all dialogue audio. This stacks multiplicatively with theSoundClassmix volume, giving you fine-grained control over dialogue loudness. - DialogueConcurrency (
USoundConcurrency*) - Controls overlap behavior when multiple audio sources play simultaneously. Use this to limit the number of concurrent dialogue sounds or define priority rules for which audio takes precedence. - DialogueAttenuation (
USoundAttenuation*) - Attenuation settings for 3D dialogue audio. Controls distance falloff, spatialization method, occlusion, and other spatial audio properties. Only relevant whenbUse3DAudiois enabled. Create aSound Attenuationasset in the Content Browser and assign it here.
// Example: Configure audio settings in C++
UStoryFlowComponent* StoryFlow = FindComponentByClass<UStoryFlowComponent>();
// Enable 3D spatialized audio (attached to this actor)
StoryFlow->bUse3DAudio = true;
// Assign attenuation for 3D falloff control
StoryFlow->DialogueAttenuation = MyAttenuationSettings;
// Stop audio when dialogue ends (default behavior)
StoryFlow->bStopAudioOnDialogueEnd = true;
// Assign a sound class for volume control
StoryFlow->DialogueSoundClass = MyDialogueSoundClass;
// Set volume to 80%
StoryFlow->DialogueVolumeMultiplier = 0.8f;
// Set concurrency rules
StoryFlow->DialogueConcurrency = MyDialogueConcurrency; PlayAudio Node
The playAudio node is a standalone audio trigger that exists separately from dialogue-attached audio. When the runtime encounters a playAudio node, it broadcasts the OnAudioPlayRequested delegate:
// Delegate signature
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnAudioPlayRequested,
const FString&, AudioPath,
bool, bLoop
);
// Bind to the delegate in your game code
StoryFlowComponent->OnAudioPlayRequested.AddDynamic(
this, &AMyActor::HandleAudioPlayRequested);
void AMyActor::HandleAudioPlayRequested(const FString& AudioPath, bool bLoop)
{
// Use AudioPath to look up and play the sound asset
// bLoop indicates whether the audio should loop
// Handle this for ambient sounds, music, stingers, etc.
} PlayAudio is a broadcast, not automatic playback
Unlike dialogue-attached audio which the plugin plays automatically, the playAudio node only broadcasts a delegate. Your game code is responsible for resolving the audio path, loading the asset, and playing it. This gives you full control over how standalone audio is handled - for example, routing music through a separate audio manager or layering ambient sounds.
SetAudio Node
The setAudio node sets the value of an audio variable. Audio variables store references to sound assets that can be used by dialogue nodes or read by other nodes in the graph.
Like other Set* nodes, if a setAudio node has no outgoing edge and was reached from a dialogue node, it returns to that dialogue for re-rendering. This allows dialogue nodes to reference audio variables that can change dynamically during a conversation.
Internal Playback
Understanding how the plugin handles audio playback internally can help when debugging or extending the audio system.
- UAudioComponent - Dialogue audio is played through a
UAudioComponent. WhenbUse3DAudiois enabled, the component is created viaSpawnSoundAttachedon the owning actor's root component for 3D spatialized playback. When disabled (default), it usesSpawnSound2Dfor flat non-spatialized audio. - Looping and Advance-on-End - The
OnAudioFinisheddelegate is always bound on theUAudioComponent. When audio finishes, the callback checks: if looping is enabled, it re-triggers playback; if advance-on-end is active, it callsAdvanceDialogueto automatically proceed. This ensures both features share the same reliable callback mechanism. - PlayDialogueAudio() / StopDialogueAudio() - These are
BlueprintNativeEventfunctions, meaning you can override them in a Blueprint subclass ofUStoryFlowComponentfor fully custom audio handling (for example, integrating with Wwise, FMOD, or MetaSounds). The default implementations handle 2D/3D playback, looping, and cleanup.StopDialogueAudiois called internally whenbStopAudioOnDialogueEndis enabled, whenaudioResettriggers, or when new dialogue audio replaces the currently playing sound.