From 7fb27f29fc327cdbf4230e1473a7f2be51a03c10 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 13:56:27 +0900 Subject: [PATCH] Improve title and opening text readability --- scripts/core/battle_state.gd | 2 +- scripts/scenes/battle_scene.gd | 110 +++++++++++++++++++++---------- tools/smoke_event_ai_priority.gd | 4 +- tools/smoke_title_menu.gd | 38 +++++++++++ tools/smoke_visual_assets.gd | 8 ++- 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/scripts/core/battle_state.gd b/scripts/core/battle_state.gd index 0a6ebf9..77a7004 100644 --- a/scripts/core/battle_state.gd +++ b/scripts/core/battle_state.gd @@ -4894,7 +4894,7 @@ func _spawn_event_deployment(deployment) -> bool: unit["moved"] = current_team == unit.get("team", "") units.append(unit) _emit_log("%s, %s에 당도." % [unit["name"], _format_cell(unit["pos"])]) - _emit_combat_feedback(unit, "증원", "priority") + _emit_combat_feedback(unit, "증원", "reinforcement") _notify_changed() return true diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index ab8cb33..9b38a8c 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -261,6 +261,9 @@ const UI_BRUSH_RULE := Color(0.065, 0.060, 0.052, 0.98) const UI_TEXT_DARK_OUTLINE := Color(0.018, 0.012, 0.007, 0.88) const UI_TEXT_LIGHT_OUTLINE := Color(0.96, 0.88, 0.66, 0.56) const UI_TEXT_SHADOW := Color(0.010, 0.007, 0.004, 0.66) +const UI_TITLE_TEXT := Color(1.0, 0.96, 0.82, 1.0) +const UI_TITLE_MUTED_TEXT := Color(0.92, 0.84, 0.66, 1.0) +const UI_TITLE_BUTTON_TEXT := Color(1.0, 0.97, 0.86, 1.0) const UI_OPENING_CAPTION_TITLE := Color(0.98, 0.93, 0.78, 1.0) const UI_OPENING_CAPTION_BODY := Color(0.97, 0.95, 0.88, 1.0) const UI_OPENING_CAPTION_MUTED := Color(0.78, 0.70, 0.52, 1.0) @@ -964,7 +967,7 @@ func _apply_panel_style(panel: PanelContainer, variant: String) -> void: margin = 5 shadow_size = 2 "cinematic_caption": - fill = Color(0.030, 0.032, 0.030, 0.93) + fill = Color(0.018, 0.018, 0.016, 0.972) border = UI_TARNISHED_BRONZE border_width = 4 radius = 10 @@ -1182,17 +1185,37 @@ func _apply_text_legibility(control: Control, font_color: Color, font_size: int control.add_theme_constant_override("shadow_offset_y", 1) +func _apply_strong_text_legibility(control: Control, font_color: Color, font_size: int = 0) -> void: + if control == null: + return + var resolved_size := font_size + if resolved_size <= 0: + resolved_size = control.get_theme_font_size("font_size") + var outline_size := 4 if resolved_size >= 18 else 3 + control.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.96)) + control.add_theme_constant_override("outline_size", outline_size) + control.add_theme_color_override("font_shadow_color", Color(0.0, 0.0, 0.0, 0.84)) + control.add_theme_constant_override("shadow_offset_x", 2) + control.add_theme_constant_override("shadow_offset_y", 2) + + func _apply_button_style(button: Button, important: bool = false) -> void: if button == null: return + var title_button := bool(button.get_meta("title_menu_button", false)) + var text_color := UI_TITLE_BUTTON_TEXT if title_button else UI_PARCHMENT_TEXT + var disabled_text_color := Color(0.80, 0.75, 0.62, 1.0) if title_button else UI_DISABLED_TEXT if _apply_generated_button_style(button, important): - button.add_theme_color_override("font_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_hover_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_pressed_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_focus_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_disabled_color", UI_DISABLED_TEXT) - _apply_control_font(button, important) - _apply_text_legibility(button, UI_PARCHMENT_TEXT, button.get_theme_font_size("font_size")) + button.add_theme_color_override("font_color", text_color) + button.add_theme_color_override("font_hover_color", text_color) + button.add_theme_color_override("font_pressed_color", text_color) + button.add_theme_color_override("font_focus_color", text_color) + button.add_theme_color_override("font_disabled_color", disabled_text_color) + _apply_control_font(button, important or title_button) + if title_button: + _apply_strong_text_legibility(button, text_color, button.get_theme_font_size("font_size")) + else: + _apply_text_legibility(button, text_color, button.get_theme_font_size("font_size")) return var normal_fill := Color(0.28, 0.055, 0.040, 0.99) if important else Color(0.070, 0.085, 0.078, 0.98) var hover_fill := Color(0.36, 0.080, 0.055, 1.0) if important else Color(0.095, 0.135, 0.118, 1.0) @@ -1202,13 +1225,16 @@ func _apply_button_style(button: Button, important: bool = false) -> void: button.add_theme_stylebox_override("pressed", _make_button_style(pressed_fill, UI_JADE_EDGE, 2)) button.add_theme_stylebox_override("focus", _make_button_style(Color(0.080, 0.120, 0.105, 0.98), UI_JADE_EDGE, 2)) button.add_theme_stylebox_override("disabled", _make_button_style(Color(0.10, 0.065, 0.040, 0.88), UI_RICE_PAPER_DARK, 1)) - button.add_theme_color_override("font_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_hover_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_pressed_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_focus_color", UI_PARCHMENT_TEXT) - button.add_theme_color_override("font_disabled_color", UI_DISABLED_TEXT) - _apply_control_font(button, important) - _apply_text_legibility(button, UI_PARCHMENT_TEXT, button.get_theme_font_size("font_size")) + button.add_theme_color_override("font_color", text_color) + button.add_theme_color_override("font_hover_color", text_color) + button.add_theme_color_override("font_pressed_color", text_color) + button.add_theme_color_override("font_focus_color", text_color) + button.add_theme_color_override("font_disabled_color", disabled_text_color) + _apply_control_font(button, important or title_button) + if title_button: + _apply_strong_text_legibility(button, text_color, button.get_theme_font_size("font_size")) + else: + _apply_text_legibility(button, text_color, button.get_theme_font_size("font_size")) button.set_meta("generated_button_texture", false) button.set_meta("generated_icon_button_texture", false) @@ -1284,7 +1310,7 @@ func _configure_title_menu_button(button: Button, node_name: String, icon_kind: button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER button.alignment = HORIZONTAL_ALIGNMENT_CENTER button.add_theme_constant_override("h_separation", 12) - button.add_theme_font_size_override("font_size", 16 if important else 14) + button.add_theme_font_size_override("font_size", 21 if important else 19) button.set_meta("title_menu_button", true) button.set_meta("command_icon", icon_kind) button.set_meta("command_label", label) @@ -2167,6 +2193,7 @@ func _apply_label_style(label: Label, font_color: Color, font_size: int = 0) -> func _apply_opening_caption_text_style(label: Label, font_color: Color, font_size: int = 0) -> void: if label == null: return + label.z_index = 2 label.add_theme_color_override("font_color", font_color) if font_size > 0: label.add_theme_font_size_override("font_size", font_size) @@ -2174,12 +2201,7 @@ func _apply_opening_caption_text_style(label: Label, font_color: Color, font_siz var resolved_size := font_size if resolved_size <= 0: resolved_size = label.get_theme_font_size("font_size") - var outline_size := 4 if resolved_size >= 18 else 3 - label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.96)) - label.add_theme_constant_override("outline_size", outline_size) - label.add_theme_color_override("font_shadow_color", Color(0.0, 0.0, 0.0, 0.82)) - label.add_theme_constant_override("shadow_offset_x", 2) - label.add_theme_constant_override("shadow_offset_y", 2) + _apply_strong_text_legibility(label, font_color, resolved_size) label.add_theme_constant_override("line_spacing", 4) @@ -3778,14 +3800,14 @@ func _create_hud() -> void: opening_prologue_title_label.custom_minimum_size = Vector2(220, 42) opening_prologue_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_title_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_opening_caption_text_style(opening_prologue_title_label, UI_OPENING_CAPTION_TITLE, 24) + _apply_opening_caption_text_style(opening_prologue_title_label, UI_OPENING_CAPTION_TITLE, 26) opening_title_column.add_child(opening_prologue_title_label) opening_prologue_step_label = Label.new() opening_prologue_step_label.custom_minimum_size = Vector2(220, 24) opening_prologue_step_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER opening_prologue_step_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_opening_caption_text_style(opening_prologue_step_label, UI_OPENING_CAPTION_MUTED, 15) + _apply_opening_caption_text_style(opening_prologue_step_label, UI_OPENING_CAPTION_MUTED, 16) opening_title_column.add_child(opening_prologue_step_label) opening_prologue_body_label = Label.new() @@ -3795,7 +3817,7 @@ func _create_hud() -> void: opening_prologue_body_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART opening_prologue_body_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER opening_prologue_body_label.clip_text = false - _apply_opening_caption_text_style(opening_prologue_body_label, UI_OPENING_CAPTION_BODY, 20) + _apply_opening_caption_text_style(opening_prologue_body_label, UI_OPENING_CAPTION_BODY, 22) opening_caption_row.add_child(opening_prologue_body_label) var opening_button_column := VBoxContainer.new() @@ -4475,7 +4497,8 @@ func _create_hud() -> void: title_caption_label.custom_minimum_size = Vector2(430, 48) title_caption_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_caption_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_label_style(title_caption_label, UI_PARCHMENT_TEXT, 34) + _apply_label_style(title_caption_label, UI_TITLE_TEXT, 36) + _apply_strong_text_legibility(title_caption_label, UI_TITLE_TEXT, 36) title_column.add_child(title_caption_label) title_subtitle_label = Label.new() @@ -4483,7 +4506,8 @@ func _create_hud() -> void: title_subtitle_label.custom_minimum_size = Vector2(430, 24) title_subtitle_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_subtitle_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _apply_label_style(title_subtitle_label, UI_OLD_BRONZE, 17) + _apply_label_style(title_subtitle_label, UI_TITLE_MUTED_TEXT, 18) + _apply_strong_text_legibility(title_subtitle_label, UI_TITLE_MUTED_TEXT, 18) title_column.add_child(title_subtitle_label) var title_body_panel := PanelContainer.new() @@ -4498,10 +4522,12 @@ func _create_hud() -> void: title_status_label = Label.new() title_status_label.custom_minimum_size = Vector2(390, 42) + title_status_label.visible = false title_status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title_status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER title_status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - _apply_label_style(title_status_label, UI_PARCHMENT_TEXT, 14) + _apply_label_style(title_status_label, UI_TITLE_MUTED_TEXT, 15) + _apply_strong_text_legibility(title_status_label, UI_TITLE_MUTED_TEXT, 15) title_menu_column.add_child(title_status_label) title_start_button = Button.new() @@ -8050,6 +8076,8 @@ func _floating_text_color(kind: String) -> Color: return Color(0.48, 0.74, 1.0) if kind == "item": return Color(0.98, 0.76, 0.28) + if kind == "reinforcement": + return Color(1.0, 0.72, 0.32) if kind == "priority": return Color(1.0, 0.54, 0.22) if kind == "support": @@ -11111,6 +11139,7 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String) if delay <= 0.0: _focus_board_on_cell(cell, true) var jitter := float((floating_texts.size() % 3) - 1) * 12.0 + var sfx_key := _combat_feedback_sfx_key(text, kind) floating_texts.append({ "text": text, "kind": kind, @@ -11120,11 +11149,21 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String) "focus_cell": cell, "force_focus": true, "focused": delay <= 0.0, + "sfx": sfx_key, + "sfx_played": delay <= 0.0, "sequence": delay > 0.0 }) + if delay <= 0.0 and not sfx_key.is_empty() and is_inside_tree(): + _play_sfx(sfx_key) queue_redraw() +func _combat_feedback_sfx_key(text: String, kind: String) -> String: + if kind == "reinforcement" or text == "증원": + return "footstep" + return "" + + func _on_unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i) -> void: if unit_id.is_empty() or from_cell == to_cell: return @@ -13922,8 +13961,8 @@ func _update_opening_prologue_page() -> void: _set_fitted_label_text( opening_prologue_title_label, str(page.get("title", "")), - 24, - 18, + 26, + 20, opening_prologue_title_label.custom_minimum_size ) _apply_opening_caption_text_style(opening_prologue_title_label, UI_OPENING_CAPTION_TITLE) @@ -13931,8 +13970,8 @@ func _update_opening_prologue_page() -> void: _set_fitted_label_text( opening_prologue_body_label, str(page.get("body", "")), - 20, - 17, + 22, + 18, opening_prologue_body_label.custom_minimum_size ) _apply_opening_caption_text_style(opening_prologue_body_label, UI_OPENING_CAPTION_BODY) @@ -13977,6 +14016,7 @@ func _update_title_menu_state() -> void: title_load_button.tooltip_text = "불러올 기록을 선택합니다." if has_load else "저장된 전기가 없습니다." if title_status_label != null: title_status_label.text = _format_title_status_text(has_load) + title_status_label.visible = false _update_title_load_panel_state() if title_settings_back_button != null: title_settings_back_button.visible = title_settings_panel != null and title_settings_panel.visible @@ -14053,6 +14093,7 @@ func _on_title_start_pressed() -> void: if not campaign_state.reset_save(campaign_state.get_start_scenario_id()): if title_status_label != null: title_status_label.text = "새 전기를 열지 못했습니다. 저장 권한을 확인해 주세요." + title_status_label.visible = true return if _should_show_opening_prologue(): _show_opening_prologue() @@ -14092,6 +14133,7 @@ func _load_title_slot(manual: bool) -> void: if not loaded: if title_status_label != null: title_status_label.text = "저장된 전기를 열지 못했습니다." + title_status_label.visible = true _set_title_load_panel_visible(false) _update_title_load_panel_state() return @@ -14132,7 +14174,7 @@ func _set_title_settings_visible(visible: bool) -> void: if visible and title_load_panel != null: title_load_panel.visible = false if title_status_label != null: - title_status_label.visible = not visible + title_status_label.visible = false if title_start_button != null: title_start_button.visible = not visible if title_load_button != null: @@ -14149,7 +14191,7 @@ func _set_title_load_panel_visible(visible: bool) -> void: if visible and title_settings_panel != null: title_settings_panel.visible = false if title_status_label != null: - title_status_label.visible = not visible + title_status_label.visible = false if title_start_button != null: title_start_button.visible = not visible if title_load_button != null: diff --git a/tools/smoke_event_ai_priority.gd b/tools/smoke_event_ai_priority.gd index ee31937..088b1cf 100644 --- a/tools/smoke_event_ai_priority.gd +++ b/tools/smoke_event_ai_priority.gd @@ -83,7 +83,7 @@ func _check_reinforcement_priority_events(failures: Array[String]) -> void: if not _feedback_has(feedback, "xiahou_dun", "표적", "priority"): failures.append("Turn 4 reinforcement priority feedback missing") for unit_id in ["yellow_turban_reinforcement_1", "yellow_turban_reinforcement_2", "yellow_turban_reinforcement_2b"]: - if not _feedback_has(feedback, unit_id, "증원", "priority"): + if not _feedback_has(feedback, unit_id, "증원", "reinforcement"): failures.append("Turn 4 reinforcement should show arrival feedback for %s" % unit_id) state._run_events("turn_start", "enemy", 7) @@ -94,7 +94,7 @@ func _check_reinforcement_priority_events(failures: Array[String]) -> void: if not _feedback_has(feedback, "cao_cao", "표적", "priority"): failures.append("Turn 7 reinforcement priority feedback missing") for unit_id in ["yellow_turban_reinforcement_3", "yellow_turban_reinforcement_4", "yellow_turban_reinforcement_4b"]: - if not _feedback_has(feedback, unit_id, "증원", "priority"): + if not _feedback_has(feedback, unit_id, "증원", "reinforcement"): failures.append("Turn 7 reinforcement should show arrival feedback for %s" % unit_id) diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 49f39a6..91cff6c 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -100,6 +100,10 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("title menu panel should be a transparent content layer over generated art") if scene.title_caption_label == null or scene.title_caption_label.text != "조조전": failures.append("title screen should present the campaign title in Korean") + else: + _check_title_text_contrast(failures, scene.title_caption_label, "title caption", 0.88, 3, 34) + if scene.title_subtitle_label != null: + _check_title_text_contrast(failures, scene.title_subtitle_label, "title subtitle", 0.66, 3, 17) var title_story_feature: Node = null if scene.title_screen_root != null: title_story_feature = scene.title_screen_root.find_child("TitleStoryFeaturePanel", true, false) @@ -120,6 +124,7 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: if scene.title_start_button.custom_minimum_size.y < 54.0: failures.append("title start command should be tall enough to read clearly") _check_title_command_fit(failures, scene.title_start_button, "title start") + _check_title_button_text_contrast(failures, scene.title_start_button, "title start") _check_title_menu_button_art(failures, scene.title_start_button, "new", "title start") if scene.title_load_button == null or scene.title_load_button.text != "로드하기": failures.append("title screen should expose `로드하기`") @@ -131,6 +136,7 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: if scene.title_load_button.custom_minimum_size.y < 54.0: failures.append("title load command should be tall enough to read clearly") _check_title_command_fit(failures, scene.title_load_button, "title load") + _check_title_button_text_contrast(failures, scene.title_load_button, "title load") _check_title_menu_button_art(failures, scene.title_load_button, "load", "title load") if scene.title_load_panel == null: failures.append("title screen should create a load-slot selection panel") @@ -144,6 +150,7 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: if scene.title_settings_button.custom_minimum_size.y < 54.0: failures.append("title settings command should be tall enough to read clearly") _check_title_command_fit(failures, scene.title_settings_button, "title settings") + _check_title_button_text_contrast(failures, scene.title_settings_button, "title settings") _check_title_menu_button_art(failures, scene.title_settings_button, "settings", "title settings") if scene.title_settings_back_button != null and scene.title_settings_back_button.visible: failures.append("title settings back command should stay hidden until settings are open") @@ -467,6 +474,37 @@ func _check_title_command_fit(failures: Array[String], button: Button, context: failures.append("%s combined width should fit inside the title menu, got %.1f" % [context, combined.x]) +func _check_title_button_text_contrast(failures: Array[String], button: Button, context: String) -> void: + if button == null: + return + _check_title_text_contrast(failures, button, "%s text" % context, 0.88, 3, 18) + var disabled_color: Color = button.get_theme_color("font_disabled_color") + if disabled_color.get_luminance() < 0.70: + failures.append("%s disabled text should remain readable, luminance %.2f" % [context, disabled_color.get_luminance()]) + + +func _check_title_text_contrast( + failures: Array[String], + control: Control, + context: String, + min_luminance: float, + min_outline: int, + min_font_size: int +) -> void: + if control == null: + failures.append("%s missing" % context) + return + var font_color: Color = control.get_theme_color("font_color") + if font_color.get_luminance() < min_luminance: + failures.append("%s should use bright readable text, luminance %.2f" % [context, font_color.get_luminance()]) + var outline_size := control.get_theme_constant("outline_size") + if outline_size < min_outline: + failures.append("%s should keep a strong outline, got %d" % [context, outline_size]) + var font_size := control.get_theme_font_size("font_size") + if font_size < min_font_size: + failures.append("%s font should be large enough to read, got %d" % [context, font_size]) + + func _check_bgm_stream_quality(failures: Array[String], stream: AudioStream, label: String, minimum_length: float) -> void: if stream == null: failures.append("%s BGM stream should load before playback" % label) diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 56284c6..b2a460e 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3686,6 +3686,12 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("reinforcement event log should become a battlefield notice: %s" % ("" if scene.objective_notice_label == null else scene.objective_notice_label.text)) if scene.objective_notice_icon_strip == null or scene.objective_notice_icon_strip.get_child_count() < 2: failures.append("reinforcement event notice should include threat and warning icons") + if scene._floating_text_color("reinforcement") == scene._floating_text_color("priority"): + failures.append("reinforcement feedback should use a distinct floating text color") + if scene._combat_feedback_sfx_key("증원", "reinforcement") != "footstep": + failures.append("reinforcement feedback should use the footstep arrival SFX") + if not scene._combat_feedback_sfx_key("표적", "priority").is_empty(): + failures.append("priority targeting feedback should not inherit reinforcement arrival SFX") scene._on_log_added("북쪽 숲 보급고를 거두었다.") if scene.objective_notice_label == null or not scene.objective_notice_label.text.contains("보급 확보") or not scene.objective_notice_label.text.contains("보급고"): failures.append("supply event log should become a battlefield notice: %s" % ("" if scene.objective_notice_label == null else scene.objective_notice_label.text)) @@ -3933,7 +3939,7 @@ func _check_opening_prologue_presentation(failures: Array[String]) -> void: var body_text: String = scene.opening_prologue_body_label.text if scene.opening_prologue_body_label != null else "" var body_area: Vector2 = scene.opening_prologue_body_label.custom_minimum_size if scene.opening_prologue_body_label != null else Vector2.ZERO var body_size: int = scene.opening_prologue_body_label.get_theme_font_size("font_size") if scene.opening_prologue_body_label != null else 0 - if body_size < 17 or body_size > 20: + if body_size < 18 or body_size > 22: failures.append("opening story body font should stay readable inside the caption range, got %d" % body_size) elif scene._estimated_wrapped_text_height(body_text, body_area.x, body_size) > body_area.y: failures.append("opening story body should fit without clipping on page %d: size=%d area=%s" % [index + 1, body_size, str(body_area)])