diff --git a/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3 b/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3 new file mode 100644 index 0000000..12e166c Binary files /dev/null and b/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3 differ diff --git a/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3.import b/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3.import new file mode 100644 index 0000000..ca44528 --- /dev/null +++ b/game/_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3.import @@ -0,0 +1,19 @@ +[remap] + +importer="mp3" +type="AudioStreamMP3" +uid="uid://bu3mcagsgxth" +path="res://.godot/imported/Deadbird_Alright_listen_here.mp3-3cc73b7ad94c9ac3c61368e7a863ddc9.mp3str" + +[deps] + +source_file="res://_development/ayuroo/voicelines/conductor/Deadbird_Alright_listen_here.mp3" +dest_files=["res://.godot/imported/Deadbird_Alright_listen_here.mp3-3cc73b7ad94c9ac3c61368e7a863ddc9.mp3str"] + +[params] + +loop=false +loop_offset=0.0 +bpm=0.0 +beat_count=0 +bar_beats=4 diff --git a/game/_development/ayuroo/voicelines/conductor/test_dialog.tres b/game/_development/ayuroo/voicelines/conductor/test_dialog.tres new file mode 100644 index 0000000..e181bbf --- /dev/null +++ b/game/_development/ayuroo/voicelines/conductor/test_dialog.tres @@ -0,0 +1,10 @@ +[gd_resource type="Resource" script_class="DialogEntry" format=3 uid="uid://ju7es8xi1gpc"] + +[ext_resource type="Script" uid="uid://crem3vn3eyf38" path="res://src/core/dialog/dialog_entry.gd" id="1_41r8w"] + +[resource] +script = ExtResource("1_41r8w") +voiceline_path = "uid://bu3mcagsgxth" +subtitle_strings = PackedStringArray("Alright listen here, lass.", "Yer in big trouble.", "If yer helpin\' crooked", "[wave]D[/wave]", "[wave]DJ[/wave]", "[wave]DJ Gro[/wave]", "[wave]DJ Grooves[/wave]", "rig the awards-,", "And I can\'t take ya to jail,", "then yer got to help me even the score.") +subtitle_timings = PackedFloat32Array(0, 2.6, 4.2, 6, 6.5, 7, 7.35, 8, 9.6, 11) +metadata/_custom_type_script = "uid://crem3vn3eyf38" diff --git a/game/src/core/dialog/dialog_entry.gd b/game/src/core/dialog/dialog_entry.gd new file mode 100644 index 0000000..6f054d5 --- /dev/null +++ b/game/src/core/dialog/dialog_entry.gd @@ -0,0 +1,41 @@ +@tool +class_name DialogEntry +extends Resource + +@export_file("*.tres", "*.res", "*.ogg", "*.wav", "*.mp3") +var voiceline_path: String +@export var precache_vo_stream: bool = false + +@export var subtitle_strings: PackedStringArray = [] +@export var subtitle_timings: PackedFloat32Array = [] + +var _precached_stream: AudioStream + + +func _init() -> void: + if precache_vo_stream and not Engine.is_editor_hint(): + _precached_stream = load(voiceline_path) + + +func get_voiceline_stream() -> AudioStream: + return _precached_stream if precache_vo_stream else load(voiceline_path) + + +func get_subtitle_string_from_elapsed_time(elapsed_time: float) -> String: + var subtitle_string: String = "" + + for time_index: int in range(subtitle_timings.size()): + if subtitle_timings.get(time_index) >= elapsed_time: + break + + subtitle_string = subtitle_strings.get(time_index) + + return subtitle_string + # old (search from reverse) + #var timings: PackedFloat32Array = subtitle_timings.duplicate() + #timings.reverse() + + #for time_index: int in range(timings.size()): + #var time: float = timings.get(time_index) + #if time <= elapsed_time: + #return subtitle_strings.get((timings.size() - time_index) - 1) diff --git a/game/src/core/dialog/dialog_entry.gd.uid b/game/src/core/dialog/dialog_entry.gd.uid new file mode 100644 index 0000000..c1d49c3 --- /dev/null +++ b/game/src/core/dialog/dialog_entry.gd.uid @@ -0,0 +1 @@ +uid://crem3vn3eyf38 diff --git a/game/src/core/dialog/dialog_entry_playback.gd b/game/src/core/dialog/dialog_entry_playback.gd new file mode 100644 index 0000000..f92f523 --- /dev/null +++ b/game/src/core/dialog/dialog_entry_playback.gd @@ -0,0 +1,13 @@ +class_name DialogEntryPlayback + + +var dialog_entry: DialogEntry +var playback_position: float = 0.0 + + +func _init(entry: DialogEntry) -> void: + dialog_entry = entry + + +func get_dialog_subtitle() -> String: + return dialog_entry.get_subtitle_string_from_elapsed_time(playback_position) diff --git a/game/src/core/dialog/dialog_entry_playback.gd.uid b/game/src/core/dialog/dialog_entry_playback.gd.uid new file mode 100644 index 0000000..2c7ffca --- /dev/null +++ b/game/src/core/dialog/dialog_entry_playback.gd.uid @@ -0,0 +1 @@ +uid://byt7cdieik05m diff --git a/game/src/core/dialog/dialog_player.gd b/game/src/core/dialog/dialog_player.gd new file mode 100644 index 0000000..6d4f32e --- /dev/null +++ b/game/src/core/dialog/dialog_player.gd @@ -0,0 +1,91 @@ +class_name DialogPlayer +extends Node + +signal dialog_finished(dialog_key: StringName) + +static var active_dialog_playbacks: Array[DialogEntryPlayback] = [] + +@export_node_path("AudioStreamPlayer", "AudioStreamPlayer2D", "AudioStreamPlayer3D") +var audio_player: NodePath: set = set_audio_player + +@export var dialogs: Dictionary[StringName, DialogEntry] = {} + +var _audio_player: Node +var _current_dialog_key: StringName +var _current_dialog_playback: DialogEntryPlayback + + +func _ready() -> void: + set_process(false) + set_audio_player(audio_player) + + +func _process(_delta: float) -> void: + if is_instance_valid(_audio_player) and is_instance_valid(_current_dialog_playback): + _current_dialog_playback.playback_position = _audio_player.get_playback_position() + + +func set_audio_player(audio_player_path: NodePath) -> void: + if is_instance_valid(_audio_player): + _audio_player.finished.disconnect(_on_dialog_finished) + + audio_player = audio_player_path + _audio_player = get_node_or_null(audio_player) + + if not is_instance_valid(_audio_player): + set_process(false) + return + + if ( + not _audio_player.has_method(&"play") + or not _audio_player.has_method(&"get_playback_position") + or not &"playing" in _audio_player + or not _audio_player.has_signal(&"finished") + ): + _audio_player = null + push_error("audio_player is not a valid player.") + return + + set_process(_audio_player.playing) + + if not _audio_player.finished.is_connected(_on_dialog_finished): + _audio_player.finished.connect(_on_dialog_finished) + + +func play(entry_key: StringName, from_position: float = 0.0) -> void: + var dialog_entry: DialogEntry = dialogs.get(entry_key) + + if is_instance_valid(dialog_entry): + stop_dialog() + + _audio_player.stream = dialog_entry.get_voiceline_stream() + _current_dialog_key = entry_key + _current_dialog_playback = DialogEntryPlayback.new(dialog_entry) + active_dialog_playbacks.append(_current_dialog_playback) + + _audio_player.play(from_position) + set_process(true) + + +func get_current_dialog_entry() -> DialogEntry: + return dialogs.get(_current_dialog_key) + + +func get_playback_position() -> float: + return _audio_player.get_playback_position() if is_instance_valid(_audio_player) else 0.0 + + +func stop_dialog() -> void: + active_dialog_playbacks.erase(_current_dialog_playback) + _current_dialog_key = &"" + _current_dialog_playback = null + _audio_player.stream = null + + +func _on_dialog_finished() -> void: + var entry: DialogEntry = dialogs.get(_current_dialog_key) + + stop_dialog() + set_process(false) + + dialog_finished.emit(entry) diff --git a/game/src/core/dialog/dialog_player.gd.uid b/game/src/core/dialog/dialog_player.gd.uid new file mode 100644 index 0000000..1ba9f1d --- /dev/null +++ b/game/src/core/dialog/dialog_player.gd.uid @@ -0,0 +1 @@ +uid://blxophwhpim0l diff --git a/game/src/gameplay/characters/player/player_character.tscn b/game/src/gameplay/characters/player/player_character.tscn index f9bd995..a04c628 100644 --- a/game/src/gameplay/characters/player/player_character.tscn +++ b/game/src/gameplay/characters/player/player_character.tscn @@ -14,6 +14,7 @@ [ext_resource type="Script" uid="uid://dyoa3tnirv7wh" path="res://src/core/camera/camera_effect_target.gd" id="7_ghuot"] [ext_resource type="Script" uid="uid://gvuvxikepb2r" path="res://src/gameplay/characters/player/hud.gd" id="11_y2ktv"] [ext_resource type="Texture2D" uid="uid://cwvfdx2v41k0h" path="res://src/ui/hud/crosshair.png" id="13_qx1cj"] +[ext_resource type="PackedScene" uid="uid://ct2u3f03lwsnr" path="res://src/ui/hud/hud_subtitle.tscn" id="15_hqe5n"] [sub_resource type="Resource" id="Resource_vq0uu"] script = ExtResource("2_1ixuj") @@ -289,4 +290,5 @@ scale = Vector2(0.25, 0.25) sprite_frames = SubResource("SpriteFrames_p81y6") animation = &"to_hollow" frame = 5 -frame_progress = 1.0 + +[node name="HudSubtitle" parent="Hud" unique_id=123148710 instance=ExtResource("15_hqe5n")] diff --git a/game/src/ui/hud/hud_subtitle.gd b/game/src/ui/hud/hud_subtitle.gd new file mode 100644 index 0000000..71792ca --- /dev/null +++ b/game/src/ui/hud/hud_subtitle.gd @@ -0,0 +1,12 @@ +class_name HudSubtitle +extends Control + +@onready var rich_text_label: RichTextLabel = %RichTextLabel + + +func _process(_delta: float) -> void: + var parts: PackedStringArray = [] + for playback: DialogEntryPlayback in DialogPlayer.active_dialog_playbacks: + parts.append(playback.get_dialog_subtitle()) + + rich_text_label.text = "\n".join(parts) diff --git a/game/src/ui/hud/hud_subtitle.gd.uid b/game/src/ui/hud/hud_subtitle.gd.uid new file mode 100644 index 0000000..815a955 --- /dev/null +++ b/game/src/ui/hud/hud_subtitle.gd.uid @@ -0,0 +1 @@ +uid://ssyyrfvcmird diff --git a/game/src/ui/hud/hud_subtitle.tscn b/game/src/ui/hud/hud_subtitle.tscn new file mode 100644 index 0000000..73c9c47 --- /dev/null +++ b/game/src/ui/hud/hud_subtitle.tscn @@ -0,0 +1,37 @@ +[gd_scene format=3 uid="uid://ct2u3f03lwsnr"] + +[ext_resource type="Script" uid="uid://ssyyrfvcmird" path="res://src/ui/hud/hud_subtitle.gd" id="1_07tug"] + +[node name="HudSubtitle" type="Control" unique_id=123148710] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("1_07tug") +metadata/_custom_type_script = "uid://ssyyrfvcmird" + +[node name="MarginContainer" type="MarginContainer" parent="." unique_id=441028605] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 12 + +[node name="RichTextLabel" type="RichTextLabel" parent="MarginContainer" unique_id=1994554868] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/outline_size = 4 +bbcode_enabled = true +text = "Example subtitle" +horizontal_alignment = 1 +vertical_alignment = 2 diff --git a/game/tools/dialog_test_scene.tscn b/game/tools/dialog_test_scene.tscn new file mode 100644 index 0000000..bd19aa6 --- /dev/null +++ b/game/tools/dialog_test_scene.tscn @@ -0,0 +1,68 @@ +[gd_scene format=3 uid="uid://bqx6t4agunl7h"] + +[ext_resource type="PackedScene" uid="uid://ct2u3f03lwsnr" path="res://src/ui/hud/hud_subtitle.tscn" id="1_koq4j"] +[ext_resource type="Script" uid="uid://blxophwhpim0l" path="res://src/core/dialog/dialog_player.gd" id="2_4qqmy"] +[ext_resource type="Script" uid="uid://crem3vn3eyf38" path="res://src/core/dialog/dialog_entry.gd" id="3_j86jr"] +[ext_resource type="Resource" uid="uid://ju7es8xi1gpc" path="res://_development/ayuroo/voicelines/conductor/test_dialog.tres" id="4_pnxnv"] + +[sub_resource type="GDScript" id="GDScript_j86jr"] +script/source = "extends Control + +@export var dialog_key: StringName +@export_range(0.0, 1.0, 0.001, \"or_greater\", \"suffix:s\") var from_position: float = 0.0 +@export_range(0.0, 1.0, 0.001, \"or_greater\", \"suffix:s\") var inital_delay: float = 0.5 + +@onready var dialog_player: DialogPlayer = $DialogPlayer +@onready var replay_button: Button = %ReplayButton + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + replay_button.pressed.connect(_on_replay_button_pressed) + + await get_tree().create_timer(inital_delay).timeout + _on_replay_button_pressed() + + +func _on_replay_button_pressed() -> void: + dialog_player.play(dialog_key, from_position) +" + +[node name="DialogTestScene" type="Control" unique_id=1728136061] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = SubResource("GDScript_j86jr") +dialog_key = &"listen_here" + +[node name="HudSubtitle" parent="." unique_id=123148710 instance=ExtResource("1_koq4j")] +layout_mode = 1 + +[node name="ReplayButton" type="Button" parent="." unique_id=283603298] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -30.0 +offset_top = -15.5 +offset_right = 30.0 +offset_bottom = 15.5 +grow_horizontal = 2 +grow_vertical = 2 +text = "Replay" + +[node name="DialogPlayer" type="Node" parent="." unique_id=928993916] +script = ExtResource("2_4qqmy") +audio_player = NodePath("AudioStreamPlayer") +dialogs = Dictionary[StringName, ExtResource("3_j86jr")]({ +&"listen_here": ExtResource("4_pnxnv") +}) +metadata/_custom_type_script = "uid://blxophwhpim0l" + +[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="DialogPlayer" unique_id=1284459]