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
+2 -70
View File
@@ -1,6 +1,7 @@
[gd_scene load_steps=5 format=3 uid="uid://p07epxnh8wwp"]
[ext_resource type="Script" uid="uid://dyq1n7q1bqjps" path="res://scripts/ship.gd" id="1_efag7"]
[ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="2_hud_scene"]
[sub_resource type="BoxMesh" id="BoxMesh_efag7"]
size = Vector3(1, 1, 4)
@@ -8,9 +9,6 @@ size = Vector3(1, 1, 4)
[sub_resource type="BoxShape3D" id="BoxShape3D_dsjou"]
size = Vector3(1, 1, 4)
[sub_resource type="LabelSettings" id="LabelSettings_efag7"]
font_size = 32
[node name="Ship" type="RigidBody3D"]
mass = 10.0
gravity_scale = 10.0
@@ -25,70 +23,4 @@ shape = SubResource("BoxShape3D_dsjou")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.867366, 0.497671, 0, -0.497671, 0.867366, 0, 2.04336, 3.31556)
[node name="HUD" type="CanvasLayer" parent="."]
[node name="Control" type="Control" parent="HUD"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TimerLabel" type="Label" parent="HUD/Control"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -90.5
offset_right = 90.5
offset_bottom = 62.0
grow_horizontal = 2
label_settings = SubResource("LabelSettings_efag7")
horizontal_alignment = 1
[node name="InfoPanel" type="VBoxContainer" parent="HUD/Control"]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -200.0
offset_right = 300.0
offset_bottom = -10.0
grow_vertical = 0
[node name="SpeedLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Speed: 0.0 m/s"
label_settings = SubResource("LabelSettings_efag7")
[node name="AltitudeLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Altitude: 0.0 m"
label_settings = SubResource("LabelSettings_efag7")
[node name="AngularVelLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Angular Vel: 0.0 rad/s"
label_settings = SubResource("LabelSettings_efag7")
[node name="CameraModeLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Camera: Ball Cam"
label_settings = SubResource("LabelSettings_efag7")
[node name="AttitudeLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Pitch: 0° Roll: 0°"
label_settings = SubResource("LabelSettings_efag7")
[node name="HeadingLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Heading: 0°"
label_settings = SubResource("LabelSettings_efag7")
[node name="ThrustLabel" type="Label" parent="HUD/Control/InfoPanel"]
layout_mode = 2
text = "Thrust: 0%"
label_settings = SubResource("LabelSettings_efag7")
[node name="HUD" parent="." instance=ExtResource("2_hud_scene")]
+1
View File
@@ -47,5 +47,6 @@ lock_rotation = true
[node name="Ball" parent="." instance=ExtResource("5_iywne")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.464103, 1.46401, -2.82393)
mass = 3.0
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
+83
View File
@@ -0,0 +1,83 @@
[gd_scene load_steps=3 format=3 uid="uid://c8kak2l3m4n5"]
[ext_resource type="Script" uid="uid://bx9j8k7l6m5n" path="res://scripts/HUDController.gd" id="1_hud_controller"]
[sub_resource type="LabelSettings" id="LabelSettings_hud"]
font_size = 32
[node name="HUD" type="CanvasLayer"]
script = ExtResource("1_hud_controller")
[node name="Control" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TimerLabel" type="Label" parent="Control"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -90.5
offset_right = 90.5
offset_bottom = 62.0
grow_horizontal = 2
label_settings = SubResource("LabelSettings_hud")
horizontal_alignment = 1
[node name="Instruments" type="Control" parent="Control"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="InfoPanel" type="VBoxContainer" parent="Control"]
layout_mode = 1
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -200.0
offset_right = 300.0
offset_bottom = -10.0
grow_vertical = 0
[node name="SpeedLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Speed: 0.0 m/s"
label_settings = SubResource("LabelSettings_hud")
[node name="AltitudeLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Altitude: 0.0 m"
label_settings = SubResource("LabelSettings_hud")
[node name="AngularVelLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Angular Vel: 0.0 rad/s"
label_settings = SubResource("LabelSettings_hud")
[node name="CameraModeLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Camera: Ball Cam"
label_settings = SubResource("LabelSettings_hud")
[node name="AttitudeLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Pitch: 0° Roll: 0°"
label_settings = SubResource("LabelSettings_hud")
[node name="HeadingLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Heading: 0°"
label_settings = SubResource("LabelSettings_hud")
[node name="ThrustLabel" type="Label" parent="Control/InfoPanel"]
layout_mode = 2
text = "Thrust: 0%"
label_settings = SubResource("LabelSettings_hud")
+1
View File
@@ -51,6 +51,7 @@ shape = SubResource("BoxShape3D_133xi")
[node name="Ball" parent="." instance=ExtResource("2_mv342")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.08165e-12, 2, 2.08165e-12)
mass = 3.0
[node name="Players" type="Node3D" parent="."]
+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