diff --git a/Game/project.godot b/Game/project.godot index 55485a06..f4bae233 100644 --- a/Game/project.godot +++ b/Game/project.godot @@ -117,16 +117,4 @@ common/physics_interpolation=true [autoload] - - - - - - GameSettings="*res://scripts/game_settings.gd" - - - - - - diff --git a/Game/scenes/HUD.tscn b/Game/scenes/HUD.tscn index 9763a2d0..7198b3f5 100644 --- a/Game/scenes/HUD.tscn +++ b/Game/scenes/HUD.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=6 format=3 uid="uid://c8kak2l3m4n5"] +[gd_scene load_steps=7 format=3 uid="uid://c8kak2l3m4n5"] [ext_resource type="Script" uid="uid://du7y176h5aaq4" path="res://scripts/HUDController.gd" id="1_hud_controller"] [ext_resource type="Script" path="res://scripts/hud_attitude_indicator.gd" id="2_adi"] @@ -8,6 +8,9 @@ [sub_resource type="LabelSettings" id="LabelSettings_hud"] font_size = 32 +[sub_resource type="LabelSettings" id="LabelSettings_kickoff"] +font_size = 96 + [node name="HUD" type="CanvasLayer"] process_mode = 3 script = ExtResource("1_hud_controller") @@ -62,6 +65,24 @@ theme_override_colors/font_color = Color(1, 0.5, 0.15, 1) theme_override_font_sizes/font_size = 32 text = "0" +[node name="KickoffLabel" type="Label" parent="Control"] +visible = false +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -90.0 +offset_top = -60.0 +offset_right = 90.0 +offset_bottom = 60.0 +grow_horizontal = 2 +grow_vertical = 2 +label_settings = SubResource("LabelSettings_kickoff") +horizontal_alignment = 1 +vertical_alignment = 1 + [node name="ResultOverlay" type="Control" parent="Control"] visible = false layout_mode = 1 diff --git a/Game/scripts/HUDController.gd b/Game/scripts/HUDController.gd index 5b47bcdb..176621ee 100644 --- a/Game/scripts/HUDController.gd +++ b/Game/scripts/HUDController.gd @@ -9,6 +9,7 @@ class_name HUDController @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 kickoff_label = get_node_or_null("Control/KickoffLabel") @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") @@ -72,6 +73,9 @@ func _initialize_hud(): if game_manager and game_manager.has_signal("match_ended"): game_manager.match_ended.connect(_on_match_ended) + if game_manager and game_manager.has_signal("kickoff_countdown"): + game_manager.kickoff_countdown.connect(_on_kickoff_countdown) + func _connect_ship_signals(): # Route ship telemetry to the flight instruments if ship.has_signal("speed_changed"): @@ -127,6 +131,13 @@ func _on_score_changed(score: Dictionary): if team1_score_label and is_instance_valid(team1_score_label): team1_score_label.text = str(score.get(1, 0)) +func _on_kickoff_countdown(count: int): + if not kickoff_label or not is_instance_valid(kickoff_label): + return + kickoff_label.visible = count > 0 + if count > 0: + kickoff_label.text = str(count) + func _on_match_ended(winning_team: int, score: Dictionary): if not result_overlay or not is_instance_valid(result_overlay): return diff --git a/Game/scripts/match_mode.gd b/Game/scripts/match_mode.gd index cd1f0e58..b8cd8d21 100644 --- a/Game/scripts/match_mode.gd +++ b/Game/scripts/match_mode.gd @@ -9,9 +9,12 @@ signal timer_updated(minutes: int, seconds: int) signal score_changed(score: Dictionary) # winning_team is -1 for a draw. signal match_ended(winning_team: int, score: Dictionary) +# Counts 3, 2, 1, then 0 ("go", hide the label). +signal kickoff_countdown(count: int) # How long the result overlay stays up before returning to the menu. const RESULT_SCREEN_SECONDS := 5.0 +const KICKOFF_COUNTDOWN_SECONDS := 3 @export var match_length_seconds := 150.0 @@ -32,6 +35,8 @@ func _start() -> void: spawn_camera_rig(player_ship) spawn_ship(1, 0, _make_opponent_controller()) + await _run_kickoff_countdown() + match_timer = Timer.new() match_timer.one_shot = true match_timer.wait_time = match_length_seconds @@ -66,8 +71,34 @@ func _on_goal_scored(conceding_team: int) -> void: score[scoring_team] += 1 score_changed.emit(score.duplicate()) print("Goal for team %d! Score: %d - %d" % [scoring_team, score[0], score[1]]) + await _run_kickoff_countdown() + + +# Used both for the initial kickoff and after every goal. Freezes ball/ships +# via RigidBody3D.freeze — a built-in engine property — so this needs zero +# changes to ship.gd/ball.gd/ship_controller.gd, keeping training_mode.gd +# (which never calls into this file) completely untouched. +func _run_kickoff_countdown() -> void: reset_ball() reset_ships() + _set_frozen(true) + for count in range(KICKOFF_COUNTDOWN_SECONDS, 0, -1): + kickoff_countdown.emit(count) + # process_always=false: if full-time fires mid-countdown (see + # _on_match_timer_timeout's get_tree().paused = true), this stalls + # harmlessly in lockstep with the pause instead of ticking a + # countdown label over the results screen. + await get_tree().create_timer(1.0, false).timeout + kickoff_countdown.emit(0) + _set_frozen(false) + + +func _set_frozen(frozen: bool) -> void: + if is_instance_valid(ball): + ball.set_deferred("freeze", frozen) + for ship in ships: + if is_instance_valid(ship): + ship.set_deferred("freeze", frozen) func _on_match_timer_timeout() -> void: