diff --git a/Game/objects/ship.tscn b/Game/objects/ship.tscn index 5fcb33e..902eaea 100644 --- a/Game/objects/ship.tscn +++ b/Game/objects/ship.tscn @@ -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")] diff --git a/Game/scenes/Game.tscn b/Game/scenes/Game.tscn index 7e4fa0a..84b7a4e 100644 --- a/Game/scenes/Game.tscn +++ b/Game/scenes/Game.tscn @@ -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"] diff --git a/Game/scenes/HUD.tscn b/Game/scenes/HUD.tscn new file mode 100644 index 0000000..0a0fbeb --- /dev/null +++ b/Game/scenes/HUD.tscn @@ -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") diff --git a/Game/scenes/match.tscn b/Game/scenes/match.tscn index 4adee70..509c162 100644 --- a/Game/scenes/match.tscn +++ b/Game/scenes/match.tscn @@ -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="."] diff --git a/Game/scripts/HUDController.gd b/Game/scripts/HUDController.gd new file mode 100644 index 0000000..f6375b8 --- /dev/null +++ b/Game/scripts/HUDController.gd @@ -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 diff --git a/Game/scripts/HUDController.gd.uid b/Game/scripts/HUDController.gd.uid new file mode 100644 index 0000000..ce312f0 --- /dev/null +++ b/Game/scripts/HUDController.gd.uid @@ -0,0 +1 @@ +uid://du7y176h5aaq4 diff --git a/Game/scripts/game.gd b/Game/scripts/game.gd index d9f262c..c3484c4 100644 --- a/Game/scripts/game.gd +++ b/Game/scripts/game.gd @@ -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 diff --git a/Game/scripts/ship.gd b/Game/scripts/ship.gd index 5dba3b4..4350904 100644 --- a/Game/scripts/ship.gd +++ b/Game/scripts/ship.gd @@ -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