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 = &"" @onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer @onready var balloon: Control = %Balloon @onready var character_label: RichTextLabel = %CharacterLabel @onready var dialogue_label: DialogueLabel = %DialogueLabel @onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu ## 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 const CHARACTER_COLOR_MAP = { "音效": Color.DARK_VIOLET, "美术": Color.WHITE_SMOKE, "default": Color.LIGHT_SALMON, } ## The current line var dialogue_line: DialogueLine: set(next_dialogue_line): is_waiting_for_input = false balloon.focus_mode = Control.FOCUS_ALL balloon.grab_focus() # The dialogue has finished so close the balloon if not next_dialogue_line: queue_free() return # If the node isn't ready yet then none of the labels will be ready yet either if not is_node_ready(): await ready dialogue_line = next_dialogue_line 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"] #print(dialogue_line.character,character_label.modulate) dialogue_label.hide() _setup_content_text() dialogue_label.dialogue_line = dialogue_line responses_menu.hide() responses_menu.set_responses(dialogue_line.responses) # Show our balloons balloon.show() will_hide_balloon = false dialogue_label.show() if not dialogue_line.text.is_empty(): dialogue_label.type_out() 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() # next(next_dialogue_line.next_id) # # 如果当前 line 运行结束,则 queue free 释放资源 # if dialogue_line == next_dialogue_line: # queue_free() func _ready() -> void: layer = GlobalConfig.CANVAS_LAYER_DIALOG balloon.hide() Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated) # If the responses menu doesn't have a next action set, use this one if responses_menu.next_action.is_empty(): responses_menu.next_action = next_action # 自定义获得文本,从 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 _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 is_instance_valid(dialogue_label): 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: if not is_node_ready(): await ready 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) ## 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_mutated(_mutation: Dictionary) -> void: is_waiting_for_input = false will_hide_balloon = true get_tree().create_timer(0.1).timeout.connect(func(): if will_hide_balloon: will_hide_balloon = false balloon.hide() ) # debug balloon 无需接管输入事件 # 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 _on_balloon_gui_input(event: InputEvent) -> void: # # 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 # # 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: # #next(dialogue_line.next_id) # 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) func _on_responses_menu_response_selected(response: DialogueResponse) -> void: next(response.next_id) #endregion