Keep dialogue portrait slot stable

This commit is contained in:
2026-06-20 00:33:06 +09:00
parent 84cdbd1ea5
commit b5625b7a27
4 changed files with 25 additions and 7 deletions

View File

@@ -117,12 +117,12 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Manual campaign checkpoint save/load.
- Cinematic title screen uses the battlefield backdrop and Cao Cao portrait art, offers start/load/settings, leads new campaigns through a short Cao Cao and Xiahou Dun opening prologue, exposes the same audio/edge-scroll settings from the top in-game toolbar, and persists BGM/SFX volume, sound toggles, and edge-scroll preference in `user://heros_settings.json`.
- Victory and defeat result overlay.
- Dialogue portrait slot with officer default image paths, optional per-line overrides, and speaker-initial fallback.
- Dialogue portrait slot stays fixed across speaker and narration lines, with officer default image paths, optional per-line overrides, and speaker-initial or record fallback.
- Initial AI-generated photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He.
- AI-generated scenario battlefield backgrounds, transparent class units, transparent generic-enemy units, and transparent item icon assets live under `art/backgrounds`, `art/units`, and `art/items`.
- Battle HUD portrait thumbnails show the selected unit, or a hovered unit when nothing is selected.
- Selected and hovered unit focus text shows combat role, movement type, current terrain move cost, terrain bonuses, core stats, and status effects.
- Dialogue lines can place the portrait on the left or right side of an expanded visual-novel-style dialogue panel with progress and previous-line controls.
- Dialogue lines can mark speaking side for text alignment inside a fixed visual-novel-style dialogue panel with stable portrait, progress, and previous-line controls.
- Placeholder menu/battle BGM plus UI, movement, attack, tactic, item, victory, and defeat SFX.
- Floating combat text for damage, recovery, misses, support effects, poison ticks, action locks, level-ups, and promotions.
- Short unit slide animation for board movement.

File diff suppressed because one or more lines are too long

View File

@@ -9112,12 +9112,14 @@ func _update_dialogue_portrait(speaker: String, portrait_path: String) -> void:
return
var texture := _load_portrait_texture(portrait_path)
var has_portrait := texture != null
var has_placeholder := not speaker.is_empty()
dialogue_portrait_panel.visible = has_portrait or has_placeholder
var placeholder_text := _dialogue_portrait_initials(speaker)
if placeholder_text.is_empty():
placeholder_text = "기록"
dialogue_portrait_panel.visible = true
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
dialogue_portrait_label.text = placeholder_text
dialogue_portrait_label.visible = not has_portrait
func _localized_speaker_name(speaker: String) -> String:

View File

@@ -3288,6 +3288,22 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
if scene.dialogue_text_label == null or scene.dialogue_text_label.text != "「군령을 전하라.」":
failures.append("dialogue text should render as a concise quotation scroll")
scene._hide_dialogue_panel()
var narrator_lines := scene._normalized_dialogue_lines([{"text": "성채 위로 먼지가 올랐다."}])
if narrator_lines.is_empty():
failures.append("dialogue normalization should keep narration lines without a speaker")
else:
var portrait_index_before_narration: int = scene.dialogue_row.get_children().find(scene.dialogue_portrait_panel)
scene.active_dialogue_lines = narrator_lines
scene.active_dialogue_index = 0
scene._render_dialogue_line()
if scene.dialogue_portrait_panel == null or not scene.dialogue_portrait_panel.visible:
failures.append("speakerless narration should keep the dialogue portrait slot visible")
if scene.dialogue_portrait_label == null or scene.dialogue_portrait_label.text != "기록":
failures.append("speakerless narration should show a stable record placeholder")
var portrait_index_after_narration: int = scene.dialogue_row.get_children().find(scene.dialogue_portrait_panel)
if portrait_index_after_narration != portrait_index_before_narration:
failures.append("speakerless narration should not move the portrait slot")
scene._hide_dialogue_panel()
var enemy_lines := scene._normalized_dialogue_lines([{"speaker": "Wen Chou", "text": "전진!"}])
if enemy_lines.is_empty() or str((enemy_lines[0] as Dictionary).get("speaker", "")) != "문추":
failures.append("dialogue normalization should localize enemy speaker names")