Add comprehensive UI system with action bar, unit frame, and character sheet
- Created UI system in level/ui/ folder - Action bar with 12 ability slots (Attack, Block, Dash, Jump + 8 expansion slots) - Unit frame showing player portrait with health bar - Character sheet/spellbook (toggle with Tab) displaying stats, weapons, and abilities - Tab hint indicator showing how to open character sheet - Custom theme with golden borders and dark backgrounds - UI Components: - HUD Manager autoload handles all UI initialization and player connections - Ability buttons with cooldown overlay and keybind display - Real-time health and cooldown tracking via signals - Scrollable character sheet with two-page layout - Player Integration: - Added UI signals: dash_cooldown_updated, attack_cooldown_updated, weapon_equipped_changed - Mouse capture system (Escape to toggle, click to recapture) - Synced attack cooldown from weapons to player for UI tracking - Updated level.gd to connect local player to HUD Manager - Updated CLAUDE.md with git commit guidelines
This commit is contained in:
+30
-58
@@ -53,6 +53,11 @@ var _dash_cooldown_timer: float = 0.0
|
||||
var _is_dashing: bool = false
|
||||
var _dash_direction: Vector3 = Vector3.ZERO
|
||||
|
||||
# UI Signals
|
||||
signal dash_cooldown_updated(remaining: float, total: float)
|
||||
signal attack_cooldown_updated(remaining: float, total: float)
|
||||
signal weapon_equipped_changed()
|
||||
|
||||
func _enter_tree():
|
||||
super._enter_tree()
|
||||
$SpringArmOffset/SpringArm3D/Camera3D.current = is_multiplayer_authority()
|
||||
@@ -61,6 +66,10 @@ func _ready():
|
||||
super._ready()
|
||||
set_respawn_point(Vector3(0, 5, 0))
|
||||
|
||||
# Capture mouse for local player
|
||||
if is_multiplayer_authority():
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
# Auto-find body node (needed for instanced scenes where @export NodePath doesn't work reliably)
|
||||
if _body == null:
|
||||
for child in get_children():
|
||||
@@ -132,10 +141,6 @@ func _ready():
|
||||
# Update health display
|
||||
_update_health_display()
|
||||
|
||||
# Create 2D UI health bar for local player
|
||||
if is_multiplayer_authority():
|
||||
_create_health_ui()
|
||||
|
||||
# Setup weapon pickup detection area
|
||||
_setup_weapon_pickup_area()
|
||||
|
||||
@@ -208,9 +213,21 @@ func _process(delta):
|
||||
|
||||
_check_fall_and_respawn()
|
||||
|
||||
# Handle mouse capture toggle (Escape to release, click to recapture)
|
||||
if Input.is_action_just_pressed("quit"):
|
||||
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
else:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
# Recapture mouse on click if it was released
|
||||
if Input.is_action_just_pressed("attack") and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
# Update attack cooldown
|
||||
if _attack_timer > 0:
|
||||
_attack_timer -= delta
|
||||
attack_cooldown_updated.emit(_attack_timer, attack_cooldown)
|
||||
|
||||
# Update dash timers
|
||||
if _dash_timer > 0:
|
||||
@@ -220,6 +237,7 @@ func _process(delta):
|
||||
|
||||
if _dash_cooldown_timer > 0:
|
||||
_dash_cooldown_timer -= delta
|
||||
dash_cooldown_updated.emit(_dash_cooldown_timer, dash_cooldown)
|
||||
|
||||
# Handle dash input
|
||||
if Input.is_action_just_pressed("dash") and _dash_cooldown_timer <= 0 and not is_dead and is_on_floor():
|
||||
@@ -441,65 +459,15 @@ func _server_apply_damage(target_name: String, damage: float, attacker_id: int,
|
||||
target.take_damage(damage, attacker_id, knockback, attacker_pos)
|
||||
|
||||
## Health display and callbacks
|
||||
func _create_health_ui():
|
||||
# Create a 2D UI for the local player's health
|
||||
var canvas = CanvasLayer.new()
|
||||
canvas.name = "HealthUI"
|
||||
add_child(canvas)
|
||||
|
||||
# Health bar background
|
||||
var health_bg = ColorRect.new()
|
||||
health_bg.name = "HealthBG"
|
||||
health_bg.color = Color(0.2, 0.2, 0.2, 0.8)
|
||||
health_bg.position = Vector2(20, 20)
|
||||
health_bg.size = Vector2(200, 30)
|
||||
canvas.add_child(health_bg)
|
||||
|
||||
# Health bar (current health)
|
||||
var health_bar = ColorRect.new()
|
||||
health_bar.name = "HealthBar"
|
||||
health_bar.color = Color(0.0, 0.8, 0.0, 1.0) # Green
|
||||
health_bar.position = Vector2(22, 22)
|
||||
health_bar.size = Vector2(196, 26)
|
||||
canvas.add_child(health_bar)
|
||||
|
||||
# Health text
|
||||
var health_text = Label.new()
|
||||
health_text.name = "HealthText"
|
||||
health_text.position = Vector2(20, 20)
|
||||
health_text.size = Vector2(200, 30)
|
||||
health_text.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
health_text.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
health_text.add_theme_color_override("font_color", Color.WHITE)
|
||||
health_text.add_theme_color_override("font_outline_color", Color.BLACK)
|
||||
health_text.add_theme_constant_override("outline_size", 2)
|
||||
health_text.text = "HP: %d/%d" % [int(current_health), int(max_health)]
|
||||
canvas.add_child(health_text)
|
||||
# Old 2D health UI - now handled by HUD Manager
|
||||
# Kept for reference, but no longer used
|
||||
|
||||
func _update_health_display():
|
||||
# Update 3D label if it exists
|
||||
# Update 3D label if it exists (for other players to see)
|
||||
if health_label:
|
||||
health_label.text = "HP: %d/%d" % [int(current_health), int(max_health)]
|
||||
|
||||
# Update 2D UI for local player
|
||||
if is_multiplayer_authority() and has_node("HealthUI"):
|
||||
var health_bar = get_node_or_null("HealthUI/HealthBar")
|
||||
var health_text = get_node_or_null("HealthUI/HealthText")
|
||||
|
||||
if health_bar:
|
||||
var health_percent = get_health_percent()
|
||||
health_bar.size.x = 196 * health_percent
|
||||
|
||||
# Change color based on health
|
||||
if health_percent > 0.6:
|
||||
health_bar.color = Color(0.0, 0.8, 0.0) # Green
|
||||
elif health_percent > 0.3:
|
||||
health_bar.color = Color(1.0, 0.8, 0.0) # Yellow
|
||||
else:
|
||||
health_bar.color = Color(0.8, 0.0, 0.0) # Red
|
||||
|
||||
if health_text:
|
||||
health_text.text = "HP: %d/%d" % [int(current_health), int(max_health)]
|
||||
# 2D UI is now handled by HUD Manager autoload
|
||||
|
||||
func _on_health_changed(_old_health: float, _new_health: float):
|
||||
_update_health_display()
|
||||
@@ -688,6 +656,7 @@ func equip_weapon(data: WeaponData):
|
||||
|
||||
if is_multiplayer_authority():
|
||||
print("Equipped: ", data.weapon_name, " to ", attach_point.name)
|
||||
weapon_equipped_changed.emit()
|
||||
|
||||
## Unequip current weapon (local only)
|
||||
func unequip_weapon(is_offhand: bool = false):
|
||||
@@ -700,6 +669,9 @@ func unequip_weapon(is_offhand: bool = false):
|
||||
equipped_weapon.queue_free()
|
||||
equipped_weapon = null
|
||||
|
||||
if is_multiplayer_authority():
|
||||
weapon_equipped_changed.emit()
|
||||
|
||||
## Sync unequip across all clients
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func _unequip_weapon_sync(is_offhand: bool = false):
|
||||
|
||||
Reference in New Issue
Block a user