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 Godot plugin.

Supported Formats

The plugin supports audio formats that Godot can import natively as AudioStream resources:

  • WAV - The preferred format. WAV files are imported directly as AudioStreamWAV resources and work with all Godot audio APIs including AudioStreamPlayer.
  • OGG Vorbis - Imported as AudioStreamOggVorbis. A good choice for longer audio like music or dialogue lines where file size matters.
  • MP3 - Imported as AudioStreamMP3. Supported in Godot 4.x natively, unlike Unreal Engine which requires decoding at import time.

Format Recommendations

For short sound effects and voice lines, WAV provides the best quality with zero decompression overhead. For longer audio clips like music or ambient loops, OGG Vorbis offers a good balance of quality and file size. MP3 is supported but OGG is generally preferred in Godot for compressed audio.

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:

GDScript
# Access audio from the current dialogue state
func _on_dialogue_updated(state: StoryFlowDialogueState) -> void:
    # state.audio is an AudioStream or null if no audio is assigned
    if state.audio:
        # Audio is available for this dialogue node
        # The plugin handles playback automatically via StoryFlowAudioController,
        # but you can also access the asset directly for custom handling
        pass

The dialogue state exposes audio as StoryFlowDialogueState.audio (AudioStream). This value is null 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 finished signal callback on the AudioStreamPlayer, 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 whether the dialogue node was reached via a normal edge traversal or via a Set* node return. When reached via an edge, audio plays normally. When returning from a Set* node, audio playback is skipped and any currently playing audio continues uninterrupted.

GDScript
# Simplified internal logic for dialogue audio playback
# (this happens inside StoryFlowComponent automatically)

# Fresh entry - play the audio
if entering_via_edge and dialogue_state.audio:
    audio_controller.play(dialogue_state.audio, audio_loop)

# Fresh entry, no audio, reset enabled - stop any playing audio
elif entering_via_edge and not dialogue_state.audio and audio_reset:
    audio_controller.stop()

# Returning from a Set* node: do nothing with audio

Component Audio Settings

The StoryFlowComponent exposes several audio-related properties in the Inspector under the Audio export group. These settings apply globally to all dialogue audio played by the component.

  • stop_audio_on_dialogue_end (bool, default: true) - When enabled, all playing dialogue audio is automatically stopped when the dialogue ends (when the runtime reaches an End node or stop_dialogue() is called). Disable this if you want audio to continue playing after the dialogue system closes.
  • dialogue_audio_bus (StringName, default: &"Master") - The audio bus used for dialogue audio playback. This allows you to route dialogue audio to a dedicated bus for volume control separate from music, SFX, and other audio categories.
  • dialogue_volume_db (float, default: 0.0) - Volume in decibels applied to all dialogue audio. Use negative values to reduce volume (e.g., -6.0 for half perceived loudness).
GDScript
# Example: Configure audio settings in GDScript
@onready var storyflow: StoryFlowComponent = %StoryFlowComponent

func _ready() -> void:
    # Stop audio when dialogue ends (default behavior)
    storyflow.stop_audio_on_dialogue_end = true

    # Route dialogue audio to a dedicated bus
    storyflow.dialogue_audio_bus = &"Dialogue"

    # Set volume to -3 dB
    storyflow.dialogue_volume_db = -3.0

Audio Bus Setup

Create a dedicated "Dialogue" bus in Godot's Audio tab (bottom panel) for fine-grained control over dialogue volume. This lets players adjust dialogue volume independently in your audio settings menu. Set the dialogue_audio_bus property to match your bus name.

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 emits the audio_play_requested signal:

GDScript
# Signal signature
signal audio_play_requested(audio_path: String, loop: bool)

# Connect to the signal in your game code
@onready var storyflow: StoryFlowComponent = %StoryFlowComponent

func _ready() -> void:
    storyflow.audio_play_requested.connect(_on_audio_play_requested)

func _on_audio_play_requested(audio_path: String, loop: bool) -> void:
    # Use audio_path to look up and play the sound asset
    # loop indicates whether the audio should loop
    # Handle this for ambient sounds, music, stingers, etc.
    var audio_stream := load(audio_path) as AudioStream
    if audio_stream:
        var player := AudioStreamPlayer.new()
        player.stream = audio_stream
        add_child(player)
        player.play()

PlayAudio is a broadcast, not automatic playback

Unlike dialogue-attached audio which the plugin plays automatically, the playAudio node only emits a signal. 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.

  • StoryFlowAudioController - A RefCounted helper class that manages dialogue audio playback. It creates and manages an AudioStreamPlayer node internally, handling play, stop, and loop operations.
  • AudioStreamPlayer - Dialogue audio is played through an AudioStreamPlayer node that is added as a child of the StoryFlowComponent. This gives the plugin a persistent reference to the playing sound for stop and volume control.
  • Looping - Audio looping is implemented through the finished signal callback on the AudioStreamPlayer. When the sound finishes and looping is enabled, the callback re-triggers playback. This approach allows the plugin to cleanly stop looped audio when the dialogue advances.
  • Asset Resolution - The audio controller resolves audio assets by checking the current script's resolved_assets first, then falling back to the project's resolved_assets. Assets are lazily loaded via ResourceLoader.load() on first access and cached for subsequent use.
  • stop() - The controller's stop() method stops the current AudioStreamPlayer and clears the looping flag. It is called internally when stop_audio_on_dialogue_end is enabled, when audioReset triggers, or when new dialogue audio replaces the currently playing sound.

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