From 82af809a6994dff7101a102f12721c59a3270084 Mon Sep 17 00:00:00 2001 From: florianvazelle Date: Sun, 11 Feb 2024 12:14:09 +0100 Subject: [PATCH] feat: add a podium at the end of the race (#50) * feat: add a podium at the end of the race * fix: update marble collision mask at each race * refactor: better oob detection * chore: add changelog entries --- CHANGELOG.md | 3 ++ project.godot | 4 +- scenes/main.tscn | 8 +++- scenes/podium.tscn | 95 ++++++++++++++++++++++++++++++++++++++++++ scripts/gui/ranking.gd | 25 +++++++---- scripts/main.gd | 54 +++++++++++++++++++++++- scripts/marble.gd | 21 +++++----- scripts/podium.gd | 19 +++++++++ 8 files changed, 206 insertions(+), 23 deletions(-) create mode 100644 scenes/podium.tscn create mode 100644 scripts/podium.gd diff --git a/CHANGELOG.md b/CHANGELOG.md index a1a9783..b897092 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,15 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added +- Add a podium at the end of the race ([#50](https://github.com/MechanicalFlower/Marble/pull/50)) ### Changed - Downgrade rendering settings of Godot ([#49](https://github.com/MechanicalFlower/Marble/pull/49)) - Refactor boost to define multiple power types ([#48](https://github.com/MechanicalFlower/Marble/pull/48)) +- Refactor the out of bound detection ([#50](https://github.com/MechanicalFlower/Marble/pull/50)) ### Deprecated ### Removed ### Fixed +- Update marble collision mask at each race ([#50](https://github.com/MechanicalFlower/Marble/pull/50)) ### Security ### Dependencies diff --git a/project.godot b/project.godot index 74fa2b5..03e9539 100644 --- a/project.godot +++ b/project.godot @@ -40,8 +40,8 @@ source/commit="41b766d42997fc9af1af4cb8680329bf02d0833f" [custom_options] -build_info/commit="3d878b4ec308bd6e91a7d10dc8734d0f65c005fb" -build_info/date="2024/02/10" +build_info/commit="8b5eab3456e7d73e84274c37eb9fd5ca14825420" +build_info/date="2024/02/11" [display] diff --git a/scenes/main.tscn b/scenes/main.tscn index 3d89f55..1b56a2e 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://bxmtpk5gebd06"] +[gd_scene load_steps=9 format=3 uid="uid://bxmtpk5gebd06"] [ext_resource type="Script" path="res://scripts/main.gd" id="1"] [ext_resource type="PackedScene" uid="uid://1ni00kklpf4b" path="res://scenes/gui/overlay.tscn" id="2"] @@ -6,6 +6,7 @@ [ext_resource type="PackedScene" uid="uid://4g5aorgnd1hu" path="res://scenes/marble.tscn" id="5"] [ext_resource type="PackedScene" uid="uid://do5uv5bqridi0" path="res://scenes/explosion.tscn" id="6"] [ext_resource type="PackedScene" uid="uid://bh0nd3c40wf75" path="res://scenes/gui/menu.tscn" id="7"] +[ext_resource type="PackedScene" uid="uid://butiacoqyc0um" path="res://scenes/podium.tscn" id="8_q3udf"] [ext_resource type="PackedScene" uid="uid://cr0ipqbbhw6fv" path="res://scenes/gui/countdown.tscn" id="9"] [node name="Main" type="Node"] @@ -78,6 +79,11 @@ visible = false [node name="Explosion" parent="." instance=ExtResource("6")] visible = false +[node name="Podium" parent="." instance=ExtResource("8_q3udf")] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10, 30, 0) +visible = false + [node name="Timer" type="Timer" parent="."] unique_name_in_owner = true wait_time = 10.0 diff --git a/scenes/podium.tscn b/scenes/podium.tscn new file mode 100644 index 0000000..e326261 --- /dev/null +++ b/scenes/podium.tscn @@ -0,0 +1,95 @@ +[gd_scene load_steps=11 format=3 uid="uid://butiacoqyc0um"] + +[ext_resource type="Script" path="res://scripts/podium.gd" id="1_62kyn"] + +[sub_resource type="BoxMesh" id="BoxMesh_j1v6k"] +size = Vector3(1, 1, 0.75) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_78627"] +diffuse_mode = 3 +specular_mode = 1 +albedo_color = Color(0.733333, 0.611765, 0.380392, 1) + +[sub_resource type="BoxShape3D" id="BoxShape3D_olhkp"] + +[sub_resource type="BoxMesh" id="BoxMesh_mf1on"] +size = Vector3(1, 0.75, 0.6) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_y6m2y"] +diffuse_mode = 3 +specular_mode = 2 +albedo_color = Color(0.690196, 0.670588, 0.654902, 1) + +[sub_resource type="BoxShape3D" id="BoxShape3D_es7k6"] +size = Vector3(1, 0.75, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_u4h8o"] +size = Vector3(1, 0.5, 0.5) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_a8m4f"] +diffuse_mode = 3 +specular_mode = 2 +albedo_color = Color(0.658824, 0.435294, 0.254902, 1) + +[sub_resource type="BoxShape3D" id="BoxShape3D_o3may"] +size = Vector3(1, 0.5, 1) + +[node name="Podium" type="Node3D"] +script = ExtResource("1_62kyn") + +[node name="FirstPlace" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) +mesh = SubResource("BoxMesh_j1v6k") +surface_material_override/0 = SubResource("StandardMaterial3D_78627") + +[node name="Marker3D" type="Marker3D" parent="FirstPlace"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0) + +[node name="StaticBody3D" type="StaticBody3D" parent="FirstPlace"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="FirstPlace/StaticBody3D"] +shape = SubResource("BoxShape3D_olhkp") + +[node name="Label3D" type="Label3D" parent="FirstPlace"] +transform = Transform3D(4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0.426261) +text = "1" + +[node name="SecondPlace" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0.375, 0) +mesh = SubResource("BoxMesh_mf1on") +surface_material_override/0 = SubResource("StandardMaterial3D_y6m2y") + +[node name="Marker3D" type="Marker3D" parent="SecondPlace"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.65, 0) + +[node name="StaticBody3D" type="StaticBody3D" parent="SecondPlace"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0.125, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="SecondPlace/StaticBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, -0.125, 0) +shape = SubResource("BoxShape3D_es7k6") + +[node name="Label3D" type="Label3D" parent="SecondPlace"] +transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0.025, 0.5) +text = "2 +" + +[node name="ThirdPlace" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0.25, 0) +mesh = SubResource("BoxMesh_u4h8o") +surface_material_override/0 = SubResource("StandardMaterial3D_a8m4f") + +[node name="Marker3D" type="Marker3D" parent="ThirdPlace"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0) + +[node name="StaticBody3D" type="StaticBody3D" parent="ThirdPlace"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0.25, 0) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="ThirdPlace/StaticBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, -0.250427, 0) +shape = SubResource("BoxShape3D_o3may") + +[node name="Label3D" type="Label3D" parent="ThirdPlace"] +transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0.5) +text = "3 +" diff --git a/scripts/gui/ranking.gd b/scripts/gui/ranking.gd index 90695ce..8ee792f 100644 --- a/scripts/gui/ranking.gd +++ b/scripts/gui/ranking.gd @@ -4,6 +4,8 @@ class_name Ranking extends VBoxContainer var _first_marble: Marble = null +var _second_marble: Marble = null +var _third_marble: Marble = null var _last_marble: Marble = null @@ -19,6 +21,10 @@ func update() -> void: if len(arr) > 0: _first_marble = arr[0].get_marble() + if len(arr) > 1: + _second_marble = arr[1].get_marble() + if len(arr) > 2: + _third_marble = arr[2].get_marble() var rank := 0 for child in arr: @@ -36,24 +42,27 @@ func more_checkpoint(a: Participant, b: Participant) -> bool: var b_marble = b.get_marble() var out: bool - if ( - a_marble.has_finish() and b_marble.has_finish() - or a_marble.get_checkpoint_count() == b_marble.get_checkpoint_count() - ): + if a_marble.has_finish() and b_marble.has_finish(): out = a.get_rank() < b.get_rank() elif a_marble.has_finish(): out = true elif b_marble.has_finish(): out = false else: - if a_marble.has_explode() and b_marble.has_explode(): + if ( + (a_marble.has_explode() or a_marble.has_oob()) + and (b_marble.has_explode() or b_marble.has_oob()) + ): out = a.get_rank() < b.get_rank() - elif a_marble.has_explode(): + elif a_marble.has_explode() or a_marble.has_oob(): out = false - elif b_marble.has_explode(): + elif b_marble.has_explode() or b_marble.has_oob(): out = true else: - out = a_marble.get_checkpoint_count() > b_marble.get_checkpoint_count() + if a_marble.get_checkpoint_count() == b_marble.get_checkpoint_count(): + out = a.get_rank() < b.get_rank() + else: + out = a_marble.get_checkpoint_count() > b_marble.get_checkpoint_count() return out diff --git a/scripts/main.gd b/scripts/main.gd index 7af0729..dbda8a4 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -17,6 +17,7 @@ var _current_marble_index := 0 var _time := 0.0 var _explosion_enabled := false var _race_has_started := false +var _lower_boundary = null # Variables used in explosion mode to check # if we need to generate another chunk of the race @@ -38,6 +39,7 @@ var _positions := [] @onready var _panel_timer := _overlay.get_node(^"Panel2") as ColorRect @onready var _label_timer = _overlay.get_node(^"Panel2/CenterContainer3/VBoxContainer/LabelTimer") @onready var _countdown := get_node(^"%Countdown") +@onready var _podium := get_node(^"%Podium") func _ready() -> void: @@ -104,6 +106,7 @@ func _unhandled_input(event): KEY_R: if _mode == State.MODE_MARBLE: _race.generate_race(!_explosion_enabled) + _lower_boundary = get_lowest_piece(_race, true).global_transform.origin.y KEY_SPACE: for marble in _marbles: @@ -156,13 +159,39 @@ func get_highest_piece() -> Piece: var pieces = _race.get_children() if len(pieces) == 0: return null - var highest_piece = pieces[0] + var highest_piece = null for piece in pieces: - if piece.position.y > highest_piece.position.y: + if not piece is Piece: + continue + if highest_piece == null: + highest_piece = piece + elif piece.position.y > highest_piece.position.y: highest_piece = piece return highest_piece +func get_lowest_piece(piece_or_race, recursive: bool = false) -> Piece: + var pieces = piece_or_race.get_children() + if len(pieces) == 0: + return null + + # Find the lower piece from children of the current node3d + var lowest_piece = null + for piece in pieces: + if not piece is Piece: + continue + if lowest_piece == null: + lowest_piece = piece + elif piece.position.y < lowest_piece.position.y: + lowest_piece = piece + + if recursive: + var child_lowest_piece = get_lowest_piece(lowest_piece) + if child_lowest_piece != null: + lowest_piece = child_lowest_piece + return lowest_piece + + # Replace cameras with a new one func replace_camera(new_camera, old_cameras) -> void: # Ensure old cameras are removed from the current scene @@ -210,10 +239,13 @@ func set_mode(mode): if _mode == State.MODE_MARBLE: # If no marbles exist if start_a_new_race: + _podium.hide() + await Fade.fade_out(1, Color.BLACK, "Diamond", false, false).finished _explosion_enabled = SettingsManager.get_value(&"marbles", &"explosion_enabled") as bool _race.generate_race(!_explosion_enabled) + _lower_boundary = get_lowest_piece(_race, true).global_transform.origin.y _overlay.reset() reset_position() @@ -313,6 +345,7 @@ func _process(delta): if lap_count > _old_lap_count: # Generate a chunk _race.generate_chunk() + _lower_boundary = get_lowest_piece(_race, true).global_transform.origin.y _old_lap_count = lap_count else: _panel_timer.hide() @@ -334,6 +367,23 @@ func _process(delta): if not found: replace_camera(_rotation_camera, [_marble_camera]) + if _race_has_started: + _race_has_started = false + _podium.show() + _podium.set_first(_ranking._first_marble) + _podium.set_second(_ranking._second_marble) + _podium.set_third(_ranking._third_marble) + + # Check if some marbles are out of bound + if _race_has_started and _lower_boundary != null: + for marble in _marbles: + if ( + marble.visible + and marble._state == Marble.State.ROLL + and marble.global_transform.origin.y + 100 < _lower_boundary + ): + marble.out_of_bound() + # Handle victory conditions on explosion mode func explosion_victory(_last_marble: Marble) -> bool: diff --git a/scripts/marble.gd b/scripts/marble.gd index af1e656..e17f55f 100644 --- a/scripts/marble.gd +++ b/scripts/marble.gd @@ -32,13 +32,6 @@ func _ready() -> void: x_ray_material.set_next_pass(toon_material) _ball_mesh.set_surface_override_material(0, x_ray_material) - # Set collision mask - var collision_enabled = SettingsManager.get_value(&"marbles", &"collision_enabled") as bool - if collision_enabled: - collision_mask = 1 << CollisionLayers.PROPS | 1 << CollisionLayers.MARBLES - else: - collision_mask = 1 << CollisionLayers.PROPS - pause() @@ -93,9 +86,6 @@ func _process(_delta: float) -> void: _name.global_transform.origin = offset _score.global_transform.origin = offset + Vector3(-0.4, -0.2, 0) - if global_transform.origin.y < -10000: - out_of_bound() - func reset() -> void: _start() @@ -134,6 +124,16 @@ func _start() -> void: set_physics_process(true) set_sleeping(false) set_linear_velocity(Vector3.ZERO) + set_inertia(Vector3.ZERO) + set_freeze_enabled(false) + + # Set collision mask + var collision_enabled = SettingsManager.get_value(&"marbles", &"collision_enabled") as bool + if collision_enabled: + collision_mask = 1 << CollisionLayers.PROPS | 1 << CollisionLayers.MARBLES + else: + collision_mask = 1 << CollisionLayers.PROPS + collision_layer = 1 << CollisionLayers.MARBLES @@ -143,4 +143,5 @@ func _stop() -> void: set_physics_process(false) set_sleeping(true) set_linear_velocity(Vector3.ZERO) + set_freeze_enabled(true) collision_layer = 0 diff --git a/scripts/podium.gd b/scripts/podium.gd new file mode 100644 index 0000000..e726706 --- /dev/null +++ b/scripts/podium.gd @@ -0,0 +1,19 @@ +extends Node3D + + +func set_first(marble: Marble): + if marble: + marble.global_position = get_node(^"FirstPlace/Marker3D").global_position + marble.visible = true + + +func set_second(marble: Marble): + if marble: + marble.global_position = get_node(^"SecondPlace/Marker3D").global_position + marble.visible = true + + +func set_third(marble: Marble): + if marble: + marble.global_position = get_node(^"ThirdPlace/Marker3D").global_position + marble.visible = true