extends CanvasLayer @export var force_locale :String: set(val): force_locale = val if force_locale: TranslationServer.set_locale(force_locale) @export var auto_play := true ## The action to use for advancing the dialogue @export var next_action: StringName = &"interact" ## The action to use to skip typing the dialogue @export var skip_action: StringName = &"" ## The dialogue resource var resource: DialogueResource ## Temporary game states var temporary_game_states: Array = [] ## See if we are waiting for the player var is_waiting_for_input: bool = false ## See if we are running a long mutation and should hide the balloon var will_hide_balloon: bool = false ## The current line var dialogue_line: DialogueLine: set(value): if value: dialogue_line = value apply_dialogue_line() else: # The dialogue has finished so close the balloon queue_free() get: return dialogue_line ## A cooldown timer for delaying the balloon hide when encountering a mutation. var mutation_cooldown: Timer = Timer.new() ## The base balloon anchor @onready var balloon: Control = %Balloon ## The label showing the name of the currently speaking character @onready var character_label: RichTextLabel = %CharacterLabel ## The label showing the currently spoken dialogue @onready var dialogue_label: DialogueLabel = %DialogueLabel @onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer func _ready() -> void: balloon.hide() Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated) mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout) add_child(mutation_cooldown) # 自定义获得文本,从 tags 中获取备注参数 func _setup_content_text() -> void: var translation_key = dialogue_line.translation_key var text if translation_key: text = tr(translation_key, "dialogue") if text == translation_key: text = dialogue_line.text else: text = dialogue_line.text if dialogue_line.tags.has("shake"): # eg. [shake rate=20 level=10][/shake] text = "[shake rate=20 level=6]" + text + "[/shake]" character_label.text = "[shake rate=20 level=6]" + character_label.text + "[/shake]" if dialogue_line.tags.has("wave"): # eg. [wave amp=25 freq=5][/wave] text = "[wave amp=15 freq=5]" + text + "[/wave]" character_label.text = "[wave amp=15 freq=5]" + character_label.text + "[/wave]" if dialogue_line.tags.has("item"): # orange color text = "[color=orange]" + text + "[/color]" dialogue_line.text = text # func _unhandled_input(_event: InputEvent) -> void: # # Only the balloon is allowed to handle input while it's showing # get_viewport().set_input_as_handled() # func _notification(what: int) -> void: # ## Detect a change of locale and update the current dialogue line to show the new language # if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label): # _locale = TranslationServer.get_locale() # var visible_ratio = dialogue_label.visible_ratio # self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id) # if visible_ratio < 1: # dialogue_label.skip_typing() ## Start some dialogue func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void: temporary_game_states = [self] + extra_game_states is_waiting_for_input = false resource = dialogue_resource self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states) const CHARACTER_COLOR_MAP = { "音效": Color.DARK_VIOLET, "美术": Color.WHITE_SMOKE, "default": Color.LIGHT_SALMON, } ## Apply any changes to the balloon given a new [DialogueLine]. func apply_dialogue_line() -> void: mutation_cooldown.stop() is_waiting_for_input = false balloon.focus_mode = Control.FOCUS_ALL balloon.grab_focus() character_label.visible = not dialogue_line.character.is_empty() character_label.text = tr(dialogue_line.character, "dialogue") #主要角色颜色 if CHARACTER_COLOR_MAP.has(dialogue_line.character): character_label.modulate = CHARACTER_COLOR_MAP[dialogue_line.character] else: character_label.modulate = CHARACTER_COLOR_MAP["default"] dialogue_label.hide() _setup_content_text() dialogue_label.dialogue_line = dialogue_line # Show our balloon balloon.show() will_hide_balloon = false dialogue_label.show() if not dialogue_line.text.is_empty(): dialogue_label.type_out() # # Wait for input # if dialogue_line.responses.size() > 0: # balloon.focus_mode = Control.FOCUS_NONE # responses_menu.show() # else: is_waiting_for_input = true # balloon.focus_mode = Control.FOCUS_ALL # balloon.grab_focus() await get_tree().create_timer(2.0).timeout # debug line 不需要下一行,直接释放(避免触发 dialogue_ended 信号) queue_free() ## Go to the next line func next(next_id: String) -> void: self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states) #region Signals func _on_mutation_cooldown_timeout() -> void: if will_hide_balloon: will_hide_balloon = false balloon.hide() func _on_mutated(_mutation: Dictionary) -> void: is_waiting_for_input = false will_hide_balloon = true mutation_cooldown.start(0.1) # func _on_balloon_gui_input(event: InputEvent) -> void: # # 根据全局配置,是否允许忽略输入 # if temporary_game_states.has(GlobalConfig.DIALOG_IGNORE_INPUT): # return # # See if we need to skip typing of the dialogue # if dialogue_label.is_typing: # if event.is_action_pressed("interact") or event.is_action_pressed("cancel"): # dialogue_label.skip_typing() # get_viewport().set_input_as_handled() # return # # var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed() # # var skip_button_was_pressed: bool = event.is_action_pressed(skip_action) # # if mouse_was_clicked or skip_button_was_pressed: # # get_viewport().set_input_as_handled() # # dialogue_label.skip_typing() # # return # if not is_waiting_for_input: return # if dialogue_line.responses.size() > 0: return # When there are no response options the balloon itself is the clickable thing # get_viewport().set_input_as_handled() # if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT: # next(dialogue_line.next_id) # elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon: # if event.is_action_pressed("interact") or event.is_action_pressed("cancel"): # # if event.is_action_pressed("interact") and get_viewport().gui_get_focus_owner() == balloon: # get_viewport().set_input_as_handled() # manually_skipped_line.emit() # next(dialogue_line.next_id)