Files
CosmicClash/Game/scripts/HUDController.gd
T

141 lines
6.1 KiB
GDScript

# HUD Controller - discovers the ship/camera/game mode via groups and routes
# their signals to the display widgets. All flight data is rendered by
# aircraft-style instruments (attitude indicator, heading tape, bar gauges) —
# this node is only the wiring hub; the instruments are dumb displays.
extends CanvasLayer
class_name HUDController
@onready var timer_label = get_node("Control/TimerLabel")
@onready var score_row = get_node_or_null("Control/ScoreRow")
@onready var team0_score_label = get_node_or_null("Control/ScoreRow/Team0Score")
@onready var team1_score_label = get_node_or_null("Control/ScoreRow/Team1Score")
@onready var result_overlay = get_node_or_null("Control/ResultOverlay")
@onready var result_label = get_node_or_null("Control/ResultOverlay/Center/VBox/ResultLabel")
@onready var final_score_label = get_node_or_null("Control/ResultOverlay/Center/VBox/FinalScoreLabel")
@onready var heading_tape: HudHeadingTape = get_node_or_null("Control/Instruments/HeadingTape")
@onready var attitude_indicator: HudAttitudeIndicator = get_node_or_null("Control/Instruments/Cluster/Dials/AttitudeIndicator")
@onready var speed_gauge: HudGauge = get_node_or_null("Control/Instruments/Cluster/Dials/SpeedGauge")
@onready var altitude_gauge: HudGauge = get_node_or_null("Control/Instruments/Cluster/Dials/AltitudeGauge")
@onready var thrust_bar: HudGauge = get_node_or_null("Control/Instruments/Cluster/ThrustBar")
@onready var camera_mode_label = get_node_or_null("Control/Instruments/Cluster/CameraModeLabel")
# Team identity, mirroring TEAM_COLORS in ship.gd
const TEAM_NAMES := {0: "Blue", 1: "Orange"}
const TEAM_COLORS := {0: Color(0.25, 0.55, 1.0), 1: Color(1.0, 0.5, 0.15)}
var ship: Node
func _ready():
# Wait one frame to ensure ship is ready and added to group
await get_tree().process_frame
_initialize_hud()
func _initialize_hud():
# Find the ship
ship = get_tree().get_first_node_in_group("ship")
if not ship:
push_error("HUDController: No ship found in 'ship' group")
return
print("HUDController: Found ship: ", ship.name)
# Gauge ranges derive from the ship and arena rather than restating numbers
if speed_gauge and "max_speed" in ship:
speed_gauge.max_value = ship.max_speed
if altitude_gauge:
altitude_gauge.max_value = ArenaBoundary.INNER_HEIGHT
_connect_ship_signals()
# Camera mode comes from the camera rig, not the ship
var camera_rig = get_tree().get_first_node_in_group("ship_camera")
if camera_rig and camera_rig.has_signal("camera_mode_changed"):
camera_rig.camera_mode_changed.connect(_on_ship_camera_mode_changed)
# Connect to game manager's timer signal; modes without a timer
# (e.g. free play) just don't show one
var game_manager = get_tree().get_first_node_in_group("game")
var has_timer = game_manager and game_manager.has_signal("timer_updated")
if has_timer:
game_manager.timer_updated.connect(_on_timer_updated)
print("HUDController: Connected to game timer")
if timer_label and is_instance_valid(timer_label):
timer_label.visible = has_timer
# Score display follows the same pattern: modes without scoring
# (e.g. free play) just don't show it
var has_score = game_manager and game_manager.has_signal("score_changed")
if has_score:
game_manager.score_changed.connect(_on_score_changed)
if score_row and is_instance_valid(score_row):
score_row.visible = has_score
if game_manager and game_manager.has_signal("match_ended"):
game_manager.match_ended.connect(_on_match_ended)
func _connect_ship_signals():
# Route ship telemetry to the flight instruments
if ship.has_signal("speed_changed"):
ship.speed_changed.connect(_on_ship_speed_changed)
if ship.has_signal("altitude_changed"):
ship.altitude_changed.connect(_on_ship_altitude_changed)
if ship.has_signal("attitude_changed"):
ship.attitude_changed.connect(_on_ship_attitude_changed)
if ship.has_signal("heading_changed"):
ship.heading_changed.connect(_on_ship_heading_changed)
if ship.has_signal("thrust_changed"):
ship.thrust_changed.connect(_on_ship_thrust_changed)
# Instrument signal handlers - all include null checks for robustness
func _on_ship_speed_changed(speed: float):
if speed_gauge and is_instance_valid(speed_gauge):
speed_gauge.set_value(speed)
func _on_ship_altitude_changed(altitude: float):
if altitude_gauge and is_instance_valid(altitude_gauge):
altitude_gauge.set_value(altitude)
func _on_ship_attitude_changed(pitch: float, roll: float, _yaw: float):
if attitude_indicator and is_instance_valid(attitude_indicator):
attitude_indicator.set_attitude(pitch, roll)
func _on_ship_heading_changed(heading_degrees: float):
if heading_tape and is_instance_valid(heading_tape):
heading_tape.set_heading(heading_degrees)
func _on_ship_thrust_changed(thrust_percent: float):
if thrust_bar and is_instance_valid(thrust_bar):
thrust_bar.set_value(thrust_percent)
func _on_ship_camera_mode_changed(is_ball_cam: bool):
if camera_mode_label and is_instance_valid(camera_mode_label):
var camera_mode = "Ball Cam" if is_ball_cam else "Ship Cam"
camera_mode_label.text = "Camera: %s" % camera_mode
# Timer signal handler from game manager
func _on_timer_updated(minutes: int, seconds: int):
if timer_label and is_instance_valid(timer_label):
timer_label.text = "%02d:%02d" % [minutes, seconds]
# Simple timer update method (kept for compatibility)
func update_timer(time_text: String):
if timer_label and is_instance_valid(timer_label):
timer_label.text = time_text
func _on_score_changed(score: Dictionary):
if team0_score_label and is_instance_valid(team0_score_label):
team0_score_label.text = str(score.get(0, 0))
if team1_score_label and is_instance_valid(team1_score_label):
team1_score_label.text = str(score.get(1, 0))
func _on_match_ended(winning_team: int, score: Dictionary):
if not result_overlay or not is_instance_valid(result_overlay):
return
if winning_team < 0:
result_label.text = "Draw"
result_label.remove_theme_color_override("font_color")
else:
result_label.text = "%s team wins!" % TEAM_NAMES[winning_team]
result_label.add_theme_color_override("font_color", TEAM_COLORS[winning_team])
final_score_label.text = "%d - %d" % [score.get(0, 0), score.get(1, 0)]
result_overlay.visible = true