Improve readable text backings

This commit is contained in:
2026-06-20 17:16:43 +09:00
parent cce0207ce7
commit 81672d5a27
3 changed files with 90 additions and 14 deletions

View File

@@ -132,7 +132,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
- Campaign completion screen. - Campaign completion screen.
- New campaign save reset. - New campaign save reset.
- Manual campaign checkpoint save/load. - Manual campaign checkpoint save/load.
- Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board and title buttons keep safer source-art proportions, linear filtering, tile-fit 9-slice surfaces, and aspect-locked canvas scaling for QHD fullscreen, while story/briefing action buttons use crisp flat readable surfaces. New campaigns then move into a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, a text-only chapter-side story ledger instead of exposed numeric cards or tick marks, `다음 기록`/`군막으로`/`전투 준비` story controls, and a bottom scroll caption panel. Briefing, dialogue, notice, auto-turn, and right-side battle information panels now favor high-contrast flat paper/ink text surfaces, larger Korean type, and explicit line spacing over busy decorative frames. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, show the active music/SFX mix beside the preview button, include one-click audio recovery, and persist preferences in `user://heros_settings.json`. - Cinematic title screen uses a dedicated ancient-war-camp dawn backdrop, Cao Cao portrait art, and a compact left-side command board with `처음부터`, `로드하기`, and `환경설정`; its generated command board and title buttons keep safer source-art proportions, linear filtering, tile-fit 9-slice surfaces, and aspect-locked canvas scaling for QHD fullscreen, while story/briefing action buttons use crisp flat readable surfaces. New campaigns then move into a generated four-panel Yellow Turban rebellion, Cao Cao resolve, Xiahou Dun meeting, and Yingchuan contact prologue with full-screen story artwork, a chapter seal, a text-only chapter-side story ledger instead of exposed numeric cards or tick marks, `다음 기록`/`군막으로`/`전투 준비` story controls, and a bottom scroll caption panel. Briefing, dialogue, notice, auto-turn, command-hint, title settings/load, battle log, and right-side battle information panels now favor high-contrast flat paper/ink text surfaces, larger Korean type, and explicit line spacing over busy decorative frames. Title and in-game settings expose clearer background-music, combat-SFX, edge-scroll, and fullscreen display controls, show the active music/SFX mix beside the preview button, include one-click audio recovery, and persist preferences in `user://heros_settings.json`.
- Victory and defeat result overlay. - Victory and defeat result overlay.
- 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. - 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. - Initial AI-generated photorealistic officer portraits for Cao Cao, Xiahou Dun, Xiahou Yuan, Cao Ren, Dian Wei, Guo Jia, and Zhang He.

View File

