From 47b9840e4b19acac8f80933d64ca73b4e1aa3fc3 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sat, 20 Jun 2026 12:10:25 +0900 Subject: [PATCH] Polish title menu presentation --- scripts/scenes/battle_scene.gd | 68 ++++++++++++++++++++++--------- tools/smoke_objective_progress.gd | 38 +++++++++-------- tools/smoke_title_menu.gd | 6 ++- tools/smoke_visual_assets.gd | 6 +++ 4 files changed, 79 insertions(+), 39 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index a14c532..d56bf4f 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -1030,17 +1030,17 @@ func _make_button_style(fill: Color, border: Color, border_width: int) -> StyleB return style -func _make_button_texture_style(state_key: String, content_margin: int = 10) -> StyleBoxTexture: +func _make_button_texture_style(state_key: String, content_margin: int = 10, texture_margin := Vector2i(92, 42)) -> StyleBoxTexture: var texture := _load_art_texture(BUTTON_TEXTURE_PATH_TEMPLATE % state_key) if texture == null: return null var style := StyleBoxTexture.new() style.texture = texture style.draw_center = true - style.texture_margin_left = 92 - style.texture_margin_right = 92 - style.texture_margin_top = 42 - style.texture_margin_bottom = 42 + style.texture_margin_left = texture_margin.x + style.texture_margin_right = texture_margin.x + style.texture_margin_top = texture_margin.y + style.texture_margin_bottom = texture_margin.y var resolved_margin: int = maxi(content_margin, 8) style.content_margin_left = resolved_margin style.content_margin_right = resolved_margin @@ -1070,11 +1070,14 @@ func _make_icon_button_texture_style(state_key: String, content_margin: int = 6) func _apply_generated_button_style(button: Button, important: bool = false) -> bool: var icon_only := bool(button.get_meta("icon_only", false)) - var normal_style := _make_icon_button_texture_style("pressed" if important else "normal") if icon_only else _make_button_texture_style("pressed" if important else "normal") - var hover_style := _make_icon_button_texture_style("hover") if icon_only else _make_button_texture_style("hover") - var pressed_style := _make_icon_button_texture_style("pressed") if icon_only else _make_button_texture_style("pressed") - var focus_style := _make_icon_button_texture_style("hover") if icon_only else _make_button_texture_style("hover") - var disabled_style := _make_icon_button_texture_style("disabled") if icon_only else _make_button_texture_style("disabled") + var title_button := bool(button.get_meta("title_menu_button", false)) + var button_margin := 8 if title_button else 10 + var button_texture_margin := Vector2i(46, 18) if title_button else Vector2i(92, 42) + var normal_style := _make_icon_button_texture_style("pressed" if important else "normal") if icon_only else _make_button_texture_style("pressed" if important else "normal", button_margin, button_texture_margin) + var hover_style := _make_icon_button_texture_style("hover") if icon_only else _make_button_texture_style("hover", button_margin, button_texture_margin) + var pressed_style := _make_icon_button_texture_style("pressed") if icon_only else _make_button_texture_style("pressed", button_margin, button_texture_margin) + var focus_style := _make_icon_button_texture_style("hover") if icon_only else _make_button_texture_style("hover", button_margin, button_texture_margin) + var disabled_style := _make_icon_button_texture_style("disabled") if icon_only else _make_button_texture_style("disabled", button_margin, button_texture_margin) if normal_style == null or hover_style == null or pressed_style == null or focus_style == null or disabled_style == null: return false button.add_theme_stylebox_override("normal", normal_style) @@ -2052,6 +2055,31 @@ func _make_title_menu_icon(kind: String) -> Texture2D: return _make_resized_icon_texture(_make_toolbar_icon(kind), TITLE_MENU_ICON_MAX_WIDTH) +func _make_title_portrait_feather_material() -> ShaderMaterial: + var shader := Shader.new() + shader.code = """ +shader_type canvas_item; + +uniform float left_feather = 0.18; +uniform float right_feather = 0.12; +uniform float top_feather = 0.10; +uniform float bottom_feather = 0.16; + +void fragment() { + vec4 tex = texture(TEXTURE, UV); + float alpha = smoothstep(0.0, left_feather, UV.x); + alpha *= smoothstep(0.0, right_feather, 1.0 - UV.x); + alpha *= smoothstep(0.0, top_feather, UV.y); + alpha *= smoothstep(0.0, bottom_feather, 1.0 - UV.y); + COLOR = vec4(tex.rgb, tex.a * alpha); +} +""" + var material := ShaderMaterial.new() + material.shader = shader + material.set_meta("title_portrait_feather_material", true) + return material + + func _make_resized_icon_texture(texture: Texture2D, max_width: int) -> Texture2D: if texture == null or max_width <= 0: return texture @@ -4355,28 +4383,29 @@ func _create_hud() -> void: var title_portrait_shadow := ColorRect.new() title_portrait_shadow.name = "TitlePortraitLacquerField" - title_portrait_shadow.position = Vector2(686, 72) - title_portrait_shadow.size = Vector2(420, 556) - title_portrait_shadow.color = Color(0.10, 0.060, 0.035, 0.10) + title_portrait_shadow.position = Vector2(612, 34) + title_portrait_shadow.size = Vector2(610, 638) + title_portrait_shadow.color = Color(0.13, 0.085, 0.052, 0.055) title_portrait_shadow.mouse_filter = Control.MOUSE_FILTER_IGNORE title_screen_root.add_child(title_portrait_shadow) title_portrait_texture_rect = TextureRect.new() title_portrait_texture_rect.name = "TitleCaoCaoPortrait" - title_portrait_texture_rect.position = Vector2(684, 50) - title_portrait_texture_rect.size = Vector2(436, 606) + title_portrait_texture_rect.position = Vector2(646, 42) + title_portrait_texture_rect.size = Vector2(492, 626) title_portrait_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE title_portrait_texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE title_portrait_texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED title_portrait_texture_rect.texture = _load_art_texture(TITLE_PORTRAIT_PATH) title_portrait_texture_rect.modulate = Color(0.96, 0.97, 0.95, 0.96) + title_portrait_texture_rect.material = _make_title_portrait_feather_material() title_screen_root.add_child(title_portrait_texture_rect) var title_portrait_caption := Label.new() title_portrait_caption.name = "TitlePortraitCaption" title_portrait_caption.text = "조조" - title_portrait_caption.position = Vector2(708, 604) - title_portrait_caption.size = Vector2(386, 34) + title_portrait_caption.position = Vector2(690, 618) + title_portrait_caption.size = Vector2(406, 34) title_portrait_caption.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT title_portrait_caption.vertical_alignment = VERTICAL_ALIGNMENT_CENTER title_portrait_caption.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -11936,11 +11965,10 @@ func _format_briefing_recovery_terrain_text() -> String: var parts := [] for summary in recovery_summaries: var name := _terrain_display_name(str(summary.get("name", "지형"))) - var count := int(summary.get("count", 0)) var heal := int(summary.get("heal", 0)) - if count <= 0 or heal <= 0: + if heal <= 0: continue - parts.append("%s %d칸 +%d" % [name, count, heal]) + parts.append("%s 회복 +%d" % [name, heal]) if parts.is_empty(): return "" return "회복지(차례 시작) %s" % _join_strings(parts, ", ") diff --git a/tools/smoke_objective_progress.gd b/tools/smoke_objective_progress.gd index dc8be3a..4823c1e 100644 --- a/tools/smoke_objective_progress.gd +++ b/tools/smoke_objective_progress.gd @@ -449,6 +449,8 @@ func _check_log_contains(failures: Array[String], logs: Array, expected: String) func _check_briefing_overview_hides_raw_map_size(failures: Array[String], context: String, overview: String) -> void: if overview.contains("칸 x") or overview.contains("칸 ×") or overview.contains("칸x"): failures.append("%s should describe battlefield scale without raw grid dimensions: %s" % [context, overview]) + if overview.contains("칸 +"): + failures.append("%s should keep recovery cell counts out of visible briefing text: %s" % [context, overview]) if not overview.contains("전장"): failures.append("%s should keep a readable battlefield scale label: %s" % [context, overview]) @@ -481,8 +483,8 @@ func _check_briefing_battlefield_overview(failures: Array[String]) -> void: _check_briefing_overview_hides_raw_map_size(failures, "001 briefing battlefield overview", overview) _check_contains(failures, "001 briefing enemy count", overview, "적세 16명") _check_contains(failures, "001 briefing enemy classes", overview, "보병") - _check_contains(failures, "001 briefing village recovery", overview, "마을 8칸 +6") - _check_contains(failures, "001 briefing castle recovery", overview, "성채 9칸 +8") + _check_contains(failures, "001 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "001 briefing castle recovery", overview, "성채 회복 +8") scene.free() @@ -567,8 +569,8 @@ func _check_sishui_gate_extended_pressure(failures: Array[String]) -> void: var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "002 briefing battlefield overview", overview) _check_contains(failures, "002 briefing enemy count", overview, "적세 8명") - _check_contains(failures, "002 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "002 briefing castle recovery", overview, "성채 6칸 +8") + _check_contains(failures, "002 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "002 briefing castle recovery", overview, "성채 회복 +8") scene.free() @@ -630,7 +632,7 @@ func _check_xingyang_ambush_extended_pressure(failures: Array[String]) -> void: var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "003 briefing battlefield overview", overview) _check_contains(failures, "003 briefing enemy count", overview, "적세 6명") - _check_contains(failures, "003 briefing village recovery", overview, "마을 4칸 +6") + _check_contains(failures, "003 briefing village recovery", overview, "마을 회복 +6") scene.free() @@ -683,7 +685,7 @@ func _check_qingzhou_campaign_extended_pressure(failures: Array[String]) -> void var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "004 briefing battlefield overview", overview) _check_contains(failures, "004 briefing enemy count", overview, "적세 8명") - _check_contains(failures, "004 briefing village recovery", overview, "마을 4칸 +6") + _check_contains(failures, "004 briefing village recovery", overview, "마을 회복 +6") scene.free() @@ -741,8 +743,8 @@ func _check_puyang_raid_extended_pressure(failures: Array[String]) -> void: var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "005 briefing battlefield overview", overview) _check_contains(failures, "005 briefing enemy count", overview, "적세 8명") - _check_contains(failures, "005 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "005 briefing camp recovery", overview, "성채 4칸 +8") + _check_contains(failures, "005 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "005 briefing camp recovery", overview, "성채 회복 +8") scene.free() @@ -822,8 +824,8 @@ func _check_dingtao_counterattack_extended_pressure(failures: Array[String]) -> var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "006 briefing battlefield overview", overview) _check_contains(failures, "006 briefing enemy count", overview, "적세 8명") - _check_contains(failures, "006 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "006 briefing center recovery", overview, "성채 4칸 +8") + _check_contains(failures, "006 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "006 briefing center recovery", overview, "성채 회복 +8") scene.free() @@ -899,8 +901,8 @@ func _check_xian_emperor_escort_extended_pressure(failures: Array[String]) -> vo var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "007 briefing battlefield overview", overview) _check_contains(failures, "007 briefing enemy count", overview, "적세 8명") - _check_contains(failures, "007 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "007 briefing road recovery", overview, "성채 4칸 +8") + _check_contains(failures, "007 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "007 briefing road recovery", overview, "성채 회복 +8") scene.free() @@ -1025,8 +1027,8 @@ func _check_wan_castle_escape_extended_pressure(failures: Array[String]) -> void var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "008 briefing battlefield overview", overview) _check_contains(failures, "008 briefing enemy count", overview, "적세 9명") - _check_contains(failures, "008 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "008 briefing gate recovery", overview, "성채 4칸 +8") + _check_contains(failures, "008 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "008 briefing gate recovery", overview, "성채 회복 +8") scene.free() @@ -1132,8 +1134,8 @@ func _check_xiapi_siege_extended_pressure(failures: Array[String]) -> void: var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "009 briefing battlefield overview", overview) _check_contains(failures, "009 briefing enemy count", overview, "적세 10명") - _check_contains(failures, "009 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "009 briefing gate recovery", overview, "성채 4칸 +8") + _check_contains(failures, "009 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "009 briefing gate recovery", overview, "성채 회복 +8") scene.free() @@ -1237,8 +1239,8 @@ func _check_white_horse_relief_extended_pressure(failures: Array[String]) -> voi var overview := scene._format_briefing_battlefield_overview_text() _check_briefing_overview_hides_raw_map_size(failures, "010 briefing battlefield overview", overview) _check_contains(failures, "010 briefing enemy count", overview, "적세 9명") - _check_contains(failures, "010 briefing village recovery", overview, "마을 4칸 +6") - _check_contains(failures, "010 briefing ferry recovery", overview, "성채 4칸 +8") + _check_contains(failures, "010 briefing village recovery", overview, "마을 회복 +6") + _check_contains(failures, "010 briefing ferry recovery", overview, "성채 회복 +8") scene.free() diff --git a/tools/smoke_title_menu.gd b/tools/smoke_title_menu.gd index 5c040d2..f61ef2d 100644 --- a/tools/smoke_title_menu.gd +++ b/tools/smoke_title_menu.gd @@ -76,10 +76,12 @@ func _check_title_menu_start_load_and_settings(failures: Array[String]) -> void: failures.append("title screen should use a dedicated cinematic background, not the first battle map backdrop") if scene.title_portrait_texture_rect == null or scene.title_portrait_texture_rect.texture == null: failures.append("title screen should show Cao Cao portrait art") + elif scene.title_portrait_texture_rect.material == null or not bool(scene.title_portrait_texture_rect.material.get_meta("title_portrait_feather_material", false)): + failures.append("title Cao Cao portrait should feather into the background instead of reading as a hard rectangle") var portrait_shadow := scene.title_screen_root.find_child("TitlePortraitLacquerField", true, false) as ColorRect if scene.title_screen_root != null else null if portrait_shadow == null: failures.append("title screen should keep a subtle portrait grounding field") - elif portrait_shadow.color.a > 0.18: + elif portrait_shadow.color.a > 0.08: failures.append("title portrait grounding field should not read as a black rectangle: %.2f alpha" % portrait_shadow.color.a) if scene.title_panel == null or scene.title_panel.position.x > 100.0: failures.append("title menu panel should sit on the left side of the cinematic title screen") @@ -426,6 +428,8 @@ func _check_title_menu_button_art(failures: Array[String], button: Button, expec failures.append("%s generated button surface is missing its texture" % context) elif style.texture.get_width() < 512 or style.texture.get_height() < 192: failures.append("%s generated button texture should keep high-resolution source pixels: %dx%d" % [context, style.texture.get_width(), style.texture.get_height()]) + if style.texture_margin_top > 24 or style.texture_margin_bottom > 24: + failures.append("%s generated button slice should stay shallow enough for the 720p title menu: %d/%d" % [context, style.texture_margin_top, style.texture_margin_bottom]) if str(button.tooltip_text).strip_edges().is_empty(): failures.append("%s should describe its command through a tooltip" % context) diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 9688298..5302d01 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3098,6 +3098,8 @@ func _check_scene_texture_loading(failures: Array[String]) -> void: failures.append("camp overview should include a readable battlefield scale label: %s" % camp_overview) if camp_overview.contains("칸 x") or camp_overview.contains("칸 ×") or camp_overview.contains("칸x"): failures.append("camp overview should not expose raw map dimensions: %s" % camp_overview) + if camp_overview.contains("칸 +"): + failures.append("camp overview should keep recovery cell counts out of visible text: %s" % camp_overview) var briefing_marker_entries := scene._briefing_tactical_marker_entries() if briefing_marker_entries.size() < 3: failures.append("briefing should expose image-first tactical marker entries") @@ -3451,6 +3453,10 @@ func _check_hud_focus_text(failures: Array[String]) -> void: var recovery_overview := scene._format_briefing_recovery_terrain_text() if not recovery_overview.contains("차례 시작"): failures.append("briefing recovery overview should mention recovery timing: %s" % recovery_overview) + if not recovery_overview.contains("마을 회복 +6") or not recovery_overview.contains("성채 회복 +8"): + failures.append("briefing recovery overview should keep recovery amount but hide cell counts: %s" % recovery_overview) + if recovery_overview.contains("칸 +"): + failures.append("briefing recovery overview should not expose recovery cell counts: %s" % recovery_overview) var archer: Dictionary = scene.state.get_unit("yellow_turban_3") var archer_text := scene._format_unit_focus_text(archer, "Hover") if not archer_text.contains("원거리 견제"):