Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation to the Background and Audio subsystem #2122

Merged
merged 21 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions addons/dialogic/Editor/TimelineEditor/test_timeline_scene.gd
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ func _ready() -> void:
get_tree().quit()
DialogicUtil.autoload().start(current_timeline)
DialogicUtil.autoload().timeline_ended.connect(get_tree().quit)
DialogicUtil.autoload().signal_event.connect(recieve_event_signal)
DialogicUtil.autoload().text_signal.connect(recieve_text_signal)
DialogicUtil.autoload().signal_event.connect(receive_event_signal)
DialogicUtil.autoload().text_signal.connect(receive_text_signal)

func recieve_event_signal(argument:String) -> void:
func receive_event_signal(argument:String) -> void:
print("[Dialogic] Encountered a signal event: ", argument)

func recieve_text_signal(argument:String) -> void:
func receive_text_signal(argument:String) -> void:
print("[Dialogic] Encountered a signal in text: ", argument)

func _input(event:InputEvent) -> void:
Expand Down
55 changes: 46 additions & 9 deletions addons/dialogic/Modules/Audio/subsystem_audio.gd
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
extends DialogicSubsystem

## Subsystem that manages music and sounds.

signal music_started(info:Dictionary)
signal sound_started(info:Dictionary)

## Subsystem for managing background music and one-shot sound effects.
##
## This subsystem has many different helper methods for managing audio
## in your timeline.
## For instance, you can listen to music changes via [signal music_started].


## Whenever a new background music is started, this signal is emitted and
## contains a dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `path` | [type String] | The path to the audio resource file. [br]
## `volume` | [type float] | The volume of the audio resource that will be set to the [member base_music_player]. [br]
## `audio_bus` | [type String] | The audio bus name that the [member base_music_player] will use. [br]
## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
signal music_started(info: Dictionary)


## Whenever a new sound effect is set, this signal is emitted and contains a
## dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `path` | [type String] | The path to the audio resource file. [br]
## `volume` | [type float] | The volume of the audio resource that will be set to [member base_sound_player]. [br]
## `audio_bus` | [type String] | The audio bus name that the [member base_sound_player] will use. [br]
## `loop` | [type bool] | Whether the audio resource will loop or not once it finishes playing. [br]
signal sound_started(info: Dictionary)


## Audio player used to play background music. Background music is long audio.
var base_music_player := AudioStreamPlayer.new()
## Audio player used to play sound effects. Sound effects are short audio.
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
var base_sound_player := AudioStreamPlayer.new()


#region STATE
####################################################################################################

func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
## Clears the state on this subsystem and stops the all sounds.
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
##
## If you want to stop audio only, use [method stop_all_sounds].
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
func clear_game_state(clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
update_music()
stop_all_sounds()


## Loads the state on this subsystem from the current state info.
func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void:
if load_flag == LoadFlags.ONLY_DNODES:
return
Expand All @@ -27,11 +58,13 @@ func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void:
update_music(info.path, info.volume, info.audio_bus, 0, info.loop)


## Pauses playing audio.
func pause() -> void:
for child in get_children():
child.stream_paused = true


## Resumes playing audio.
func resume() -> void:
for child in get_children():
child.stream_paused = false
Expand Down Expand Up @@ -85,12 +118,13 @@ func update_music(path := "", volume := 0.0, audio_bus := "Master", fade_time :=
fader.tween_callback(prev_node.queue_free)


## Whether music is playing.
func has_music() -> bool:
return !dialogic.current_state_info.get('music', {}).get('path', '').is_empty()


## Plays a given sound file.
func play_sound(path:String, volume := 0.0, audio_bus := "Master", loop := false) -> void:
func play_sound(path: String, volume := 0.0, audio_bus := "Master", loop := false) -> void:
if base_sound_player != null and !path.is_empty():
sound_started.emit({'path':path, 'volume':volume, 'audio_bus':audio_bus, 'loop':loop})
var new_sound_node := base_sound_player.duplicate()
Expand All @@ -110,6 +144,7 @@ func play_sound(path:String, volume := 0.0, audio_bus := "Master", loop := false
new_sound_node.finished.connect(new_sound_node.queue_free)


## Stops all audio.
func stop_all_sounds() -> void:
for node in get_children():
if node == base_sound_player:
Expand All @@ -118,7 +153,9 @@ func stop_all_sounds() -> void:
node.queue_free()


func interpolate_volume_linearly(value:float, node:Node) -> void:
## Converts a linear loudness value to decibel and sets that volume to
## the given [param node].
func interpolate_volume_linearly(value: float, node: Node) -> void:
node.volume_db = linear_to_db(value)


Expand Down
36 changes: 26 additions & 10 deletions addons/dialogic/Modules/Background/subsystem_backgrounds.gd
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
extends DialogicSubsystem

## Subsystem for managing backgrounds.

signal background_changed(info:Dictionary)


##
## This subsystem has many different helper methods for managing backgrounds.
## For instance, you can listen to background changes via
## [signal background_changed].


## Whenever a new background is set, this signal is emitted and contains a
## dictionary with the following keys: [br]
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `scene` | [type String] | The scene path of the new background. [br]
## `argument` | [type String] | Information given to the background on its update routine. [br]
## `fade_time` | [type float] | The time the background may take to transition in. [br]
## `same_scene`| [type bool] | If the new background uses the same Godot scene. [br]
signal background_changed(info: Dictionary)

## The default background Dialogic will use.
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
var default_background_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('DefaultBackgroundScene/default_background.tscn'))
## The default transition Dialogic will use.
var default_transition: String = get_script().resource_path.get_base_dir().path_join("Transitions/Defaults/simple_fade.gd")


#region STATE
####################################################################################################

func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR):
## Empties the current background state.
func clear_game_state(_clear_flag := DialogicGameHandler.ClearFlags.FULL_CLEAR) -> void:
update_background()


func load_game_state(load_flag:=LoadFlags.FULL_LOAD):
## Loads the background state from the current state info.
func load_game_state(_load_flag := LoadFlags.FULL_LOAD) -> void:
update_background(dialogic.current_state_info.get('background_scene', ''), dialogic.current_state_info.get('background_argument', ''), 0.0, default_transition, true)

#endregion
Expand Down Expand Up @@ -128,7 +143,8 @@ func _on_transition_finished(background_node:DialogicNode_BackgroundHolder, tran
background_node.color = Color.TRANSPARENT
transition_node.queue_free()


## Adds sub-viewport with the given background scene as child to
## Dialogic scene.
func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder) -> SubViewportContainer:
var v_con := SubViewportContainer.new()
var viewport := SubViewport.new()
Expand Down Expand Up @@ -160,7 +176,7 @@ func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder

return v_con


## Whether a background is set.
func has_background() -> bool:
return !dialogic.current_state_info.get('background_scene', '').is_empty() or !dialogic.current_state_info.get('background_argument','').is_empty()

Expand Down
19 changes: 13 additions & 6 deletions addons/dialogic/Modules/Save/subsystem_save.gd
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
extends DialogicSubsystem

### Subsystem that manages saving and loading data.
## Subsystem to save and load game states.
##
## This subsystem has many different helper methods save Dialogic or custom
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
## game data to named save slots.
##
## You can listen to saves via [signal saved]. \
## If you want to save, you can invoke [method save]. \
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved


## Emitted when a save happened.
## The [param info] contains the following keys:
## - slot_name: The `String` name of the slot that the game state was saved to.
## - is_autosave: `true` if the save was an autosave.
## Emitted when a save happened with the following info:
## [br]
## Key | Value Type | Value [br]
## ----------- | ------------- | ----- [br]
## `slot_name` | [type String] | The name of the slot that the game state was saved to. [br]
## `is_autosave` | [type bool] | `true`, if the save was an autosave. [br]
signal saved(info: Dictionary)


Expand Down
91 changes: 75 additions & 16 deletions addons/dialogic/Modules/Voice/subsystem_voice.gd
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
extends DialogicSubsystem

## Subsystem that manages setting voice lines for text events.


signal voiceline_started(info:Dictionary)
signal voiceline_finished(info:Dictionary)
# Emitted if the voiceline didn't end but was cut off
##
## It's recommended to use the [class DialogicVoiceEvent] to set the voice lines
## for text events and not start playing them directly.


## Whenever a new voiceline starts playing.
## The [param info] contains following keys and values:
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
## [br]
## Key | Value Type | Value [br]
## -------- | ------------- | ----- [br]
## `file` | [type String] | The path to file played. [br]
signal voiceline_started(info: Dictionary)


## Whenever a new voiceline finished playing.
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
## The [param info] contains following keys and values:
## [br]
## Key | Value Type | Value [br]
## ---------------- | ------------- | ----- [br]
## `file` | [type String] | The path to file played. [br]
## `remaining_time` | [type float] | The remaining time of the voiceline. [br]
signal voiceline_finished(info: Dictionary)


## Whenever a voiceline gets interrupted and does not finish playing.
## The [param info] contains following keys and values:
## [br]
## Key | Value Type | Value [br]
## ---------------- | ------------- | ----- [br]
## `file` | [type String] | The path to file played. [br]
## `remaining_time` | [type float] | The remaining time of the voiceline. [br]
signal voiceline_stopped(info:Dictionary)


## The current audio file being played.
var current_audio_file: String

## The audio player for the voiceline.
var voice_player := AudioStreamPlayer.new()

#region STATE
####################################################################################################

## Stops the current voice from playing.
func pause() -> void:
voice_player.stream_paused = true


## Resumes a paused voice.
func resume() -> void:
voice_player.stream_paused = false

Expand All @@ -30,53 +60,82 @@ func resume() -> void:

func _ready() -> void:
add_child(voice_player)
voice_player.finished.connect(_on_voice_finnished)
voice_player.finished.connect(_on_voice_finished)


func is_voiced(index:int) -> bool:
## Whether the current event is a text event and has a voice
## event before it.
func is_voiced(index: int) -> bool:
if dialogic.current_timeline_events[index] is DialogicTextEvent:

if dialogic.current_timeline_events[index-1] is DialogicVoiceEvent:
return true

return false


## Plays the voiceline. This will be invoked by Dialogic.
## Requires [method set_file] to be called before or nothing plays.
func play_voice() -> void:
voice_player.play()
voiceline_started.emit({'file':current_audio_file})
voiceline_started.emit({'file': current_audio_file})


func set_file(path:String) -> void:
## Set a voice file [param path] to be played, then invoke [method play_voice].
## This method is called by Dialogic and must be not be called.
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
##
## This method does not check if [param path] is a valid file.
##
## This method does not start playing the voiceline if [param path] and
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
## [member current_audio_file] are the same.
func set_file(path: String) -> void:
if current_audio_file == path:
return

current_audio_file = path
var audio: AudioStream = load(path)
voice_player.stream = audio


func set_volume(value:float) -> void:
## Set the volume to a [param value] in decibels.
func set_volume(value: float) -> void:
voice_player.volume_db = value


func set_bus(value:String) -> void:
voice_player.bus = value
## Set the voice player's bus to a [param bus_name].
func set_bus(bus_name: String) -> void:
voice_player.bus = bus_name


## Stops the current voiceline from playing.
func stop_audio() -> void:
if voice_player.playing:
voiceline_stopped.emit({'file':current_audio_file, 'remaining_time':get_remaining_time()})

voice_player.stop()


func _on_voice_finnished() -> void:
## Called when the voiceline finishes playing.
## Connected to [signal finished].
Jowan-Spooner marked this conversation as resolved.
Show resolved Hide resolved
func _on_voice_finished() -> void:
voiceline_finished.emit({'file':current_audio_file, 'remaining_time':get_remaining_time()})


## Returns the remaining time of the current voiceline in seconds.
##
## If there is no voiceline playing, returns `0`.
func get_remaining_time() -> float:
if not voice_player or !voice_player.playing:
if not voice_player or not voice_player.playing:
return 0.0
return voice_player.stream.get_length()-voice_player.get_playback_position()

var stream_length := voice_player.stream.get_length()
var playback_position := voice_player.get_playback_position()
var remaining_seconds := stream_length - playback_position

return remaining_seconds


## Whether there is still positive time remaining for the current voiceline.
func is_running() -> bool:
return get_remaining_time() > 0.0

Expand Down
Loading