@@ -852,9 +852,11 @@ func _panel_texture_spec(variant: String) -> Dictionary:
func _panel_variant_prefers_flat_text_surface(variant: String) -> bool: func _panel_variant_prefers_flat_text_surface(variant: String) -> bool:
return variant in [ return variant in [
"compact",
"dialogue", "dialogue",
"speech_scroll", "speech_scroll",
"notice_edict", "notice_edict",
"target_hint",
"cinematic_caption", "cinematic_caption",
"story_caption" "story_caption"
] ]
@@ -983,14 +985,14 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void:
margin = 18 margin = 18
shadow_size = 6 shadow_size = 6
"hud_info": "hud_info":
fill = Color(0.045, 0.050, 0.048, 0.90) fill = Color(0.045, 0.050, 0.048, 0.988)
border = Color(0.20, 0.24, 0.21, 0.96) border = Color(0.20, 0.24, 0.21, 1.0)
border_width = 2 border_width = 2
radius = 9 radius = 9
margin = 7 margin = 7
shadow_size = 3 shadow_size = 3
"target_hint": "target_hint":
fill = Color(0.050, 0.070, 0.062, 0.94) fill = Color(0.050, 0.070, 0.062, 0.992)
border = UI_JADE_EDGE border = UI_JADE_EDGE
border_width = 3 border_width = 3
radius = 10 radius = 10
@@ -1016,8 +1018,8 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void:
margin = 10 margin = 10
shadow_size = 10 shadow_size = 10
"compact": "compact":
fill = Color(0.045, 0.048, 0.044, 0.93) fill = Color(0.045, 0.048, 0.044, 0.988)
border = UI_TARNISHED_BRONZE border = Color(UI_TARNISHED_BRONZE.r, UI_TARNISHED_BRONZE.g, UI_TARNISHED_BRONZE.b, 1.0)
border_width = 3 border_width = 3
radius = 10 radius = 10
margin = 8 margin = 8
@@ -2329,6 +2331,32 @@ func _apply_label_style(label: Label, font_color: Color, font_size: int = 0) ->
_apply_text_legibility(label, font_color, font_size) _apply_text_legibility(label, font_color, font_size)
func _apply_rich_text_style(label: RichTextLabel, font_color: Color, font_size: int = 13) -> void:
if label == null:
return
label.add_theme_color_override("default_color", font_color)
label.add_theme_color_override("font_outline_color", _text_outline_color_for(font_color))
label.add_theme_color_override("font_shadow_color", UI_TEXT_SHADOW)
label.add_theme_constant_override("outline_size", 2 if font_size < 16 else 3)
label.add_theme_constant_override("shadow_offset_x", 1)
label.add_theme_constant_override("shadow_offset_y", 1)
label.add_theme_constant_override("line_separation", 4)
var regular := _ui_font(false)
if regular != null:
label.add_theme_font_override("normal_font", regular)
label.add_theme_font_override("italics_font", regular)
label.add_theme_font_override("mono_font", regular)
var bold := _ui_font(true)
if bold != null:
label.add_theme_font_override("bold_font", bold)
label.add_theme_font_override("bold_italics_font", bold)
label.add_theme_font_size_override("normal_font_size", font_size)
label.add_theme_font_size_override("bold_font_size", font_size)
label.add_theme_font_size_override("italics_font_size", font_size)
label.add_theme_font_size_override("bold_italics_font_size", font_size)
label.add_theme_font_size_override("mono_font_size", font_size)
func _apply_opening_caption_text_style(label: Label, font_color: Color, font_size: int = 0) -> void: func _apply_opening_caption_text_style(label: Label, font_color: Color, font_size: int = 0) -> void:
if label == null: if label == null:
return return
@@ -3920,8 +3948,8 @@ func _create_hud() -> void:
log_box = RichTextLabel.new() log_box = RichTextLabel.new()
log_box.custom_minimum_size = Vector2(420, 70) log_box.custom_minimum_size = Vector2(420, 70)
log_box.fit_content = false log_box.fit_content = false
log_box.add_theme_color_override("default_color", UI_PARCHMENT_TEXT) _apply_rich_text_style(log_box, UI_PARCHMENT_TEXT, 13)
log_box.add_theme_stylebox_override("normal", _make_panel_style(Color(0.11, 0.07, 0.045, 0.78), UI_RICE_PAPER_DARK, 1, 6, 6, 0)) log_box.add_theme_stylebox_override("normal", _make_panel_style(Color(0.030, 0.028, 0.024, 0.996), Color(0.22, 0.18, 0.12, 1.0), 1, 6, 8, 0))
side_column.add_child(log_box) side_column.add_child(log_box)
_create_command_hint_panel(root) _create_command_hint_panel(root)
@@ -4802,7 +4830,7 @@ func _create_hud() -> void:
title_load_panel = PanelContainer.new() title_load_panel = PanelContainer.new()
title_load_panel.visible = false title_load_panel.visible = false
title_load_panel.custom_minimum_size = Vector2(390, 188) title_load_panel.custom_minimum_size = Vector2(390, 188)
_apply_panel_style(title_load_panel, "hud_info") _apply_panel_style(title_load_panel, "hud_readable")
title_menu_column.add_child(title_load_panel) title_menu_column.add_child(title_load_panel)
var title_load_column := VBoxContainer.new() var title_load_column := VBoxContainer.new()
@@ -4856,7 +4884,7 @@ func _create_hud() -> void:
title_settings_panel = PanelContainer.new() title_settings_panel = PanelContainer.new()
title_settings_panel.visible = false title_settings_panel.visible = false
title_settings_panel.custom_minimum_size = Vector2(390, 362) title_settings_panel.custom_minimum_size = Vector2(390, 362)
_apply_panel_style(title_settings_panel, "hud_info") _apply_panel_style(title_settings_panel, "hud_readable")
title_menu_column.add_child(title_settings_panel) title_menu_column.add_child(title_settings_panel)
var title_settings_column := VBoxContainer.new() var title_settings_column := VBoxContainer.new()

View File

