From 4b22c13ca9dd94efa7075c44f3fad42555abc27e Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 22:27:25 +0900 Subject: [PATCH] Add icon chips to top battle HUD --- scripts/scenes/battle_scene.gd | 120 ++++++++++++++++++++++++++++----- tools/smoke_visual_assets.gd | 30 +++++++++ 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 0810878..839b4f0 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -107,6 +107,7 @@ const HUD_UNIT_SUMMARY_SIZE := Vector2(320, 42) const HUD_UNIT_BADGE_ROW_SIZE := Vector2(320, 22) const HUD_UNIT_BADGE_SIZE := Vector2(26, 22) const HUD_UNIT_BAR_SIZE := Vector2(252, 12) +const TOP_HUD_CHIP_ICON_SIZE := Vector2(28, 28) const HUD_COMMAND_GRID_SIZE := Vector2(420, 46) const HUD_COMMAND_BUTTON_SIZE := Vector2(72, 42) const HUD_COMMAND_GRID_COLUMNS := 4 @@ -310,6 +311,9 @@ var battle_result_summary := {} var post_battle_dialogue_started := false var campaign_complete_screen := false +var top_status_chip: PanelContainer +var top_objective_chip: PanelContainer +var top_campaign_chip: PanelContainer var status_label: Label var objective_label: Label var campaign_status_label: Label @@ -1104,6 +1108,50 @@ func _format_local_command_tooltip(label: String, hint: String) -> String: return "%s\n%s" % [normalized_label, normalized_hint] +func _make_top_hud_chip(node_name: String, icon_kind: String, chip_size: Vector2, variant: String) -> Dictionary: + var chip := PanelContainer.new() + chip.name = node_name + chip.custom_minimum_size = chip_size + chip.size_flags_vertical = Control.SIZE_SHRINK_CENTER + _apply_panel_style(chip, variant) + + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 6) + chip.add_child(row) + row.add_child(_make_top_hud_chip_icon(icon_kind)) + + return { + "chip": chip, + "row": row + } + + +func _make_top_hud_chip_icon(icon_kind: String) -> TextureRect: + var icon := TextureRect.new() + icon.name = "TopHudChipIcon" + icon.custom_minimum_size = TOP_HUD_CHIP_ICON_SIZE + icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.texture = _make_toolbar_icon(icon_kind) + return icon + + +func _set_top_hud_chip_tooltip(chip: PanelContainer, tooltip: String) -> void: + if chip == null: + return + _set_control_tree_tooltip(chip, tooltip) + + +func _set_control_tree_tooltip(node: Node, tooltip: String) -> void: + if node == null: + return + if node is Control: + (node as Control).tooltip_text = tooltip + for child in node.get_children(): + _set_control_tree_tooltip(child, tooltip) + + func _make_toolbar_icon(kind: String) -> Texture2D: var image := Image.create(32, 32, false, Image.FORMAT_RGBA8) image.fill(Color(0, 0, 0, 0)) @@ -1111,6 +1159,30 @@ func _make_toolbar_icon(kind: String) -> Texture2D: var shadow := Color(0.07, 0.028, 0.012, 0.82) var red := Color(UI_SEAL_RED.r, UI_SEAL_RED.g, UI_SEAL_RED.b, 1.0) match kind: + "status": + _icon_circle_outline(image, Vector2i(16, 16), 10, shadow, 4) + _icon_circle_outline(image, Vector2i(16, 16), 10, ink, 2) + _icon_line(image, Vector2i(16, 8), Vector2i(16, 16), red, 3) + _icon_line(image, Vector2i(16, 16), Vector2i(22, 20), red, 3) + _icon_circle(image, Vector2i(16, 16), 2, ink) + "objective": + _icon_line(image, Vector2i(16, 6), Vector2i(26, 16), shadow, 4) + _icon_line(image, Vector2i(26, 16), Vector2i(16, 26), shadow, 4) + _icon_line(image, Vector2i(16, 26), Vector2i(6, 16), shadow, 4) + _icon_line(image, Vector2i(6, 16), Vector2i(16, 6), shadow, 4) + _icon_line(image, Vector2i(16, 6), Vector2i(26, 16), ink, 2) + _icon_line(image, Vector2i(26, 16), Vector2i(16, 26), ink, 2) + _icon_line(image, Vector2i(16, 26), Vector2i(6, 16), ink, 2) + _icon_line(image, Vector2i(6, 16), Vector2i(16, 6), ink, 2) + _icon_circle(image, Vector2i(16, 16), 5, red) + _icon_circle(image, Vector2i(16, 16), 2, ink) + "campaign": + _icon_rect(image, Rect2i(8, 7, 17, 20), shadow) + _icon_rect(image, Rect2i(7, 6, 17, 20), ink) + _icon_rect(image, Rect2i(10, 10, 11, 2), Color(0.16, 0.07, 0.03, 1.0)) + _icon_rect(image, Rect2i(10, 15, 10, 2), Color(0.16, 0.07, 0.03, 1.0)) + _icon_rect(image, Rect2i(10, 20, 8, 2), Color(0.16, 0.07, 0.03, 1.0)) + _icon_circle(image, Vector2i(24, 9), 3, red) "move": _icon_line(image, Vector2i(8, 22), Vector2i(16, 9), shadow, 5) _icon_line(image, Vector2i(16, 9), Vector2i(24, 22), shadow, 5) @@ -1705,37 +1777,47 @@ func _create_hud() -> void: top_row.add_theme_constant_override("separation", 8) top_bar.add_child(top_row) + var status_chip_data := _make_top_hud_chip("TopStatusChip", "status", Vector2(138, 54), "hud_info") + top_status_chip = status_chip_data["chip"] as PanelContainer + top_row.add_child(top_status_chip) + status_label = Label.new() - status_label.custom_minimum_size = Vector2(118, 48) + status_label.custom_minimum_size = Vector2(74, 36) status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(status_label, UI_PARCHMENT_TEXT) - top_row.add_child(status_label) + (status_chip_data["row"] as HBoxContainer).add_child(status_label) - var objective_top_panel := PanelContainer.new() - objective_top_panel.custom_minimum_size = Vector2(396, 54) - _apply_panel_style(objective_top_panel, "edict_compact") - top_row.add_child(objective_top_panel) + top_objective_chip = PanelContainer.new() + top_objective_chip.name = "TopObjectiveChip" + top_objective_chip.custom_minimum_size = Vector2(396, 54) + _apply_panel_style(top_objective_chip, "edict_compact") + top_row.add_child(top_objective_chip) var objective_top_row := HBoxContainer.new() - objective_top_row.add_theme_constant_override("separation", 7) - objective_top_panel.add_child(objective_top_row) - objective_top_row.add_child(_make_bamboo_gutter(14, 36)) + objective_top_row.add_theme_constant_override("separation", 6) + top_objective_chip.add_child(objective_top_row) + objective_top_row.add_child(_make_bamboo_gutter(10, 36)) + objective_top_row.add_child(_make_top_hud_chip_icon("objective")) objective_label = Label.new() objective_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - objective_label.custom_minimum_size = Vector2(318, 36) + objective_label.custom_minimum_size = Vector2(274, 36) objective_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL _apply_label_style(objective_label, UI_AGED_INK, OBJECTIVE_HUD_FONT_SIZE) objective_top_row.add_child(objective_label) - objective_top_row.add_child(_make_bamboo_gutter(14, 36)) + objective_top_row.add_child(_make_bamboo_gutter(10, 36)) + + var campaign_chip_data := _make_top_hud_chip("TopCampaignChip", "campaign", Vector2(156, 54), "hud_info") + top_campaign_chip = campaign_chip_data["chip"] as PanelContainer + top_row.add_child(top_campaign_chip) campaign_status_label = Label.new() - campaign_status_label.custom_minimum_size = Vector2(144, 48) + campaign_status_label.custom_minimum_size = Vector2(88, 36) campaign_status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER campaign_status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _apply_label_style(campaign_status_label, UI_PARCHMENT_TEXT) - top_row.add_child(campaign_status_label) + (campaign_chip_data["row"] as HBoxContainer).add_child(campaign_status_label) var top_tool_row := HBoxContainer.new() top_tool_row.name = "TopToolBar" @@ -6327,18 +6409,24 @@ func _refresh_ranges() -> void: func _update_hud() -> void: + var status_detail := _format_status_hud_detail_text() status_label.text = _format_status_hud_text() - status_label.tooltip_text = _format_status_hud_detail_text() + status_label.tooltip_text = status_detail + _set_top_hud_chip_tooltip(top_status_chip, status_detail) + var objective_detail := _format_objective_hud_detail_text() objective_label.text = _format_objective_hud_text() - objective_label.tooltip_text = _format_objective_hud_detail_text() + objective_label.tooltip_text = objective_detail + _set_top_hud_chip_tooltip(top_objective_chip, objective_detail) _fit_label_font_size_to_text( objective_label, objective_label.text, OBJECTIVE_HUD_FONT_SIZE, OBJECTIVE_HUD_MIN_FONT_SIZE ) + var campaign_detail := campaign_state.get_progress_text() campaign_status_label.text = _format_campaign_hud_summary_text() - campaign_status_label.tooltip_text = campaign_state.get_progress_text() + campaign_status_label.tooltip_text = campaign_detail + _set_top_hud_chip_tooltip(top_campaign_chip, campaign_detail) _update_mission_panel() _update_cell_info() _update_forecast() diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index d64c696..77bb04e 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3176,6 +3176,9 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("top objective label should avoid repeated visible heading: %s" % ("" if scene.objective_label == null else scene.objective_label.text)) elif not scene.objective_label.tooltip_text.contains("승리:") or not scene.objective_label.tooltip_text.contains("패배:"): failures.append("top objective tooltip should retain objective detail: %s" % ("" if scene.objective_label == null else scene.objective_label.tooltip_text)) + _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, "군자금") var mission_text := scene._format_mission_panel_text() for expected in ["승리:", "패배:", "진행:", "주의:"]: if not mission_text.contains(expected): @@ -3502,6 +3505,33 @@ func _find_descendant_by_name(root: Node, node_name: String) -> Node: return null +func _is_descendant_of(node: Node, ancestor: Node) -> bool: + var current := node + while current != null: + if current == ancestor: + return true + current = current.get_parent() + return false + + +func _check_top_hud_chip(failures: Array[String], scene, chip_name: String, expected_label: Label, expected_tooltip_text: String) -> void: + var chip := _find_descendant_by_name(scene.ui_root_control, chip_name) + if chip == null: + failures.append("top HUD chip missing: %s" % chip_name) + return + if not chip is PanelContainer: + failures.append("top HUD chip should be a framed panel: %s" % chip_name) + if expected_label == null or not _is_descendant_of(expected_label, chip): + failures.append("top HUD chip should contain the existing label var: %s" % chip_name) + if not _has_descendant_texture(chip): + failures.append("top HUD chip should contain an icon texture: %s" % chip_name) + if (chip as Control).custom_minimum_size.x <= 0.0 or (chip as Control).custom_minimum_size.y <= 0.0: + failures.append("top HUD chip should reserve stable space: %s" % chip_name) + var tooltip_text := _collect_child_tooltips(chip) + if not tooltip_text.contains(expected_tooltip_text): + failures.append("top HUD chip tooltip should preserve detail `%s`: %s" % [expected_tooltip_text, tooltip_text]) + + func _collect_child_tooltips(root: Node) -> String: if root == null: return ""