NEO-97: fix HUD overlap with vertical stack and larger fonts

Replace fixed-height absolute labels with HudRoot VBoxContainer so text
expands without overlapping; apply 22/20px theme via prototype_hud_theme.
pull/136/head
VinPropane 2026-05-29 21:32:18 -04:00
parent d2765dca82
commit ab0debd0b4
7 changed files with 180 additions and 132 deletions

View File

@ -1128,225 +1128,195 @@ anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 12.0
offset_top = -44.0
offset_top = -56.0
offset_right = -12.0
offset_bottom = -8.0
grow_horizontal = 2
theme_override_colors/font_color = Color(0.95, 0.75, 0.75, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
[node name="PlayerPositionLabel" type="Label" parent="UICanvas" unique_id=9000003]
[node name="HudRoot" type="VBoxContainer" parent="UICanvas" unique_id=9000022]
offset_left = 8.0
offset_top = 8.0
offset_right = 360.0
offset_bottom = 118.0
grow_horizontal = 0
grow_vertical = 0
offset_right = 664.0
theme_override_constants/separation = 12
[node name="PlayerPositionLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000003]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.92, 0.95, 0.98, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "x: —
y: —
z: —
srv: —"
[node name="TargetLockLabel" type="Label" parent="UICanvas" unique_id=9000004]
offset_left = 8.0
offset_top = 122.0
offset_right = 360.0
offset_bottom = 306.0
grow_horizontal = 0
grow_vertical = 0
[node name="TargetLockLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000004]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.86, 0.94, 1, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "Target: —
Validity: —
Seq: —"
[node name="CastFeedbackLabel" type="Label" parent="UICanvas" unique_id=9000005]
offset_left = 8.0
offset_top = 318.0
offset_right = 520.0
offset_bottom = 372.0
grow_horizontal = 0
grow_vertical = 0
[node name="CastFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000005]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.95, 0.88, 0.72, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "Cast: —"
[node name="CombatTargetHpLabel" type="Label" parent="UICanvas" unique_id=9000018]
offset_left = 8.0
offset_top = 374.0
offset_right = 520.0
offset_bottom = 402.0
grow_horizontal = 0
grow_vertical = 0
[node name="CombatTargetHpLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000018]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.92, 0.78, 0.82, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "Target HP: — (Tab → dummy)"
[node name="PlayerCombatHpLabel" type="Label" parent="UICanvas" unique_id=9000019]
offset_left = 8.0
offset_top = 404.0
offset_right = 520.0
offset_bottom = 432.0
grow_horizontal = 0
grow_vertical = 0
[node name="PlayerCombatHpLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000019]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.82, 0.92, 0.98, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "Player HP: —"
[node name="NpcStateLabel" type="Label" parent="UICanvas" unique_id=9000020]
offset_left = 8.0
offset_top = 434.0
offset_right = 520.0
offset_bottom = 472.0
grow_horizontal = 0
grow_vertical = 0
[node name="NpcStateLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000020]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.88, 0.82, 0.98, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "NPC state: —"
[node name="TelegraphLabel" type="Label" parent="UICanvas" unique_id=9000021]
offset_left = 8.0
offset_top = 474.0
offset_right = 520.0
offset_bottom = 502.0
grow_horizontal = 0
grow_vertical = 0
[node name="TelegraphLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000021]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(1, 0.72, 0.55, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 18
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 22
autowrap_mode = 3
text = "Telegraph: —"
[node name="GatherFeedbackLabel" type="Label" parent="UICanvas" unique_id=9000008]
offset_left = 8.0
offset_top = 504.0
offset_right = 520.0
offset_bottom = 532.0
grow_horizontal = 0
grow_vertical = 0
[node name="GatherFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000008]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.88, 0.95, 0.72, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Gather: —"
[node name="CraftFeedbackLabel" type="Label" parent="UICanvas" unique_id=9000010]
offset_left = 8.0
offset_top = 534.0
offset_right = 520.0
offset_bottom = 562.0
grow_horizontal = 0
grow_vertical = 0
[node name="CraftFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000010]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.95, 0.82, 0.72, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Craft: —"
[node name="CooldownSlotsLabel" type="Label" parent="UICanvas" unique_id=9000006]
offset_left = 8.0
offset_top = 564.0
offset_right = 520.0
offset_bottom = 642.0
grow_horizontal = 0
grow_vertical = 0
[node name="CooldownSlotsLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000006]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.78, 0.92, 0.86, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Cooldowns: —"
[node name="EconomyHudSection" type="VBoxContainer" parent="UICanvas" unique_id=9000014]
offset_left = 8.0
offset_top = 648.0
offset_right = 520.0
offset_bottom = 1164.0
grow_horizontal = 0
grow_vertical = 0
[node name="EconomyHudSection" type="VBoxContainer" parent="UICanvas/HudRoot" unique_id=9000014]
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("20_economy_hud")
[node name="HeaderRow" type="HBoxContainer" parent="UICanvas/EconomyHudSection" unique_id=9000015]
[node name="HeaderRow" type="HBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection" unique_id=9000015]
layout_mode = 2
[node name="ToggleButton" type="CheckButton" parent="UICanvas/EconomyHudSection/HeaderRow" unique_id=9000016]
[node name="ToggleButton" type="CheckButton" parent="UICanvas/HudRoot/EconomyHudSection/HeaderRow" unique_id=9000016]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "Economy HUD"
[node name="Body" type="VBoxContainer" parent="UICanvas/EconomyHudSection" unique_id=9000017]
[node name="Body" type="VBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection" unique_id=9000017]
layout_mode = 2
theme_override_constants/separation = 8
[node name="InventoryLabel" type="Label" parent="UICanvas/EconomyHudSection/Body" unique_id=9000007]
custom_minimum_size = Vector2(512, 148)
[node name="InventoryLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000007]
custom_minimum_size = Vector2(656, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.9, 0.86, 0.98, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Inventory: (refresh: I)
Loading…"
[node name="SkillProgressionLabel" type="Label" parent="UICanvas/EconomyHudSection/Body" unique_id=9000009]
custom_minimum_size = Vector2(512, 84)
[node name="SkillProgressionLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000009]
custom_minimum_size = Vector2(656, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.82, 0.9, 1, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Skills:
Loading…"
[node name="GigXpLabel" type="Label" parent="UICanvas/EconomyHudSection/Body" unique_id=9000018]
custom_minimum_size = Vector2(512, 48)
[node name="GigXpLabel" type="Label" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000018]
custom_minimum_size = Vector2(656, 0)
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0.88, 0.78, 1, 1)
theme_override_colors/font_outline_color = Color(0.08, 0.08, 0.1, 1)
theme_override_constants/outline_size = 6
theme_override_font_sizes/font_size = 17
theme_override_constants/outline_size = 8
theme_override_font_sizes/font_size = 20
autowrap_mode = 3
text = "Gig XP:
Loading…"
[node name="CraftRecipePanel" type="Control" parent="UICanvas/EconomyHudSection/Body" unique_id=9000011]
custom_minimum_size = Vector2(512, 280)
[node name="CraftRecipePanel" type="Control" parent="UICanvas/HudRoot/EconomyHudSection/Body" unique_id=9000011]
custom_minimum_size = Vector2(656, 320)
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("19_craft_panel")
[node name="ScrollContainer" type="ScrollContainer" parent="UICanvas/EconomyHudSection/Body/CraftRecipePanel" unique_id=9000012]
layout_mode = 0
offset_right = 512.0
offset_bottom = 280.0
[node name="ScrollContainer" type="ScrollContainer" parent="UICanvas/HudRoot/EconomyHudSection/Body/CraftRecipePanel" unique_id=9000012]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="UICanvas/EconomyHudSection/Body/CraftRecipePanel/ScrollContainer" unique_id=9000013]
[node name="VBoxContainer" type="VBoxContainer" parent="UICanvas/HudRoot/EconomyHudSection/Body/CraftRecipePanel/ScrollContainer" unique_id=9000013]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 8

View File

@ -2,6 +2,8 @@ extends Control
## NEO-74: scrollable prototype recipe list with per-row Craft buttons.
const PrototypeHudTheme := preload("res://scripts/prototype_hud_theme.gd")
signal craft_requested(recipe_id: String)
var _item_defs_client: Node = null
@ -33,9 +35,11 @@ func populate(recipes: Array) -> void:
info.size_flags_horizontal = Control.SIZE_EXPAND_FILL
info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
info.text = _format_recipe_line(recipe)
PrototypeHudTheme.apply_to_label(info, false)
row.add_child(info)
var craft_btn := Button.new()
craft_btn.text = "Craft"
PrototypeHudTheme.apply_to_button(craft_btn, false)
craft_btn.pressed.connect(_on_craft_pressed.bind(recipe_id))
row.add_child(craft_btn)
_vbox.add_child(row)

View File

@ -17,7 +17,7 @@ extends Node3D
## Steep ramp: **2 m / 1.5 m** rise/run (~53°) so slope exceeds `Player` `floor_max_angle` (~50°)
## and is not walkable; gentle ramp stays shallower than `NavigationMesh.agent_max_climb`. Steep is
## under `World` only (not nav source geometry).
## Prototype HUD: world `CharacterBody3D` position in `UICanvas/PlayerPositionLabel`
## Prototype HUD: world `CharacterBody3D` position in `UICanvas/HudRoot/PlayerPositionLabel`
## (updated in `_physics_process` so it matches physics ticks).
const MOVE_REJECT_MSG_SECONDS: float = 4.0
@ -46,6 +46,7 @@ const NpcRuntimeClientScript := preload("res://scripts/npc_runtime_client.gd")
const PlayerCombatHealthClientScript := preload("res://scripts/player_combat_health_client.gd")
const NpcRuntimeHudStateScript := preload("res://scripts/npc_runtime_hud_state.gd")
const NpcCombatHudHelpers := preload("res://scripts/npc_combat_hud_helpers.gd")
const PrototypeHudTheme := preload("res://scripts/prototype_hud_theme.gd")
const COMBAT_POLL_INTERVAL_SEC: float = 1.0
const COOLDOWN_REASON_ON_CD := "on_cooldown"
const DEV_BOOTSTRAP_CAST_ABILITY_ID := "prototype_pulse"
@ -119,18 +120,19 @@ var _dev_pulse_bootstrap_attempted: bool = false
@onready var _interactables_root: Node3D = $World/NavigationRegion3D/InteractablesRoot
@onready var _radius_preview: Node3D = $World/InteractionMarkers
@onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel
@onready var _player_pos_label: Label = $UICanvas/PlayerPositionLabel
@onready var _target_lock_label: Label = $UICanvas/TargetLockLabel
@onready var _cast_feedback_label: Label = $UICanvas/CastFeedbackLabel
@onready var _combat_target_hp_label: Label = $UICanvas/CombatTargetHpLabel
@onready var _player_combat_hp_label: Label = $UICanvas/PlayerCombatHpLabel
@onready var _npc_state_label: Label = $UICanvas/NpcStateLabel
@onready var _telegraph_label: Label = $UICanvas/TelegraphLabel
@onready var _cooldown_slots_label: Label = $UICanvas/CooldownSlotsLabel
@onready var _economy_hud_section: VBoxContainer = $UICanvas/EconomyHudSection
@onready var _hud_root: VBoxContainer = $UICanvas/HudRoot
@onready var _player_pos_label: Label = $UICanvas/HudRoot/PlayerPositionLabel
@onready var _target_lock_label: Label = $UICanvas/HudRoot/TargetLockLabel
@onready var _cast_feedback_label: Label = $UICanvas/HudRoot/CastFeedbackLabel
@onready var _combat_target_hp_label: Label = $UICanvas/HudRoot/CombatTargetHpLabel
@onready var _player_combat_hp_label: Label = $UICanvas/HudRoot/PlayerCombatHpLabel
@onready var _npc_state_label: Label = $UICanvas/HudRoot/NpcStateLabel
@onready var _telegraph_label: Label = $UICanvas/HudRoot/TelegraphLabel
@onready var _cooldown_slots_label: Label = $UICanvas/HudRoot/CooldownSlotsLabel
@onready var _economy_hud_section: VBoxContainer = $UICanvas/HudRoot/EconomyHudSection
@onready var _inventory_label: Label = _economy_hud_section.get_node("Body/InventoryLabel")
@onready var _gather_feedback_label: Label = $UICanvas/GatherFeedbackLabel
@onready var _craft_feedback_label: Label = $UICanvas/CraftFeedbackLabel
@onready var _gather_feedback_label: Label = $UICanvas/HudRoot/GatherFeedbackLabel
@onready var _craft_feedback_label: Label = $UICanvas/HudRoot/CraftFeedbackLabel
@onready
var _skill_progression_label: Label = _economy_hud_section.get_node("Body/SkillProgressionLabel")
@onready var _gig_xp_label: Label = _economy_hud_section.get_node("Body/GigXpLabel")
@ -150,6 +152,7 @@ var _skill_progression_label: Label = _economy_hud_section.get_node("Body/SkillP
func _ready() -> void:
set_physics_process(true)
set_process_unhandled_key_input(true)
_apply_prototype_hud_theme()
await get_tree().process_frame
_dev_obstacle_smoke = get_node_or_null("World/NavigationRegion3D/Obstacle") as Node3D
# Runtime load + .call avoids class_name / preload static resolution issues across Godot parses.
@ -285,6 +288,12 @@ func _on_authoritative_position(world: Vector3, apply_as_snap: bool) -> void:
## [code]push_warning[/code] in [code]ability_cast_client.gd[/code].
## HUD [member _cast_feedback_label] (NEO-28). Deny branches in [code]AbilityCastApi.cs[/code].
## TODO(E9.M1): catalog + ingest.
func _apply_prototype_hud_theme() -> void:
PrototypeHudTheme.apply_to_label(_move_reject_label, true)
if is_instance_valid(_hud_root):
PrototypeHudTheme.apply_to_hud_root(_hud_root)
func _on_authoritative_ack_for_hud(world: Vector3) -> void:
_last_ack_world = world
_have_last_ack = true

View File

@ -0,0 +1,48 @@
extends RefCounted
## Applies shared prototype HUD font sizing to scene labels and dynamic craft rows.
const Constants := preload("res://scripts/prototype_hud_theme_constants.gd")
static func apply_to_label(label: Label, primary: bool = true) -> void:
if not is_instance_valid(label):
return
var font_size: int = (
Constants.FONT_SIZE_PRIMARY if primary else Constants.FONT_SIZE_SECONDARY
)
label.add_theme_font_size_override("font_size", font_size)
label.add_theme_constant_override("outline_size", Constants.OUTLINE_SIZE)
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.custom_minimum_size.x = Constants.HUD_STACK_WIDTH
static func apply_to_button(button: BaseButton, primary: bool = true) -> void:
if not is_instance_valid(button):
return
var font_size: int = (
Constants.FONT_SIZE_PRIMARY if primary else Constants.FONT_SIZE_SECONDARY
)
button.add_theme_font_size_override("font_size", font_size)
static func apply_to_hud_root(root: VBoxContainer) -> void:
if not is_instance_valid(root):
return
root.add_theme_constant_override("separation", Constants.STACK_SEPARATION)
for child in root.get_children():
if child is Label:
var label: Label = child as Label
apply_to_label(label, not Constants.SECONDARY_LABEL_NAMES.has(label.name))
elif child.name == "EconomyHudSection":
apply_to_economy_section(child)
static func apply_to_economy_section(section: Node) -> void:
if not is_instance_valid(section):
return
for label in section.find_children("*", "Label", true, false):
apply_to_label(label as Label, false)
var toggle: Node = section.get_node_or_null("HeaderRow/ToggleButton")
if toggle is BaseButton:
apply_to_button(toggle as BaseButton, false)

View File

@ -0,0 +1 @@
uid://c8hudthemeapply01

View File

@ -0,0 +1,15 @@
extends RefCounted
## Prototype HUD typography + stack spacing (NEO-97 readability pass).
const FONT_SIZE_PRIMARY: int = 22
const FONT_SIZE_SECONDARY: int = 20
const OUTLINE_SIZE: int = 8
const STACK_SEPARATION: int = 12
const HUD_STACK_WIDTH: float = 656.0
const SECONDARY_LABEL_NAMES: Array[StringName] = [
&"GatherFeedbackLabel",
&"CraftFeedbackLabel",
&"CooldownSlotsLabel",
]

View File

@ -0,0 +1 @@
uid://c8hudthemeconst01