Show selected unit resources as bars

This commit is contained in:
2026-06-19 22:04:10 +09:00
parent d02627e5d4
commit d7bf2e3de6
3 changed files with 112 additions and 17 deletions

View File

@@ -102,6 +102,9 @@ const SHOP_MERCHANT_BADGE_SIZE := Vector2(50, 50)
const SHOP_ITEM_ICON_SIZE := Vector2(48, 48) const SHOP_ITEM_ICON_SIZE := Vector2(48, 48)
const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104) const HUD_UNIT_PORTRAIT_SIZE := Vector2(92, 104)
const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100) const HUD_UNIT_PORTRAIT_STACK_SIZE := Vector2(88, 100)
const HUD_UNIT_INFO_SIZE := Vector2(320, 126)
const HUD_UNIT_SUMMARY_SIZE := Vector2(320, 58)
const HUD_UNIT_BAR_SIZE := Vector2(252, 12)
const HUD_COMMAND_GRID_SIZE := Vector2(420, 46) const HUD_COMMAND_GRID_SIZE := Vector2(420, 46)
const HUD_COMMAND_BUTTON_SIZE := Vector2(72, 42) const HUD_COMMAND_BUTTON_SIZE := Vector2(72, 42)
const HUD_COMMAND_GRID_COLUMNS := 4 const HUD_COMMAND_GRID_COLUMNS := 4
@@ -319,6 +322,9 @@ var screen_backdrop: ColorRect
var hud_unit_portrait_panel: PanelContainer var hud_unit_portrait_panel: PanelContainer
var hud_unit_portrait_texture: TextureRect var hud_unit_portrait_texture: TextureRect
var hud_unit_portrait_label: Label var hud_unit_portrait_label: Label
var hud_unit_info_column: VBoxContainer
var hud_unit_hp_bar: ProgressBar
var hud_unit_mp_bar: ProgressBar
var selected_label: Label var selected_label: Label
var cell_info_panel: PanelContainer var cell_info_panel: PanelContainer
var cell_info_label: Label var cell_info_label: Label
@@ -851,6 +857,36 @@ func _configure_local_command_button(button: Button, icon_kind: String, label: S
button.mouse_exited.connect(_on_local_command_button_mouse_exited) button.mouse_exited.connect(_on_local_command_button_mouse_exited)
func _make_hud_unit_resource_row(label_text: String, fill_color: Color) -> Dictionary:
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(HUD_UNIT_INFO_SIZE.x, 18.0)
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_theme_constant_override("separation", 6)
var row_label := Label.new()
row_label.text = label_text
row_label.custom_minimum_size = Vector2(42, 16)
row_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_apply_label_style(row_label, UI_OLD_BRONZE, 11)
row.add_child(row_label)
var bar := ProgressBar.new()
bar.custom_minimum_size = HUD_UNIT_BAR_SIZE
bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL
bar.min_value = 0.0
bar.max_value = 1.0
bar.value = 0.0
bar.show_percentage = false
bar.add_theme_stylebox_override("background", _make_panel_style(Color(0.045, 0.024, 0.014, 0.95), UI_LACQUER_EDGE, 1, 0, 0, 0))
bar.add_theme_stylebox_override("fill", _make_panel_style(fill_color, fill_color.lightened(0.28), 1, 0, 0, 0))
row.add_child(bar)
return {
"row": row,
"bar": bar
}
func _format_local_command_tooltip(label: String, hint: String) -> String: func _format_local_command_tooltip(label: String, hint: String) -> String:
var normalized_label := label.strip_edges() var normalized_label := label.strip_edges()
var normalized_hint := hint.strip_edges() var normalized_hint := hint.strip_edges()
@@ -1582,12 +1618,26 @@ func _create_hud() -> void:
_apply_label_style(hud_unit_portrait_label, UI_PARCHMENT_TEXT) _apply_label_style(hud_unit_portrait_label, UI_PARCHMENT_TEXT)
hud_unit_portrait_stack.add_child(hud_unit_portrait_label) hud_unit_portrait_stack.add_child(hud_unit_portrait_label)
hud_unit_info_column = VBoxContainer.new()
hud_unit_info_column.custom_minimum_size = HUD_UNIT_INFO_SIZE
hud_unit_info_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hud_unit_info_column.add_theme_constant_override("separation", 5)
selected_row.add_child(hud_unit_info_column)
selected_label = Label.new() selected_label = Label.new()
selected_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART selected_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
selected_label.custom_minimum_size = Vector2(320, 126) selected_label.custom_minimum_size = HUD_UNIT_SUMMARY_SIZE
selected_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL selected_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_apply_label_style(selected_label, UI_PARCHMENT_TEXT) _apply_label_style(selected_label, UI_PARCHMENT_TEXT, 13)
selected_row.add_child(selected_label) hud_unit_info_column.add_child(selected_label)
var hp_row := _make_hud_unit_resource_row("병력", Color(0.66, 0.12, 0.075, 1.0))
hud_unit_hp_bar = hp_row["bar"] as ProgressBar
hud_unit_info_column.add_child(hp_row["row"] as Control)
var mp_row := _make_hud_unit_resource_row("기력", Color(0.20, 0.42, 0.70, 1.0))
hud_unit_mp_bar = mp_row["bar"] as ProgressBar
hud_unit_info_column.add_child(mp_row["row"] as Control)
var mission_title_row := HBoxContainer.new() var mission_title_row := HBoxContainer.new()
mission_title_row.add_theme_constant_override("separation", 8) mission_title_row.add_theme_constant_override("separation", 8)
@@ -6081,9 +6131,12 @@ func _update_hud() -> void:
if hovered_unit.is_empty(): if hovered_unit.is_empty():
selected_label.text = "전장 관망" selected_label.text = "전장 관망"
selected_label.tooltip_text = "마우스를 부대 위에 올리면 상세 군세가 표시됩니다." selected_label.tooltip_text = "마우스를 부대 위에 올리면 상세 군세가 표시됩니다."
_update_hud_unit_resource_bars({}, selected_label.tooltip_text)
else: else:
var hover_detail := _format_unit_focus_text(hovered_unit, "Hover")
selected_label.text = _format_unit_focus_summary_text(hovered_unit, "Hover") selected_label.text = _format_unit_focus_summary_text(hovered_unit, "Hover")
selected_label.tooltip_text = _format_unit_focus_text(hovered_unit, "Hover") selected_label.tooltip_text = hover_detail
_update_hud_unit_resource_bars(hovered_unit, hover_detail)
_update_wait_button({}) _update_wait_button({})
_update_tactic_button({}) _update_tactic_button({})
_hide_tactic_menu() _hide_tactic_menu()
@@ -6093,8 +6146,10 @@ func _update_hud() -> void:
_hide_equip_menu() _hide_equip_menu()
else: else:
_update_hud_unit_portrait(selected) _update_hud_unit_portrait(selected)
var selected_detail := _format_unit_focus_text(selected, "Selected")
selected_label.text = _format_unit_focus_summary_text(selected, "Selected") selected_label.text = _format_unit_focus_summary_text(selected, "Selected")
selected_label.tooltip_text = _format_unit_focus_text(selected, "Selected") selected_label.tooltip_text = selected_detail
_update_hud_unit_resource_bars(selected, selected_detail)
_update_wait_button(selected) _update_wait_button(selected)
_update_tactic_button(selected) _update_tactic_button(selected)
_update_item_button(selected) _update_item_button(selected)
@@ -6216,23 +6271,47 @@ func _format_unit_focus_summary_text(unit: Dictionary, prefix: String) -> String
for index in range(3): for index in range(3):
visible_parts.append(str(status_parts[index]).strip_edges()) visible_parts.append(str(status_parts[index]).strip_edges())
status_text = "%s%d" % [_join_strings(visible_parts, ", "), status_parts.size() - 3] status_text = "%s%d" % [_join_strings(visible_parts, ", "), status_parts.size() - 3]
var summary := "%s: %s%s\n%s 품계 %d · %s\n병력 %d/%d · 기력 %d/%d\n군령: %s" % [ return "%s: %s%s\n%s 품계 %d · %s\n군령: %s" % [
_unit_focus_prefix_text(prefix), _unit_focus_prefix_text(prefix),
name, name,
control_label, control_label,
_unit_team_text(str(unit.get("team", ""))), _unit_team_text(str(unit.get("team", ""))),
int(unit.get("level", 1)), int(unit.get("level", 1)),
_unit_class_display_name(unit), _unit_class_display_name(unit),
int(unit.get("hp", 0)),
int(unit.get("max_hp", 1)),
int(unit.get("mp", 0)),
int(unit.get("max_mp", 0)),
status_text status_text
] ]
var status_effects := state.get_status_effect_summary(str(unit.get("id", "")))
if not status_effects.is_empty():
summary += "\n상태: %s" % status_effects func _update_hud_unit_resource_bars(unit: Dictionary, detail_text: String) -> void:
return summary if unit.is_empty():
_set_hud_unit_resource_bar(hud_unit_hp_bar, 0, 1, "병력", "", false)
_set_hud_unit_resource_bar(hud_unit_mp_bar, 0, 1, "기력", "", false)
return
var hp_max := maxi(1, int(unit.get("max_hp", 1)))
var hp_value := clampi(int(unit.get("hp", 0)), 0, hp_max)
_set_hud_unit_resource_bar(hud_unit_hp_bar, hp_value, hp_max, "병력", detail_text, true)
var mp_max := int(unit.get("max_mp", 0))
var mp_value := clampi(int(unit.get("mp", 0)), 0, maxi(0, mp_max))
_set_hud_unit_resource_bar(hud_unit_mp_bar, mp_value, maxi(1, mp_max), "기력", detail_text, mp_max > 0)
func _set_hud_unit_resource_bar(bar: ProgressBar, value: int, max_value: int, label_text: String, detail_text: String, visible: bool) -> void:
if bar == null:
return
var row := bar.get_parent() as Control
if row != null:
row.visible = visible
bar.max_value = float(maxi(1, max_value))
bar.value = float(clampi(value, 0, maxi(1, max_value)))
var tooltip := ""
if visible:
tooltip = "%s %d/%d" % [label_text, value, max_value]
if not detail_text.strip_edges().is_empty():
tooltip = "%s\n%s" % [tooltip, detail_text]
bar.tooltip_text = tooltip
if row != null:
row.tooltip_text = tooltip
func _unit_focus_prefix_text(prefix: String) -> String: func _unit_focus_prefix_text(prefix: String) -> String:

View File

@@ -67,7 +67,10 @@ func _check_hud_reuses_status_summary(failures: Array[String]) -> void:
scene.state.selected_unit_id = "cao_cao" scene.state.selected_unit_id = "cao_cao"
scene.hover_cell = Vector2i(1, 6) scene.hover_cell = Vector2i(1, 6)
scene._update_hud() scene._update_hud()
_check_contains(failures, "selected HUD status", scene.selected_label.text, "상태: 방비 -2 (2회)") if scene.selected_label.text.contains("상태:"):
failures.append("selected HUD should keep status effects in tooltip/markers, not visible text: %s" % scene.selected_label.text)
_check_contains(failures, "selected HUD status tooltip", scene.selected_label.tooltip_text, "상태: 방비 -2 (2회)")
_check_contains(failures, "selected HP bar status tooltip", scene.hud_unit_hp_bar.tooltip_text, "상태: 방비 -2 (2회)")
_check_contains(failures, "hover tile tooltip status", scene.cell_info_label.tooltip_text, "상태: 방비 -2 (2회)") _check_contains(failures, "hover tile tooltip status", scene.cell_info_label.tooltip_text, "상태: 방비 -2 (2회)")
scene.state.clear_selection() scene.state.clear_selection()
scene._refresh_ranges() scene._refresh_ranges()

View File

@@ -3043,8 +3043,21 @@ func _check_hud_focus_text(failures: Array[String]) -> void:
failures.append("Cao Cao HUD focus should include terrain move cost: %s" % cao_text) failures.append("Cao Cao HUD focus should include terrain move cost: %s" % cao_text)
if not cao_text.contains("방비 +0"): if not cao_text.contains("방비 +0"):
failures.append("Cao Cao HUD focus should include terrain defense: %s" % cao_text) failures.append("Cao Cao HUD focus should include terrain defense: %s" % cao_text)
if not cao_summary.contains("군기: 조조") or not cao_summary.contains("") or cao_summary.contains("무위") or cao_summary.contains("지형 2,7"): if not cao_summary.contains("군기: 조조") or cao_summary.contains("병력") or cao_summary.contains("") or cao_summary.contains("무위") or cao_summary.contains("지형 2,7"):
failures.append("Cao Cao visible HUD summary should stay compact while tooltip keeps detail: %s" % cao_summary) failures.append("Cao Cao visible HUD summary should leave resource/detail text to bars and tooltip: %s" % cao_summary)
scene._create_hud()
scene.battle_started = true
scene.state.selected_unit_id = "cao_cao"
scene.hover_cell = Vector2i(1, 6)
scene._update_hud()
if scene.hud_unit_hp_bar == null or int(scene.hud_unit_hp_bar.value) != int(cao_cao.get("hp", 0)) or int(scene.hud_unit_hp_bar.max_value) != int(cao_cao.get("max_hp", 1)):
failures.append("selected HUD should expose Cao Cao HP through a resource bar")
if scene.hud_unit_mp_bar == null or int(scene.hud_unit_mp_bar.value) != int(cao_cao.get("mp", 0)) or int(scene.hud_unit_mp_bar.max_value) != int(cao_cao.get("max_mp", 0)):
failures.append("selected HUD should expose Cao Cao MP through a resource bar")
if not scene.hud_unit_hp_bar.tooltip_text.contains("병력") or not scene.hud_unit_hp_bar.tooltip_text.contains("군기: 조조"):
failures.append("selected HP bar tooltip should preserve unit detail: %s" % scene.hud_unit_hp_bar.tooltip_text)
if scene.selected_label.text.contains("병력") or scene.selected_label.text.contains("기력"):
failures.append("selected label should not duplicate HP/MP bars: %s" % scene.selected_label.text)
var xiahou_dun: Dictionary = scene.state.get_unit("xiahou_dun") var xiahou_dun: Dictionary = scene.state.get_unit("xiahou_dun")
xiahou_dun["pos"] = Vector2i(4, 1) xiahou_dun["pos"] = Vector2i(4, 1)
var forest_text := scene._unit_current_terrain_text(xiahou_dun) var forest_text := scene._unit_current_terrain_text(xiahou_dun)