Add dialogue portrait slots

This commit is contained in:
2026-06-18 03:38:45 +09:00
parent bdd38104f4
commit 2166ea51ef
7 changed files with 97 additions and 11 deletions

View File

@@ -84,6 +84,9 @@ var formation_list: VBoxContainer
var formation_status_label: Label
var formation_unit_id := ""
var dialogue_panel: PanelContainer
var dialogue_portrait_panel: PanelContainer
var dialogue_portrait_texture: TextureRect
var dialogue_portrait_label: Label
var dialogue_speaker_label: Label
var dialogue_text_label: Label
var dialogue_continue_button: Button
@@ -447,17 +450,42 @@ func _create_hud() -> void:
dialogue_panel.size = Vector2(760, 140)
root.add_child(dialogue_panel)
var dialogue_row := HBoxContainer.new()
dialogue_row.add_theme_constant_override("separation", 14)
dialogue_panel.add_child(dialogue_row)
dialogue_portrait_panel = PanelContainer.new()
dialogue_portrait_panel.custom_minimum_size = Vector2(96, 112)
dialogue_row.add_child(dialogue_portrait_panel)
var dialogue_portrait_stack := Control.new()
dialogue_portrait_stack.custom_minimum_size = Vector2(96, 112)
dialogue_portrait_panel.add_child(dialogue_portrait_stack)
dialogue_portrait_texture = TextureRect.new()
dialogue_portrait_texture.set_anchors_preset(Control.PRESET_FULL_RECT)
dialogue_portrait_texture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
dialogue_portrait_stack.add_child(dialogue_portrait_texture)
dialogue_portrait_label = Label.new()
dialogue_portrait_label.set_anchors_preset(Control.PRESET_FULL_RECT)
dialogue_portrait_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
dialogue_portrait_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
dialogue_portrait_label.add_theme_font_size_override("font_size", 22)
dialogue_portrait_stack.add_child(dialogue_portrait_label)
var dialogue_column := VBoxContainer.new()
dialogue_column.custom_minimum_size = Vector2(620, 112)
dialogue_column.add_theme_constant_override("separation", 8)
dialogue_panel.add_child(dialogue_column)
dialogue_row.add_child(dialogue_column)
dialogue_speaker_label = Label.new()
dialogue_speaker_label.custom_minimum_size = Vector2(720, 22)
dialogue_speaker_label.custom_minimum_size = Vector2(600, 22)
dialogue_column.add_child(dialogue_speaker_label)
dialogue_text_label = Label.new()
dialogue_text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
dialogue_text_label.custom_minimum_size = Vector2(720, 56)
dialogue_text_label.custom_minimum_size = Vector2(600, 56)
dialogue_column.add_child(dialogue_text_label)
dialogue_continue_button = Button.new()
@@ -1486,13 +1514,14 @@ func _normalized_dialogue_lines(lines: Array) -> Array:
continue
result.append({
"speaker": str(line.get("speaker", "")),
"text": text
"text": text,
"portrait": str(line.get("portrait", ""))
})
elif typeof(line) == TYPE_STRING:
var text := str(line)
if text.is_empty():
continue
result.append({"speaker": "", "text": text})
result.append({"speaker": "", "text": text, "portrait": ""})
return result
@@ -1533,12 +1562,54 @@ func _render_dialogue_line() -> void:
dialogue_speaker_label.text = speaker
dialogue_speaker_label.visible = not speaker.is_empty()
dialogue_text_label.text = str(line.get("text", ""))
_update_dialogue_portrait(speaker, str(line.get("portrait", "")))
dialogue_continue_button.text = "Close" if active_dialogue_index >= active_dialogue_lines.size() - 1 else "Next"
dialogue_panel.visible = true
_update_hud()
queue_redraw()
func _update_dialogue_portrait(speaker: String, portrait_path: String) -> void:
if dialogue_portrait_panel == null:
return
var texture := _load_dialogue_portrait(portrait_path)
var has_portrait := texture != null
var has_placeholder := not speaker.is_empty()
dialogue_portrait_panel.visible = has_portrait or has_placeholder
dialogue_portrait_texture.texture = texture
dialogue_portrait_texture.visible = has_portrait
dialogue_portrait_label.text = _dialogue_portrait_initials(speaker)
dialogue_portrait_label.visible = not has_portrait and has_placeholder
func _load_dialogue_portrait(portrait_path: String) -> Texture2D:
var normalized_path := portrait_path.strip_edges()
if normalized_path.is_empty():
return null
if not normalized_path.begins_with("res://") and not normalized_path.begins_with("user://"):
normalized_path = "res://%s" % normalized_path
if not ResourceLoader.exists(normalized_path, "Texture2D"):
return null
return load(normalized_path) as Texture2D
func _dialogue_portrait_initials(speaker: String) -> String:
var normalized_speaker := speaker.strip_edges()
if normalized_speaker.is_empty():
return ""
var parts := normalized_speaker.split(" ", false)
var initials := ""
for part in parts:
if str(part).is_empty():
continue
initials += str(part).substr(0, 1).to_upper()
if initials.length() >= 2:
break
if initials.is_empty():
return normalized_speaker.substr(0, 1).to_upper()
return initials
func _advance_dialogue() -> void:
if not _is_dialogue_visible():
return