Skip to main content

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 USoundWave assets and work with UGameplayStatics::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 USoundWave asset, 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:

C++
// 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 the OnAudioFinished delegate callback on the UAudioComponent, 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.

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.

C++
// 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.

  • bStopAudioOnDialogueEnd (bool, default: true) — When enabled, all playing dialogue audio is automatically stopped when the dialogue ends (when the runtime reaches an End node or StopDialogue is 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 the SoundClass mix 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.
C++
// Example: Configure audio settings in C++
UStoryFlowComponent* StoryFlow = FindComponentByClass<UStoryFlowComponent>();

// 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:

C++
// 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 created via SpawnSound2D. This gives the plugin a persistent reference to the playing sound for pause, stop, and volume control.
  • Looping — Audio looping is implemented through the OnAudioFinished delegate callback on the UAudioComponent. When the sound finishes and looping is enabled, the callback re-triggers playback. This approach is used instead of the built-in bLooping property to allow the plugin to cleanly stop looped audio when the dialogue advances.
  • StopDialogueAudio() — This protected function stops the current UAudioComponent and clears the looping callback. It is called internally when bStopAudioOnDialogueEnd is enabled, when audioReset triggers, or when new dialogue audio replaces the currently playing sound. It is not directly callable from outside the component — to stop audio externally, call StopDialogue() which handles audio cleanup automatically.

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