xiandie/scene/dialog/balloon.gd

237 lines
8.3 KiB
GDScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends CanvasLayer
signal manually_skipped_line
@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
## 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")
#主要角色颜色
var color = dialogue_line.get_tag_value("color")
if color:
character_label.modulate = Color.WHITE
character_label.text = "[color=" + color + "]" + character_label.text + "[/color]"
elif GlobalConfig.CHARACTER_COLOR_MAP.has(dialogue_line.character):
character_label.modulate = GlobalConfig.CHARACTER_COLOR_MAP[dialogue_line.character]
else:
character_label.modulate = GlobalConfig.CHARACTER_COLOR_MAP["default"]
# 配色结束后匿名化处理
if dialogue_line.tags.has("anonymous"):
character_label.text = tr("", "dialogue")
#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 balloon
balloon.show()
will_hide_balloon = false
dialogue_label.show()
if not dialogue_line.text.is_empty():
dialogue_label.type_out()
#sound
var audio_time_len := 0.0
# 因为版权问题,有些 mp3 文件打不开,所以使用 ogg 格式
var audio_path = "res://asset/audio/peiyin/ogg/%s.ogg" % dialogue_line.translation_key
# var audio_path = "res://asset/audio/peiyin/%s.mp3" % dialogue_line.translation_key
if FileAccess.file_exists(audio_path):
var stream = load(audio_path)
audio_time_len = stream.get_length()
if audio_stream_player.stream != stream or not audio_stream_player.playing:
audio_stream_player.stream = stream
audio_stream_player.play()
elif audio_stream_player.playing:
audio_stream_player.stop()
if dialogue_label.is_typing:
await dialogue_label.finished_typing
if audio_stream_player.playing:
await audio_stream_player.finished
# 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()
# if dialogue_line.time != "":
# auto_play: 不管是否配置等待时长,都执行自动播放
if auto_play or next_dialogue_line.time:
# 如果音频时长为 0则等待玩家读完文本否则音频播放结束后等待 0.5 秒
var wait_time = 0.5
if not audio_time_len:
# 0.2 秒每个字符,最小 3 秒,最大 5 秒
wait_time = max(min(0.2 * dialogue_line.text.length(), 5.0), 3.0)
# 从 tags 中获取备注参数,覆盖默认等待时长
var wait = next_dialogue_line.get_tag_value("wait")
# eg. [#wait=2.5]
if wait:
wait_time = wait.to_float()
await get_tree().create_timer(wait_time).timeout
# 如果不再是当前 line则不跳转
if dialogue_line.translation_key == next_dialogue_line.translation_key:
next(next_dialogue_line.next_id)
# var time = next_dialogue_line.text.length() * 0.2 if next_dialogue_line.time == "auto" else next_dialogue_line.time.to_float()
# await get_tree().create_timer(time).timeout
# 如果当前 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()
)
# 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:
# 根据全局配置,是否允许忽略输入
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
# 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