refactor(*): Make hud more performant and maintainable

This commit is contained in:
Josh Creek
2025-07-16 19:19:58 +01:00
parent 9ed5afa092
commit 35a23f79e3
8 changed files with 264 additions and 108 deletions
+102
View File
@@ -0,0 +1,102 @@
# HUD Controller - handles all display logic and label updates
# Receives ship data via signals and updates UI accordingly
# Simplified version focusing on labels only
extends CanvasLayer
class_name HUDController
# UI Label References
@onready var timer_label = get_node("Control/TimerLabel")
@onready var speed_label = get_node("Control/InfoPanel/SpeedLabel")
@onready var altitude_label = get_node("Control/InfoPanel/AltitudeLabel")
@onready var angular_vel_label = get_node("Control/InfoPanel/AngularVelLabel")
@onready var camera_mode_label = get_node("Control/InfoPanel/CameraModeLabel")
@onready var attitude_label = get_node("Control/InfoPanel/AttitudeLabel")
@onready var heading_label = get_node("Control/InfoPanel/HeadingLabel")
@onready var thrust_label = get_node("Control/InfoPanel/ThrustLabel")
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:
# Try to find it as our parent (ship contains this HUD)
var parent_node = get_parent()
if parent_node and parent_node.is_in_group("ship"):
ship = parent_node
else:
push_error("HUDController: No ship found in 'ship' group")
return
print("HUDController: Found ship: ", ship.name)
_connect_ship_signals()
# Connect to game manager's timer signal
var game_manager = get_tree().get_first_node_in_group("game")
if game_manager and game_manager.has_signal("timer_updated"):
game_manager.timer_updated.connect(_on_timer_updated)
print("HUDController: Connected to game timer")
func _connect_ship_signals():
# Connect ship signals to label update methods
if ship.has_signal("speed_changed"):
ship.speed_changed.connect(_on_ship_speed_changed)
print("HUDController: Connected to speed_changed signal")
if ship.has_signal("altitude_changed"):
ship.altitude_changed.connect(_on_ship_altitude_changed)
if ship.has_signal("angular_velocity_changed"):
ship.angular_velocity_changed.connect(_on_ship_angular_velocity_changed)
if ship.has_signal("camera_mode_changed"):
ship.camera_mode_changed.connect(_on_ship_camera_mode_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)
# Signal handlers for ship data - these update the legacy text labels
# All handlers include null checks for robustness
func _on_ship_speed_changed(speed: float):
if speed_label and is_instance_valid(speed_label):
speed_label.text = "Speed: %.1f m/s" % speed
func _on_ship_altitude_changed(altitude: float):
if altitude_label and is_instance_valid(altitude_label):
altitude_label.text = "Altitude: %.1f m" % altitude
func _on_ship_angular_velocity_changed(angular_speed: float):
if angular_vel_label and is_instance_valid(angular_vel_label):
angular_vel_label.text = "Angular Vel: %.2f rad/s" % angular_speed
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
func _on_ship_attitude_changed(pitch: float, roll: float, yaw: float):
if attitude_label and is_instance_valid(attitude_label):
attitude_label.text = "Pitch: %.0f° Roll: %.0f°" % [pitch, roll]
func _on_ship_heading_changed(heading_degrees: float):
if heading_label and is_instance_valid(heading_label):
heading_label.text = "Heading: %.0f°" % heading_degrees
func _on_ship_thrust_changed(thrust_percent: float):
if thrust_label and is_instance_valid(thrust_label):
thrust_label.text = "Thrust: %.0f%%" % thrust_percent
# Timer signal handler from game manager (restored original functionality)
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
+1
View File
@@ -0,0 +1 @@
uid://du7y176h5aaq4
+3
View File
@@ -4,6 +4,9 @@ extends Node3D
signal timer_updated(minutes: int, seconds: int)
func _ready():
# Add to group for discovery by HUD
add_to_group("game")
# Set timer to 2 minutes 30 seconds (150 seconds)
game_timer.wait_time = 150.0
game_timer.one_shot = true # Timer runs once
+71 -38
View File
@@ -20,22 +20,42 @@ var camera_smoothing = 10.0
@onready var camera : Camera3D = get_node("Camera3D")
@onready var ball = get_parent().get_node("Ball")
# HUD Nodes
@onready var timer_label = get_node("HUD/Control/TimerLabel")
@onready var speed_label = get_node("HUD/Control/InfoPanel/SpeedLabel")
@onready var altitude_label = get_node("HUD/Control/InfoPanel/AltitudeLabel")
@onready var angular_vel_label = get_node("HUD/Control/InfoPanel/AngularVelLabel")
@onready var camera_mode_label = get_node("HUD/Control/InfoPanel/CameraModeLabel")
@onready var attitude_label = get_node("HUD/Control/InfoPanel/AttitudeLabel")
@onready var heading_label = get_node("HUD/Control/InfoPanel/HeadingLabel")
@onready var thrust_label = get_node("HUD/Control/InfoPanel/ThrustLabel")
var ball_cam_enabled = true
# Instrument signals for efficient data distribution
signal speed_changed(speed: float)
signal attitude_changed(pitch: float, roll: float, yaw: float)
signal altitude_changed(altitude: float)
signal thrust_changed(thrust_percent: float)
signal angular_velocity_changed(angular_speed: float)
signal camera_mode_changed(is_ball_cam: bool)
signal heading_changed(heading_degrees: float)
# Performance optimization - track last emitted values to avoid unnecessary signals
var _last_speed: float = -1.0
var _last_altitude: float = -999999.0
var _last_angular_speed: float = -1.0
var _last_pitch: float = -999.0
var _last_roll: float = -999.0
var _last_yaw: float = -999.0
var _last_heading: float = -999.0
var _last_thrust: float = -1.0
var _last_camera_mode: bool = true
# Thresholds for signal emission (only emit if change is significant)
const SPEED_THRESHOLD = 0.1 # m/s
const ALTITUDE_THRESHOLD = 0.5 # meters
const ANGULAR_THRESHOLD = 0.01 # rad/s
const ATTITUDE_THRESHOLD = 1.0 # degrees
const THRUST_THRESHOLD = 1.0 # percent
func _ready():
mass = 5
gravity_scale = 1.0
# Add ship to group for instrument discovery
add_to_group("ship")
# Set custom inertia for better rotation
# Physics: I = m * r² (moment of inertia = mass × radius²)
# Lower inertia = easier to rotate, higher inertia = more stable
@@ -58,26 +78,18 @@ func _ready():
axis_lock_angular_y = false
axis_lock_angular_z = false
var game_scene = get_parent()
if game_scene and game_scene.has_signal("timer_updated"):
game_scene.timer_updated.connect(_on_timer_updated)
else:
print("Game scene not found or doesn't have timer_updated signal")
print("Ship physics configured - Mass: ", mass, " Gravity scale: ", gravity_scale, " Inertia: ", inertia)
print("Rotation locks - X:", axis_lock_angular_x, " Y:", axis_lock_angular_y, " Z:", axis_lock_angular_z)
func _input(event):
if event.is_action_pressed("ui_accept"): # Enter key
ball_cam_enabled = !ball_cam_enabled
func _on_timer_updated(minutes: int, seconds: int):
timer_label.text = "%02d:%02d" % [minutes, seconds]
camera_mode_changed.emit(ball_cam_enabled)
func _physics_process(delta):
if camera:
_update_camera(delta)
_update_hud()
_emit_telemetry_data()
func _update_camera(delta):
if ball_cam_enabled and ball:
@@ -283,44 +295,65 @@ func apply_drag_and_limits(state: PhysicsDirectBodyState3D, rotation_input: Vect
# Physics: ω̂ = ω / |ω|, ω_limited = ω̂ * ω_max
state.angular_velocity = state.angular_velocity.normalized() * max_angular_speed
func _update_hud():
# Update speed display
func _emit_telemetry_data():
# Ship only calculates and emits data - HUD handles display
# Performance optimization: only emit signals when values change significantly
# Speed telemetry
# Physics: |v| = √(vₓ² + vᵧ² + vᵤ²) (magnitude of velocity vector)
var current_speed = linear_velocity.length()
speed_label.text = "Speed: %.1f m/s" % current_speed
if abs(current_speed - _last_speed) > SPEED_THRESHOLD:
speed_changed.emit(current_speed)
_last_speed = current_speed
# Update altitude (Y position relative to ground level)
# Altitude telemetry
# Physics: Height measurement from reference point (y = 0)
var current_altitude = global_transform.origin.y
altitude_label.text = "Altitude: %.1f m" % current_altitude
if abs(current_altitude - _last_altitude) > ALTITUDE_THRESHOLD:
altitude_changed.emit(current_altitude)
_last_altitude = current_altitude
# Update angular velocity
# Angular velocity telemetry
# Physics: |ω| = √(ωₓ² + ωᵧ² + ωᵤ²) (magnitude of angular velocity vector)
var angular_speed = angular_velocity.length()
angular_vel_label.text = "Angular Vel: %.2f rad/s" % angular_speed
if abs(angular_speed - _last_angular_speed) > ANGULAR_THRESHOLD:
angular_velocity_changed.emit(angular_speed)
_last_angular_speed = angular_speed
# Update camera mode
var camera_mode = "Ball Cam" if ball_cam_enabled else "Ship Cam"
camera_mode_label.text = "Camera: %s" % camera_mode
# Camera mode telemetry (only emit when it actually changes)
if ball_cam_enabled != _last_camera_mode:
camera_mode_changed.emit(ball_cam_enabled)
_last_camera_mode = ball_cam_enabled
# Calculate attitude (pitch and roll from ship orientation)
# Attitude telemetry (pitch, roll, yaw from ship orientation)
# Physics: Euler angles from rotation matrix
# Pitch = rotation around X-axis, Roll = rotation around Z-axis
var ship_rotation = global_transform.basis.get_euler(EULER_ORDER_XYZ)
var pitch_deg = rad_to_deg(ship_rotation.x)
var roll_deg = rad_to_deg(ship_rotation.z)
attitude_label.text = "Pitch: %.0f° Roll: %.0f°" % [pitch_deg, roll_deg]
var roll_deg = rad_to_deg(ship_rotation.z)
var yaw_deg = rad_to_deg(ship_rotation.y)
# Calculate heading (yaw - direction ship is facing)
if abs(pitch_deg - _last_pitch) > ATTITUDE_THRESHOLD or \
abs(roll_deg - _last_roll) > ATTITUDE_THRESHOLD or \
abs(yaw_deg - _last_yaw) > ATTITUDE_THRESHOLD:
attitude_changed.emit(pitch_deg, roll_deg, yaw_deg)
_last_pitch = pitch_deg
_last_roll = roll_deg
_last_yaw = yaw_deg
# Heading telemetry (yaw - direction ship is facing)
# Physics: Yaw = rotation around Y-axis (compass heading)
# Convert to 0-360° range for traditional compass display
var yaw_deg = rad_to_deg(ship_rotation.y)
var heading = fmod(yaw_deg + 360.0, 360.0) # Normalize to 0-360°
heading_label.text = "Heading: %.0f°" % heading
if abs(heading - _last_heading) > ATTITUDE_THRESHOLD:
heading_changed.emit(heading)
_last_heading = heading
# Calculate current thrust percentage
# Thrust telemetry
# Physics: Thrust output as percentage of maximum available thrust
var thrust_input = get_thrust_input()
var thrust_magnitude = thrust_input.length()
var thrust_percent = thrust_magnitude * 100.0
thrust_label.text = "Thrust: %.0f%%" % thrust_percent
if abs(thrust_percent - _last_thrust) > THRUST_THRESHOLD:
thrust_changed.emit(thrust_percent)
_last_thrust = thrust_percent