@@ -3692,6 +3692,7 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
_check_top_hud_chip(failures, scene, "TopStatusChip", scene.status_label, "군령") _check_top_hud_chip(failures, scene, "TopStatusChip", scene.status_label, "군령")
_check_top_hud_chip(failures, scene, "TopObjectiveChip", scene.objective_label, "승리:") _check_top_hud_chip(failures, scene, "TopObjectiveChip", scene.objective_label, "승리:")
_check_top_hud_chip(failures, scene, "TopCampaignChip", scene.campaign_status_label, "군자금") _check_top_hud_chip(failures, scene, "TopCampaignChip", scene.campaign_status_label, "군자금")
_check_panel_uses_flat_readable_backing(failures, _find_descendant_by_name(scene.ui_root_control, "TopHudBar") as PanelContainer, "top HUD compact text backing")
var mission_text := scene._format_mission_panel_text() var mission_text := scene._format_mission_panel_text()
for expected in ["승리:", "패배:", "진행:", "주의:"]: for expected in ["승리:", "패배:", "진행:", "주의:"]:
if not mission_text.contains(expected): if not mission_text.contains(expected):
@@ -3741,6 +3742,10 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void:
_check_panel_style_frame(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.42, 0.35, 0.24, 0.98), 2) _check_panel_style_frame(failures, scene.cell_info_panel, "HUD terrain info panel", Color(0.42, 0.35, 0.24, 0.98), 2)
_check_panel_style_fill(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.060, 0.056, 0.048, 0.982)) _check_panel_style_fill(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.060, 0.056, 0.048, 0.982))
_check_panel_style_frame(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.42, 0.35, 0.24, 0.98), 2) _check_panel_style_frame(failures, scene.forecast_panel, "HUD forecast info panel", Color(0.42, 0.35, 0.24, 0.98), 2)
_check_panel_uses_flat_readable_backing(failures, scene.title_load_panel, "title load readable panel")
_check_panel_uses_flat_readable_backing(failures, scene.title_settings_panel, "title settings readable panel")
_check_panel_uses_flat_readable_backing(failures, _find_descendant_by_name(scene.ui_root_control, "SettingsPanel") as PanelContainer, "battle settings readable panel")
_check_rich_text_readability(failures, scene.log_box, "battle log text")
if scene.cell_info_icon == null: if scene.cell_info_icon == null:
failures.append("HUD terrain info panel should expose an icon chip") failures.append("HUD terrain info panel should expose an icon chip")
if scene.forecast_icon == null: if scene.forecast_icon == null:
@@ -4331,6 +4336,47 @@ func _check_panel_uses_flat_text_surface(failures: Array[String], panel: PanelCo
failures.append("%s should reserve breathing room around readable text" % label) failures.append("%s should reserve breathing room around readable text" % label)
func _check_panel_uses_flat_readable_backing(failures: Array[String], panel: PanelContainer, label: String) -> void:
if panel == null:
failures.append("%s missing" % label)
return
var stylebox := panel.get_theme_stylebox("panel")
if not (stylebox is StyleBoxFlat):
failures.append("%s should use a flat readable backing behind Korean text" % label)
return
if bool(panel.get_meta("generated_panel_texture", false)):
failures.append("%s should not mark a busy generated texture behind text" % label)
var style := stylebox as StyleBoxFlat
if style.bg_color.a < 0.98:
failures.append("%s should keep an opaque backing: %s" % [label, str(style.bg_color)])
if style.bg_color.get_luminance() > 0.18 and style.bg_color.a < 0.995:
failures.append("%s bright text backing should be almost opaque: %s" % [label, str(style.bg_color)])
if style.content_margin_left < 6 or style.content_margin_top < 6:
failures.append("%s should keep at least compact text padding" % label)
func _check_rich_text_readability(failures: Array[String], label: RichTextLabel, context: String) -> void:
if label == null:
failures.append("%s missing" % context)
return
var stylebox := label.get_theme_stylebox("normal")
if not (stylebox is StyleBoxFlat):
failures.append("%s should use a flat text log backing" % context)
else:
var style := stylebox as StyleBoxFlat
if style.bg_color.a < 0.98:
failures.append("%s backing should be opaque enough: %s" % [context, str(style.bg_color)])
var text_color := label.get_theme_color("default_color")
if text_color.a < 0.95 or text_color.get_luminance() < 0.70:
failures.append("%s should use bright readable default text: %s" % [context, str(text_color)])
if label.get_theme_font_size("normal_font_size") < 13:
failures.append("%s should keep readable Korean log size: %d" % [context, label.get_theme_font_size("normal_font_size")])
if label.get_theme_constant("outline_size") < 2:
failures.append("%s should keep an outline for text over dark UI" % context)
if label.get_theme_font("normal_font") == null:
failures.append("%s should use the project serif font" % context)
func _check_panel_uses_generated_ornament_texture(failures: Array[String], panel: PanelContainer, label: String) -> void: func _check_panel_uses_generated_ornament_texture(failures: Array[String], panel: PanelContainer, label: String) -> void:
if panel == null: if panel == null:
failures.append("%s missing" % label) failures.append("%s missing" % label)
@@ -4676,8 +4722,9 @@ func _check_hud_command_grid_layout(failures: Array[String]) -> void:
failures.append("unit command hint should show command title: %s" % ("" if scene.command_hint_title_label == null else scene.command_hint_title_label.text)) failures.append("unit command hint should show command title: %s" % ("" if scene.command_hint_title_label == null else scene.command_hint_title_label.text))
if scene.command_hint_detail_label == null or not scene.command_hint_detail_label.text.contains("부대 미지정"): if scene.command_hint_detail_label == null or not scene.command_hint_detail_label.text.contains("부대 미지정"):
failures.append("disabled unit command hint should show the blocked reason: %s" % ("" if scene.command_hint_detail_label == null else scene.command_hint_detail_label.text)) failures.append("disabled unit command hint should show the blocked reason: %s" % ("" if scene.command_hint_detail_label == null else scene.command_hint_detail_label.text))
if scene.command_hint_icon == null or scene.command_hint_icon.texture == null: if scene.command_hint_icon == null or scene.command_hint_icon.texture == null:
failures.append("unit command hint should show generated command icon artwork") failures.append("unit command hint should show generated command icon artwork")
_check_panel_uses_flat_readable_backing(failures, scene.command_hint_panel, "unit command hint readable panel")
scene._on_command_hint_button_mouse_exited() scene._on_command_hint_button_mouse_exited()
if scene.command_hint_panel != null and scene.command_hint_panel.visible: if scene.command_hint_panel != null and scene.command_hint_panel.visible:
failures.append("unit command hint panel should hide when leaving the icon") failures.append("unit command hint panel should hide when leaving the icon")
@@ -4714,6 +4761,7 @@ func _check_hud_command_grid_layout(failures: Array[String]) -> void:
failures.append("top command hint should keep command detail: %s" % ("" if scene.command_hint_detail_label == null else scene.command_hint_detail_label.text)) failures.append("top command hint should keep command detail: %s" % ("" if scene.command_hint_detail_label == null else scene.command_hint_detail_label.text))
if scene.command_hint_icon == null or scene.command_hint_icon.texture == null or str(scene.end_turn_button.get_meta("command_icon", "")) != "end_turn": if scene.command_hint_icon == null or scene.command_hint_icon.texture == null or str(scene.end_turn_button.get_meta("command_icon", "")) != "end_turn":
failures.append("top command hint should use generated top command icon artwork") failures.append("top command hint should use generated top command icon artwork")
_check_panel_uses_flat_readable_backing(failures, scene.command_hint_panel, "top command hint readable panel")
scene.free() scene.free()
@@ -4743,8 +4791,8 @@ func _check_battle_unit_list_panel(failures: Array[String]) -> void:
if scene.battle_unit_list_panel == null or not scene.battle_unit_list_panel.visible: if scene.battle_unit_list_panel == null or not scene.battle_unit_list_panel.visible:
failures.append("battle unit list panel should open from the top toolbar") failures.append("battle unit list panel should open from the top toolbar")
else: else:
_check_panel_style_fill(failures, scene.battle_unit_list_panel, "battle unit list panel", Color(0.045, 0.048, 0.044, 0.93)) _check_panel_style_fill(failures, scene.battle_unit_list_panel, "battle unit list panel", Color(0.045, 0.048, 0.044, 0.988))
_check_panel_uses_generated_texture(failures, scene.battle_unit_list_panel, "battle unit list generated frame") _check_panel_uses_flat_readable_backing(failures, scene.battle_unit_list_panel, "battle unit list readable panel")
var header_icon := _find_descendant_by_name(scene.battle_unit_list_panel, "BattleUnitListHeaderIcon") var header_icon := _find_descendant_by_name(scene.battle_unit_list_panel, "BattleUnitListHeaderIcon")
if header_icon == null or not header_icon is TextureRect or (header_icon as TextureRect).texture == null: if header_icon == null or not header_icon is TextureRect or (header_icon as TextureRect).texture == null:
failures.append("battle unit list header should carry generated unit-list icon artwork") failures.append("battle unit list header should carry generated unit-list icon artwork")