diff --git a/addons/dialogic/Modules/Text/event_text.gd b/addons/dialogic/Modules/Text/event_text.gd index b02043d9e..02b9c9211 100644 --- a/addons/dialogic/Modules/Text/event_text.gd +++ b/addons/dialogic/Modules/Text/event_text.gd @@ -141,9 +141,9 @@ func _execute() -> void: final_text = segment dialogic.Text.about_to_show_text.emit({'text':final_text, 'character':character, 'portrait':portrait, 'append': is_append}) - final_text = await dialogic.Text.update_dialog_text(final_text, false, is_append) - + await dialogic.Text.update_text_boxes(final_text, false) _try_play_current_line_voice() + final_text = dialogic.Text.update_dialog_text(final_text, false, is_append) _mark_as_read(final_text) diff --git a/addons/dialogic/Modules/Text/node_dialog_text.gd b/addons/dialogic/Modules/Text/node_dialog_text.gd index bd5bd1c51..d3bd12a5f 100644 --- a/addons/dialogic/Modules/Text/node_dialog_text.gd +++ b/addons/dialogic/Modules/Text/node_dialog_text.gd @@ -17,19 +17,27 @@ enum Alignment {LEFT, CENTER, RIGHT} var revealing := false var base_visible_characters := 0 -# time per character -var lspeed:float = 0.01 -var speed_counter:float = 0 +# Letter speed used per revealed character. +var lspeed: float = 0.01 +# The used speed per revealed character. +# May be overwritten when syncing reveal speed to voice. +var active_speed: float = lspeed +var speed_counter: float = 0 + +func _set(property: StringName, what: Variant) -> bool: + if property == 'text' and typeof(what) == TYPE_STRING: -func _set(property:StringName, what:Variant) -> bool: - if property == &'text' and typeof(what) == TYPE_STRING: text = what + if hide_when_empty: - textbox_root.visible = !text.is_empty() + textbox_root.visible = !what.is_empty() + return true return false + return false + func _ready() -> void: # add to necessary @@ -45,7 +53,8 @@ func _ready() -> void: # this is called by the DialogicGameHandler to set text -func reveal_text(_text:String, keep_previous:bool=false) -> void: + +func reveal_text(_text: String, keep_previous:=false) -> void: if !enabled: return show() @@ -59,10 +68,11 @@ func reveal_text(_text:String, keep_previous:bool=false) -> void: elif alignment == Alignment.RIGHT: text = '[right]'+text visible_characters = 0 + else: base_visible_characters = len(text) visible_characters = len(text) - text = text+_text + text = text + _text # If Auto-Skip is enabled and we append the text (keep_previous), # we can skip revealing the text and just show it all at once. @@ -70,19 +80,32 @@ func reveal_text(_text:String, keep_previous:bool=false) -> void: visible_characters = 1 return + if DialogicUtil.autoload().Text.is_text_voice_synced() and DialogicUtil.autoload().Voice.is_running(): + var total_characters := get_total_character_count() as float + var remaining_time: float = DialogicUtil.autoload().Voice.get_remaining_time() + var synced_speed := remaining_time / total_characters + active_speed = synced_speed + + else: + active_speed = lspeed + + revealing = true speed_counter = 0 started_revealing_text.emit() -# called by the timer -> reveals more text +## Reveals one additional character. func continue_reveal() -> void: if visible_characters <= get_total_character_count(): revealing = false - await DialogicUtil.autoload().Text.execute_effects(visible_characters-base_visible_characters, self, false) + + var current_index := visible_characters - base_visible_characters + await DialogicUtil.autoload().Text.execute_effects(current_index, self, false) if visible_characters == -1: return + revealing = true visible_characters += 1 @@ -96,8 +119,7 @@ func continue_reveal() -> void: DialogicUtil.autoload().Inputs.block_input(0.3) -# shows all the text imidiatly -# called by this thing itself or the DialogicGameHandler +## Reveals the entire text instantly. func finish_text() -> void: visible_ratio = 1 DialogicUtil.autoload().Text.execute_effects(-1, self, true) @@ -107,13 +129,13 @@ func finish_text() -> void: finished_revealing_text.emit() -# Calls continue_reveal. Used instead of a timer to allow multiple reveals per frame. -func _process(delta:float) -> void: +## Checks if the next character in the text can be revealed. +func _process(delta: float) -> void: if !revealing or DialogicUtil.autoload().paused: return speed_counter += delta - while speed_counter > lspeed and revealing and !DialogicUtil.autoload().paused: - speed_counter -= lspeed + while speed_counter > active_speed and revealing and !DialogicUtil.autoload().paused: + speed_counter -= active_speed continue_reveal() diff --git a/addons/dialogic/Modules/Text/subsystem_text.gd b/addons/dialogic/Modules/Text/subsystem_text.gd index 44e5eab3d..c31b2c912 100644 --- a/addons/dialogic/Modules/Text/subsystem_text.gd +++ b/addons/dialogic/Modules/Text/subsystem_text.gd @@ -30,13 +30,16 @@ var text_modifiers := [] # set by the [speed] effect, multies the letter speed and [pause] effects -var speed_multiplier := 1.0 +var _speed_multiplier := 1.0 # stores the pure letter speed (unmultiplied) var _pure_letter_speed := 0.1 var _letter_speed_absolute := false +var _voice_synced_text := false + var _autopauses := {} + #################################################################################################### ## STATE #################################################################################################### @@ -54,7 +57,6 @@ func clear_game_state(clear_flag:=DialogicGameHandler.ClearFlags.FULL_CLEAR) -> text_node.textbox_root.hide() - func load_game_state(load_flag:=LoadFlags.FULL_LOAD) -> void: update_dialog_text(dialogic.current_state_info.get('text', ''), true) var character:DialogicCharacter = null @@ -83,46 +85,61 @@ func parse_text(text:String, type:int=TextTypes.DIALOG_TEXT, variables:= true, g text = dialogic.Glossary.parse_glossary(text) return text - -## Shows the given text on all visible DialogText nodes. -## Instant can be used to skip all revieling. -## If additional is true, the previous text will be kept. -func update_dialog_text(text:String, instant:bool= false, additional:= false) -> String: - update_text_speed() - connect_meta_signals() - +## When an event updates the text spoken, this can adjust the state of +## the dialog text box. +## This method is async. +func update_text_boxes(text: String, instant: bool = false) -> void: if text.is_empty(): await hide_text_boxes(instant) else: await show_text_boxes(instant) + if !dialogic.current_state_info['text'].is_empty(): animation_textbox_new_text.emit() + if dialogic.Animations.is_animating(): await dialogic.Animations.finished + + +## Shows the given text on all visible DialogText nodes. +## Instant can be used to skip all revieling. +## If additional is true, the previous text will be kept. +func update_dialog_text(text: String, instant := false, additional := false) -> String: + update_text_speed() + + if !instant: dialogic.current_state = dialogic.States.REVEALING_TEXT dialogic.current_state_info['text'] = text + for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'): + connect_meta_signals(text_node) + if text_node.enabled and (text_node == text_node.textbox_root or text_node.textbox_root.is_visible_in_tree()): + if instant: text_node.text = text + else: text_node.reveal_text(text, additional) + if !text_node.finished_revealing_text.is_connected(_on_dialog_text_finished): text_node.finished_revealing_text.connect(_on_dialog_text_finished) + dialogic.current_state_info['text_parsed'] = (text_node as RichTextLabel).get_parsed_text() - # also resets temporary autoadvance and noskip settings: + # Reset Auto-Advance temporarily and the No-Skip setting: update_text_speed(-1, false, 1, -1) - dialogic.Inputs.auto_advance.enabled_until_next_event = false dialogic.Inputs.auto_advance.override_delay_for_current_event = -1 dialogic.Inputs.set_manualadvance(true, true) + set_text_reveal_skippable(true, true) + return text -func _on_dialog_text_finished(): +func _on_dialog_text_finished() -> void: text_finished.emit({'text':dialogic.current_state_info['text'], 'character':dialogic.current_state_info['speaker']}) @@ -209,33 +226,55 @@ func hide_next_indicators(_fake_arg = null) -> void: next_indicator.hide() -func update_text_speed(letter_speed:float = -1, absolute:bool = false, _speed_multiplier:float= -1, _user_speed:float=-1) -> void: +## This method will sync the text speed to the voice audio clip length, if a +## voice is playing. +## For instance, if the voice is playing for four seconds, the text will finish +## revealing after this time. +## This feature ignores Auto-Pauses on letters. Pauses via BBCode will desync +## the reveal. +func set_text_voice_synced(enabled: bool = true) -> void: + _voice_synced_text = enabled + update_text_speed() + + +## Returns whether voice-synced text is enabled. +func is_text_voice_synced() -> bool: + return _voice_synced_text + + +## Sets how fast text will be revealed. +## +## [param absolute] will force test to display at the given speed, regardless +## of the user's text speed setting. +## +## [param _speed_multiplier] adjusts the speed of the text, if set to -1, +## the value won't be updated and the current value will persist. +## +## [param _user_speed] adjusts the speed of the text, if set to -1, the +## project setting 'text_speed' will be used.operator +func update_text_speed(letter_speed: float = -1, + absolute := false, + speed_multiplier := _speed_multiplier, + user_speed: float = dialogic.Settings.get_setting('text_speed', 1)) -> void: + if letter_speed == -1: letter_speed = ProjectSettings.get_setting('dialogic/text/letter_speed', 0.01) + _pure_letter_speed = letter_speed _letter_speed_absolute = absolute - - if _speed_multiplier == -1: - _speed_multiplier = speed_multiplier - else: - speed_multiplier = _speed_multiplier - - if _user_speed == -1: - _user_speed = dialogic.Settings.get_setting('text_speed', 1) - + _speed_multiplier = speed_multiplier for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'): if absolute: text_node.lspeed = letter_speed else: - text_node.lspeed = letter_speed*_speed_multiplier*_user_speed - - + text_node.lspeed = letter_speed * _speed_multiplier * user_speed func set_text_reveal_skippable(skippable:= true, temp:=false) -> void: if !dialogic.current_state_info.has('text_reveal_skippable'): dialogic.current_state_info['text_reveal_skippable'] = {'enabled':false, 'temp_enabled':false} + if temp: dialogic.current_state_info['text_reveal_skippable']['temp_enabled'] = skippable else: @@ -352,14 +391,15 @@ func _update_user_speed(user_speed:float) -> void: update_text_speed(_pure_letter_speed, _letter_speed_absolute) -func connect_meta_signals() -> void: - for text_node in get_tree().get_nodes_in_group('dialogic_dialog_text'): - if not text_node.meta_clicked.is_connected(emit_meta_signal): - text_node.meta_clicked.connect(emit_meta_signal.bind("meta_clicked")) - if not text_node.meta_hover_started.is_connected(emit_meta_signal): - text_node.meta_hover_started.connect(emit_meta_signal.bind("meta_hover_started")) - if not text_node.meta_hover_ended.is_connected(emit_meta_signal): - text_node.meta_hover_ended.connect(emit_meta_signal.bind("meta_hover_ended")) +func connect_meta_signals(text_node: Node) -> void: + if not text_node.meta_clicked.is_connected(emit_meta_signal): + text_node.meta_clicked.connect(emit_meta_signal.bind("meta_clicked")) + + if not text_node.meta_hover_started.is_connected(emit_meta_signal): + text_node.meta_hover_started.connect(emit_meta_signal.bind("meta_hover_started")) + + if not text_node.meta_hover_ended.is_connected(emit_meta_signal): + text_node.meta_hover_ended.connect(emit_meta_signal.bind("meta_hover_ended")) func emit_meta_signal(meta:Variant, sig:String) -> void: @@ -418,12 +458,15 @@ func effect_pause(text_node:Control, skipped:bool, argument:String) -> void: var text_speed: float = dialogic.Settings.get_setting('text_speed', 1) if argument: + if argument.ends_with('!'): await get_tree().create_timer(float(argument.trim_suffix('!'))).timeout - elif speed_multiplier != 0 and dialogic.Settings.get_setting('text_speed', 1) != 0: - await get_tree().create_timer(float(argument)*speed_multiplier*dialogic.Settings.get_setting('text_speed', 1)).timeout - elif speed_multiplier != 0 and dialogic.Settings.get_setting('text_speed', 1) != 0: - await get_tree().create_timer(0.5*speed_multiplier*dialogic.Settings.get_setting('text_speed', 1)).timeout + + elif _speed_multiplier != 0 and dialogic.Settings.get_setting('text_speed', 1) != 0: + await get_tree().create_timer(float(argument) * _speed_multiplier * dialogic.Settings.get_setting('text_speed', 1)).timeout + + elif _speed_multiplier != 0 and dialogic.Settings.get_setting('text_speed', 1) != 0: + await get_tree().create_timer(0.5 * _speed_multiplier*dialogic.Settings.get_setting('text_speed', 1)).timeout func effect_speed(text_node:Control, skipped:bool, argument:String) -> void: