From 81672d5a27da5099b0f7ae7829aaef128954c46a Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 17:16:43 +0900 Subject: [PATCH] Improve readable text backings --- README.md | 2 +- scripts/scenes/battle_scene.gd | 46 ++++++++++++++++++++++------ tools/smoke_visual_assets.gd | 56 +++++++++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8699855..4522433 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr - Campaign completion screen. - New campaign save reset. - 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. - 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. diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index d155900..563db00 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -852,9 +852,11 @@ func _panel_texture_spec(variant: String) -> Dictionary: func _panel_variant_prefers_flat_text_surface(variant: String) -> bool: return variant in [ + "compact", "dialogue", "speech_scroll", "notice_edict", + "target_hint", "cinematic_caption", "story_caption" ] @@ -983,14 +985,14 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void: margin = 18 shadow_size = 6 "hud_info": - fill = Color(0.045, 0.050, 0.048, 0.90) - border = Color(0.20, 0.24, 0.21, 0.96) + fill = Color(0.045, 0.050, 0.048, 0.988) + border = Color(0.20, 0.24, 0.21, 1.0) border_width = 2 radius = 9 margin = 7 shadow_size = 3 "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_width = 3 radius = 10 @@ -1016,8 +1018,8 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void: margin = 10 shadow_size = 10 "compact": - fill = Color(0.045, 0.048, 0.044, 0.93) - border = UI_TARNISHED_BRONZE + fill = Color(0.045, 0.048, 0.044, 0.988) + border = Color(UI_TARNISHED_BRONZE.r, UI_TARNISHED_BRONZE.g, UI_TARNISHED_BRONZE.b, 1.0) border_width = 3 radius = 10 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) +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: if label == null: return @@ -3920,8 +3948,8 @@ func _create_hud() -> void: log_box = RichTextLabel.new() log_box.custom_minimum_size = Vector2(420, 70) log_box.fit_content = false - log_box.add_theme_color_override("default_color", UI_PARCHMENT_TEXT) - 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)) + _apply_rich_text_style(log_box, UI_PARCHMENT_TEXT, 13) + 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) _create_command_hint_panel(root) @@ -4802,7 +4830,7 @@ func _create_hud() -> void: title_load_panel = PanelContainer.new() title_load_panel.visible = false 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) var title_load_column := VBoxContainer.new() @@ -4856,7 +4884,7 @@ func _create_hud() -> void: title_settings_panel = PanelContainer.new() title_settings_panel.visible = false 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) var title_settings_column := VBoxContainer.new() diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 9b566f8..8269aa9 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -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, "TopObjectiveChip", scene.objective_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() for expected in ["승리:", "패배:", "진행:", "주의:"]: 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_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_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: failures.append("HUD terrain info panel should expose an icon chip") 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) +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: if panel == null: 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)) 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)) - if scene.command_hint_icon == null or scene.command_hint_icon.texture == null: - failures.append("unit command hint should show generated command icon artwork") + if scene.command_hint_icon == null or scene.command_hint_icon.texture == null: + 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() if scene.command_hint_panel != null and scene.command_hint_panel.visible: 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)) 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") + _check_panel_uses_flat_readable_backing(failures, scene.command_hint_panel, "top command hint readable panel") 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: failures.append("battle unit list panel should open from the top toolbar") 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_uses_generated_texture(failures, scene.battle_unit_list_panel, "battle unit list generated frame") + _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_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") 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")