Compare commits

...

6 commits

Author SHA1 Message Date
2f646169fb
initial ring map support (#2) 2025-05-04 00:36:12 +02:00
edf99640cf
Fix memory leak and save file deletion + other stuff
might also be the commit where timer starts only when you move
2025-05-03 14:33:49 +02:00
709ce8eea3
split gravity logic, various bug fixes
fixed gravity strength being possibly strong at level beginning
fixed restarting being allowed after getting the last ring
fixed save file's "played_for" being reset or set to another file's
fixed the displayed save file total play time not being updated on main menu

maybe there's other stuff
2025-04-29 21:18:04 +02:00
38897c706a
"save" & animate main menu, fix pause menu & restart (#3)
pause menu had classic issues of mouse inputs being eaten by something else (gravity)
restart didn't have working code anymore because of level logic changes
also remove the demo scene which I believe is unused beyond the jam version
2025-04-27 21:30:03 +02:00
71441a9335
show level name, best time, and thumbnail (#3)
now thumbnails need to be made
2025-04-25 16:03:36 +02:00
59bed24acd
simple level selection menu, change level logic (#3) 2025-04-24 14:54:54 +02:00
22 changed files with 870 additions and 452 deletions

View file

@ -4,31 +4,38 @@
script/source = "class_name Player
extends Node3D
var velocity: Vector3 = Vector3(0, 0, 0)
signal velocity_change
var velocity := 0.0:
get: return velocity
set(value):
if value != velocity:
velocity_change.emit(value)
velocity = value
@onready var ball_position: Vector3 = $Sphere.position
func _physics_process(_delta: float) -> void:
ball_position = $Sphere.position
$SpotLightMain.position = $Sphere.position + Vector3(0, 5, 0)
$SpotLightTop.position = $Sphere.position
$SpotLightLeft.position = $Sphere.position
$SpotLightRight.position = $Sphere.position
$SpotLightBottom.position = $Sphere.position
## Keep the ball at the center of the camera, at a distance
# Keep the ball at the center of the camera, at a distance
$Camera.position = $Sphere.position + Vector3(0, 0, 15)
## Angle of the camera, so the player can see where the ball is going
velocity = $Sphere.linear_velocity
var desired_x = max(min(velocity.y, 35), -35)
var desired_y = max(min(-velocity.x, 50), -50)
# Angle of the camera, so the player can see where the ball is going
velocity = abs($Sphere.linear_velocity.x) + abs($Sphere.linear_velocity.y)
var desired_x = max(min($Sphere.linear_velocity.y, 35), -35)
var desired_y = max(min(-$Sphere.linear_velocity.x, 50), -50)
var difference_x = $Camera.rotation_degrees.x - desired_x
var difference_y = $Camera.rotation_degrees.y - desired_y
$Camera.rotation_degrees.x -= max(min(difference_x / 2, 3), -3)
$Camera.rotation_degrees.y -= max(min(difference_y / 2, 3), -3)
## FOV of the camera, so it unzooms more at higher velocities
# FOV of the camera, so it unzooms more at higher velocities
const default_fov = 75
var total_velocity = abs(velocity.x) + abs(velocity.y)
$Camera.fov = default_fov + (total_velocity / 12)
$Camera.fov = default_fov + (velocity / 12)
"
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_vumbr"]

View file

@ -29,6 +29,7 @@ vertex_color_use_as_albedo = true
albedo_color = Color(0.212217, 0.468618, 0, 1)
[node name="Tree" type="Node3D"]
physics_interpolation_mode = 2
script = SubResource("GDScript_tbkod")
[node name="Dirt" type="CSGBox3D" parent="."]

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=8 format=3 uid="uid://dw0xl8644x166"]
[gd_scene load_steps=7 format=3 uid="uid://dw0xl8644x166"]
[ext_resource type="Texture2D" uid="uid://ctjugvy1v3y6b" path="res://graphics/arrow.svg" id="1_4mbho"]
[ext_resource type="AudioStream" uid="uid://dmdbharecy448" path="res://sounds/gravity.ogg" id="2_5uwlw"]
@ -6,66 +6,35 @@
[sub_resource type="GDScript" id="GDScript_3yq1l"]
script/source = "extends Control
var player_ready = false
const normal = 7
const stronger = normal * 3
func _input(_event: InputEvent) -> void:
if player_ready:
var grav = stronger if Input.is_action_pressed(\"gravity_strong\") else normal
PhysicsServer3D.area_set_param(get_viewport().find_world_3d().space, PhysicsServer3D.AREA_PARAM_GRAVITY, grav)
"
[sub_resource type="GDScript" id="GDScript_my602"]
script/source = "extends TextureRect
@onready var direction_node: TextureRect = $Direction
@onready var animation: AnimationPlayer = $Direction/AnimationPlayer
@onready var audio: AudioStreamPlayer = $Direction/AudioStreamPlayer
func _ready() -> void:
# set the indicator to be fully transparent when it first spawns
self.self_modulate = Color(1, 1, 1, 0)
direction_node.self_modulate = Color(1, 1, 1, 0)
func _input(ev: InputEvent) -> void:
if ev is InputEventKey and ev.is_pressed():
var up := Input.is_action_just_pressed(\"gravity_up\")
var left := Input.is_action_just_pressed(\"gravity_left\")
var right := Input.is_action_just_pressed(\"gravity_right\")
var down := Input.is_action_just_pressed(\"gravity_down\")
if up or left or right or down:
var direction = Vector3()
if up:
direction = Vector3.UP
self.rotation_degrees = -90
$AudioStreamPlayer.pitch_scale = 1.05
elif left:
direction = Vector3.LEFT
self.rotation_degrees = -180
$AudioStreamPlayer.pitch_scale = 1.02
elif right:
func react_to_gravity_change(direction: Vector3) -> void:
if direction == Vector3.UP:
direction_node.rotation_degrees = -90
audio.pitch_scale = 1.05
elif direction == Vector3.LEFT:
direction_node.rotation_degrees = -180
audio.pitch_scale = 1.02
elif direction == Vector3.RIGHT:
direction = Vector3.RIGHT
self.rotation_degrees = 0
$AudioStreamPlayer.pitch_scale = 0.98
elif down:
direction = Vector3.DOWN
self.rotation_degrees = 90
$AudioStreamPlayer.pitch_scale = 0.95
direction_node.rotation_degrees = 0
audio.pitch_scale = 0.98
elif direction == Vector3.DOWN:
direction_node.rotation_degrees = 90
audio.pitch_scale = 0.95
if get_parent().player_ready == false or direction != PhysicsServer3D.area_get_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR
):
get_parent().player_ready = true
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR,
direction
)
if $AnimationPlayer.is_playing():
$AnimationPlayer.stop()
$AnimationPlayer.play(\"grow_fadeout\")
if animation.is_playing():
animation.stop()
animation.play(\"grow_fadeout\")
if Settings.sound_on_gravity_change:
$AudioStreamPlayer.play()
audio.play()
"
[sub_resource type="Animation" id="Animation_6rwl4"]
@ -137,6 +106,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = SubResource("GDScript_3yq1l")
[node name="Direction" type="TextureRect" parent="."]
@ -155,8 +125,8 @@ grow_horizontal = 2
grow_vertical = 2
scale = Vector2(0.3, 0.3)
pivot_offset = Vector2(400, 400)
mouse_filter = 2
texture = ExtResource("1_4mbho")
script = SubResource("GDScript_my602")
[node name="AnimationPlayer" type="AnimationPlayer" parent="Direction"]
libraries = {

66
gui/map.tscn Normal file
View file

@ -0,0 +1,66 @@
[gd_scene load_steps=2 format=3 uid="uid://bcxbw6wd54ksv"]
[sub_resource type="GDScript" id="GDScript_r4thc"]
script/source = "extends Control
func _ready() -> void:
$TemplateRing.hide()
func display_rings(player: Node3D, rings: Array[Ring]) -> void:
var old_rings_display := $Rings.get_children()
var difference := len(rings) - len(old_rings_display)
# Too many displayed, free them
for i in -difference:
old_rings_display[-(1 + i)].queue_free()
# Not enough displayed, create some
for i in difference:
var new_child := $TemplateRing.duplicate()
new_child.name = \"Ring\" + str(i + 1)
new_child.show()
$Rings.add_child(new_child)
var rings_display := $Rings.get_children()
var center: Vector2i = get_viewport().size / 2
for i in len(rings):
rings_display[i].color = rings[i].material.albedo_color
rings_display[i].rotation = rings[i].rotation.z
rings_display[i].position.x = center.x + rings[i].position.x - player.ball_position.x
rings_display[i].position.y = center.y - rings[i].position.y + player.ball_position.y
if rings[i].collected:
rings_display[i].scale = $TemplateRing.scale * 2 * min(4, max(1, rings[i].scale.x))
else:
rings_display[i].scale = $TemplateRing.scale * 3 * min(4, max(1, rings[i].scale.x))
"
[node name="Map" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_r4thc")
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(1, 0, 1, 0.0392157)
[node name="Rings" type="Control" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="TemplateRing" type="ColorRect" parent="."]
layout_mode = 0
offset_right = 2.0
offset_bottom = 2.0

View file

@ -8,13 +8,9 @@ script/source = "extends VBoxContainer
var enabled := false:
get: return enabled
set(value):
if enabled == value:
return
enabled = value
if value == true:
var save_file = SaveFiles.read(SaveFiles.selected_file)
if save_file.has(\"played_for\") and save_file.played_for is float:
seconds_spent_total = save_file.played_for
else:
seconds_spent_total = 0.0
if value == false:
$AnimationPlayer.play(\"scale_linear\")
await get_tree().create_timer($AnimationPlayer.current_animation_length).timeout
@ -31,10 +27,13 @@ var seconds_spent_level_attempt := 0.0:
seconds_spent_level_attempt = value
$Level.text = \"Level: \" + seconds_to_readable(seconds_spent_level_attempt)
func _ready():
var data = SaveFiles.read(SaveFiles.selected_file)
if data.has(\"played_for\") and data.played_for is float:
seconds_spent_total = data.played_for
func _on_visibility_changed() -> void:
if self.visible and len(SaveFiles.selected_file):
var save_file = SaveFiles.read(SaveFiles.selected_file)
if save_file.has(\"played_for\") and save_file.played_for is float:
seconds_spent_total = save_file.played_for
else:
seconds_spent_total = 0.0
func seconds_to_readable(seconds: float) -> String:
var minutes: int = floor(seconds / 60)
@ -77,23 +76,6 @@ tracks/0/keys = {
"values": [Vector2(1, 1)]
}
[sub_resource type="Animation" id="Animation_2a86r"]
resource_name = "scale_linear"
length = 0.25
step = 0.25
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.25),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector2(1, 1), Vector2(1.2, 1.2)]
}
[sub_resource type="Animation" id="Animation_houb3"]
resource_name = "scale_cubic"
length = 0.25
@ -111,6 +93,23 @@ tracks/0/keys = {
"values": [Vector2(1, 1), Vector2(1.2, 1.2)]
}
[sub_resource type="Animation" id="Animation_2a86r"]
resource_name = "scale_linear"
length = 0.25
step = 0.25
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.25),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector2(1, 1), Vector2(1.2, 1.2)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_jmgpn"]
_data = {
&"RESET": SubResource("Animation_m1tld"),
@ -120,27 +119,24 @@ _data = {
[node name="Timer" type="VBoxContainer"]
process_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -992.0
offset_bottom = -563.0
grow_horizontal = 2
grow_vertical = 2
offset_right = 315.0
offset_bottom = 85.0
theme_override_constants/separation = -5
script = SubResource("GDScript_q235s")
[node name="Total" type="Label" parent="."]
layout_mode = 2
text = "Total timer"
text = "Total: 0:00.000"
label_settings = SubResource("LabelSettings_m1tld")
[node name="Level" type="Label" parent="."]
layout_mode = 2
text = "Level timer"
text = "Level: 0:00.000"
label_settings = SubResource("LabelSettings_2a86r")
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = {
&"": SubResource("AnimationLibrary_jmgpn")
}
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]

View file

@ -1,24 +1,26 @@
[gd_scene load_steps=7 format=3 uid="uid://ccgnnif026wb4"]
[gd_scene load_steps=8 format=3 uid="uid://ccgnnif026wb4"]
[ext_resource type="PackedScene" uid="uid://xd3nsiglcdfc" path="res://gui/timer.tscn" id="1_356j3"]
[ext_resource type="PackedScene" uid="uid://dw0xl8644x166" path="res://gui/gravity.tscn" id="1_ir7so"]
[ext_resource type="PackedScene" uid="uid://cckeamgkt8bqo" path="res://gui/speed.tscn" id="2_2gn6w"]
[ext_resource type="PackedScene" uid="uid://bcxbw6wd54ksv" path="res://gui/map.tscn" id="2_d1yoc"]
[ext_resource type="PackedScene" uid="uid://cn55m5dqo3m6u" path="res://gui/rings.tscn" id="3_mbj17"]
[ext_resource type="PackedScene" uid="uid://dkxtwpcy4moyo" path="res://menus/pause_menu.tscn" id="4_3bfj3"]
[sub_resource type="GDScript" id="GDScript_8n212"]
script/source = "extends Node
var playing: bool = false
var changing_level: bool = false
var playing := false
var changing_level := false
var current_level_int: int = 0
var levels = [
preload(\"res://levels/base/level.tscn\"),
preload(\"res://levels/forest/level.tscn\"),
preload(\"res://levels/night/level.tscn\"),
]
var area_resource = preload(\"res://menus/main/area.tscn\")
var main_menu: Node
var current_level: Level
var current_level_scene: PackedScene
const res_main_menu = preload(\"res://menus/main/main_menu.tscn\")
@onready var gui_gravity := $GUI/Gravity
@onready var gui_map := $GUI/Map
@onready var gui_timer := $GUI/TopLeft/Timer
@onready var gui_speed := $GUI/TopRight/Speed
@onready var gui_rings := $GUI/BottomRight/Rings
@ -31,8 +33,6 @@ func _ready() -> void:
$DevInfos.text += \"build \" + ProjectSettings.get_setting(\"application/config/version\") + \" (\" + OS.get_name() + \")\"
## Hide UI stuff that shouldn't be visible until later in the game
$VictoryScreen.hide()
$GUI.hide()
pause_menu.hide()
## Connect to necessary signals
@ -40,107 +40,84 @@ func _ready() -> void:
pause_menu.connect(\"request_fullscreen\", fullscreen_game)
pause_menu.connect(\"request_restart\", restart_level)
launch_area()
set_main_menu()
func launch_area():
func set_main_menu() -> void:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
var area := area_resource.instantiate()
area.connect(\"request_start\", start_game)
$Levels.add_child(area)
$GUI.hide()
## Get all the levels that are currently being played!
## In theory, there should ever be only zero or one,
## but it turns out it's more simple to handle things by using Arrays.
func get_current_levels() -> Array[Level]:
var children := $Levels.get_children(true)
var current_levels: Array[Level] = []
for child in children:
if is_instance_of(child, Level):
current_levels.push_back(child)
return current_levels
## If no main menu exists yet, create one
if !is_instance_valid(main_menu):
main_menu = res_main_menu.instantiate()
main_menu.connect(\"request_play_level\", start_level)
add_child(main_menu)
func start_level(level_scene: PackedScene) -> void:
$VictoryScreen.hide()
var level = level_scene.instantiate()
$Levels.add_child(level)
level.connect(\"completed\", stop_level)
level.connect(\"ring_collected\", func():
gui_rings.remaining_rings = len(level.rings) - level.finished_rings_count
func start_level(level: Level, scene: PackedScene) -> void:
if is_instance_valid(current_level):
current_level.queue_free()
if is_instance_valid(main_menu) and self.is_ancestor_of(main_menu):
self.remove_child(main_menu)
current_level = level
current_level_scene = scene
add_child(current_level)
current_level.connect(\"started_playing\", func(): gui_timer.enabled = true)
current_level.connect(\"gravity_change\", gui_gravity.react_to_gravity_change)
current_level.connect(\"completed\", finish_current_level)
current_level.connect(\"ring_collected\", func():
gui_rings.remaining_rings = len(current_level.rings) - current_level.finished_rings_count
)
gui_rings.remaining_rings = len(level.rings) - level.finished_rings_count
gui_rings.remaining_rings = len(current_level.rings) - current_level.finished_rings_count
$GUI.show()
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
pause_game(false)
playing = true
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR,
Vector3.DOWN
)
changing_level = false
gui_timer.seconds_spent_level_attempt = 0.0
gui_timer.enabled = true
func stop_level() -> void:
func finish_current_level() -> void:
gui_timer.enabled = false
PhysicsServer3D.area_set_param(get_viewport().find_world_3d().space, PhysicsServer3D.AREA_PARAM_GRAVITY, 1)
PhysicsServer3D.area_set_param(get_viewport().find_world_3d().space, PhysicsServer3D.AREA_PARAM_GRAVITY, 0.5)
playing = false
changing_level = true
SaveFiles.change_property(\"played_for\", gui_timer.seconds_spent_total, SaveFiles.selected_file)
var save_file_data = SaveFiles.read(SaveFiles.selected_file)
var property_name := \"best_time_for_level_\" + str(current_level_int)
var property_name := current_level.id + \"_best_time\"
if !save_file_data.has(property_name) or save_file_data[property_name] is not float or save_file_data[property_name] > gui_timer.seconds_spent_level_attempt:
SaveFiles.change_property(property_name, gui_timer.seconds_spent_level_attempt, SaveFiles.selected_file)
var current_levels = get_current_levels()
for level in current_levels:
level.music.fadeOut(2)
current_level.music.fadeOut(2)
await get_tree().create_timer(2).timeout
level.queue_free()
current_level.queue_free()
if len(levels) > current_level_int + 1:
current_level_int += 1
start_level(levels[current_level_int])
else:
win_game()
func win_game() -> void:
$VictoryScreen.show()
await get_tree().create_timer(2).timeout
launch_area()
$VictoryScreen.hide()
set_main_menu()
func restart_level() -> void:
var current_levels := get_current_levels()
if len(current_levels) && !changing_level:
pause_game(false)
changing_level = true
for level in current_levels:
level.queue_free()
start_level(levels[current_level_int])
func start_game() -> void:
current_level_int = 0
var current_levels = $Levels.get_children(true)
for level in current_levels:
level.queue_free()
$GUI.show()
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
start_level(levels[current_level_int])
gui_timer.enabled = false
if !current_level or changing_level:
return
if current_level_scene is not PackedScene:
print(\"Tried to restart the level despite not having the level scene\")
return
SaveFiles.change_property(\"played_for\", gui_timer.seconds_spent_total, SaveFiles.selected_file)
start_level(current_level_scene.instantiate(), current_level_scene)
func pause_game(to_pause: bool) -> void:
if !len(get_current_levels()) or changing_level:
if !is_instance_valid(current_level) or changing_level:
return
SaveFiles.change_property(\"played_for\", gui_timer.seconds_spent_total, SaveFiles.selected_file)
if to_pause:
$Levels.process_mode = Node.PROCESS_MODE_DISABLED
SaveFiles.change_property(\"played_for\", gui_timer.seconds_spent_total, SaveFiles.selected_file)
current_level.process_mode = Node.PROCESS_MODE_DISABLED
playing = false
pause_menu.show()
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
$Levels.process_mode = Node.PROCESS_MODE_INHERIT
current_level.process_mode = Node.PROCESS_MODE_INHERIT
playing = true
pause_menu.hide()
Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
@ -153,12 +130,16 @@ func fullscreen_game() -> void:
DisplayServer.window_set_mode(DisplayServer.WindowMode.WINDOW_MODE_WINDOWED)
func _process(_delta: float) -> void:
var current_levels = get_current_levels()
if len(current_levels):
var current_level = current_levels[0]
if is_instance_valid(current_level):
gui_speed.ball_velocity = current_level.velocity
if Input.is_action_pressed(\"display_map\"):
gui_map.display_rings(current_level.player, current_level.rings)
func _input(_event: InputEvent) -> void:
if Input.is_action_just_released(\"display_map\"):
gui_map.hide()
if Input.is_action_just_pressed(\"display_map\"):
gui_map.show()
if Input.is_action_just_pressed(\"restart_level\"):
restart_level()
@ -166,11 +147,6 @@ func _on_btn_quit_pressed() -> void:
get_tree().quit()
"
[sub_resource type="LabelSettings" id="LabelSettings_1bs00"]
font_size = 160
outline_size = 20
outline_color = Color(0, 0, 0, 1)
[node name="Game" type="Node"]
script = SubResource("GDScript_8n212")
@ -181,7 +157,14 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
mouse_filter = 2
[node name="Gravity" parent="GUI" instance=ExtResource("1_ir7so")]
layout_mode = 1
[node name="Map" parent="GUI" instance=ExtResource("2_d1yoc")]
visible = false
layout_mode = 1
[node name="TopLeft" type="MarginContainer" parent="GUI"]
layout_mode = 1
@ -226,30 +209,6 @@ theme_override_constants/margin_bottom = 15
[node name="Rings" parent="GUI/BottomRight" instance=ExtResource("3_mbj17")]
layout_mode = 2
[node name="VictoryScreen" 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="Label" type="Label" parent="VictoryScreen"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "YOU WON!"
label_settings = SubResource("LabelSettings_1bs00")
horizontal_alignment = 1
vertical_alignment = 1
[node name="Levels" type="Node" parent="."]
[node name="PauseMenu" parent="." instance=ExtResource("4_3bfj3")]
[node name="DevInfos" type="Label" parent="."]
anchors_preset = 12
anchor_top = 1.0
@ -259,3 +218,5 @@ offset_top = -23.0
grow_horizontal = 2
grow_vertical = 0
horizontal_alignment = 1
[node name="PauseMenu" parent="." instance=ExtResource("4_3bfj3")]

View file

@ -1,12 +1,21 @@
[gd_scene load_steps=12 format=3 uid="uid://ovtknjyj83gh"]
[gd_scene load_steps=11 format=3 uid="uid://ovtknjyj83gh"]
[ext_resource type="Script" uid="uid://w3fetao1pegm" path="res://levels/level.gd" id="1_rj40i"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="2_b00jj"]
[ext_resource type="PackedScene" uid="uid://cpm3laywhlbq5" path="res://elements/ring.tscn" id="3_hel5x"]
[ext_resource type="PackedScene" uid="uid://dw0xl8644x166" path="res://elements/gravity.tscn" id="3_muudg"]
[ext_resource type="PackedScene" uid="uid://c77bli40240nk" path="res://elements/sign.tscn" id="4_atq6y"]
[ext_resource type="PackedScene" uid="uid://dnuakh7n3fuij" path="res://levels/base/music.tscn" id="4_uq42r"]
[sub_resource type="GDScript" id="GDScript_1yugx"]
script/source = "extends Level
func _init() -> void:
self.id = \"base\"
func _on_player_velocity_change(new_velocity: float) -> void:
self.velocity = new_velocity
self.music.adaptInstrumentsToVelocity(new_velocity * 1.5)
"
[sub_resource type="Gradient" id="Gradient_hs6gw"]
[sub_resource type="GradientTexture2D" id="GradientTexture2D_hyysp"]
@ -30,11 +39,9 @@ ambient_light_color = Color(1, 1, 1, 1)
reflected_light_source = 2
[node name="Base" type="Node3D"]
script = ExtResource("1_rj40i")
script = SubResource("GDScript_1yugx")
metadata/_custom_type_script = "uid://w3fetao1pegm"
[node name="Gravity" parent="." instance=ExtResource("3_muudg")]
[node name="Environment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_y0yoy")
@ -155,6 +162,8 @@ transform = Transform3D(0.0172464, 0.899835, 0, -0.899835, 0.0172464, 0, 0, 0, 0
[node name="Ring13" parent="Rings" instance=ExtResource("3_hel5x")]
transform = Transform3D(0.0114976, 0.59989, 0, -0.59989, 0.0114976, 0, 0, 0, 0.6, -414.274, 8.64172, 0)
[connection signal="velocity_change" from="Player" to="." method="_on_player_velocity_change"]
[editable path="Signs/Sign"]
[editable path="Signs/Sign7"]
[editable path="Signs/Sign2"]

View file

@ -1,50 +0,0 @@
[gd_scene load_steps=11 format=3 uid="uid://0re2mcnpub4e"]
[ext_resource type="Script" uid="uid://w3fetao1pegm" path="res://levels/level.gd" id="1_scm0b"]
[ext_resource type="PackedScene" uid="uid://dw0xl8644x166" path="res://elements/gravity.tscn" id="2_fo4i1"]
[ext_resource type="PackedScene" uid="uid://drfy3vhe6skp1" path="res://levels/night/music.tscn" id="4_qr8kk"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="5_j5vh3"]
[ext_resource type="PackedScene" uid="uid://cpm3laywhlbq5" path="res://elements/ring.tscn" id="6_st6rs"]
[sub_resource type="Gradient" id="Gradient_x6q8u"]
[sub_resource type="GradientTexture1D" id="GradientTexture1D_lu6nv"]
gradient = SubResource("Gradient_x6q8u")
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_4yv3y"]
panorama = SubResource("GradientTexture1D_lu6nv")
[sub_resource type="Sky" id="Sky_ys2yp"]
sky_material = SubResource("PanoramaSkyMaterial_4yv3y")
[sub_resource type="Environment" id="Environment_8o42c"]
background_mode = 1
sky = SubResource("Sky_ys2yp")
ambient_light_source = 2
ambient_light_color = Color(1, 1, 1, 1)
reflected_light_source = 2
[node name="Demo" type="Node3D"]
script = ExtResource("1_scm0b")
metadata/_custom_type_script = "uid://w3fetao1pegm"
[node name="Gravity" parent="." instance=ExtResource("2_fo4i1")]
[node name="Environment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_8o42c")
[node name="Music" parent="." instance=ExtResource("4_qr8kk")]
[node name="Player" parent="." instance=ExtResource("5_j5vh3")]
[node name="Rings" type="Node3D" parent="."]
[node name="Ring" parent="Rings" instance=ExtResource("6_st6rs")]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 15.6731, -50)
visible = false
[node name="Ring2" parent="Rings" instance=ExtResource("6_st6rs")]
transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, 10, -10, 0)
[node name="Ring3" parent="Rings" instance=ExtResource("6_st6rs")]
transform = Transform3D(-4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0, 1, -10, -10, 0)

View file

@ -1,13 +1,32 @@
[gd_scene load_steps=11 format=3 uid="uid://e1761h6d522a"]
[gd_scene load_steps=10 format=3 uid="uid://e1761h6d522a"]
[ext_resource type="Script" uid="uid://w3fetao1pegm" path="res://levels/level.gd" id="1_fdxcj"]
[ext_resource type="PackedScene" uid="uid://dw0xl8644x166" path="res://elements/gravity.tscn" id="2_gxmta"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="2_mjogx"]
[ext_resource type="PackedScene" uid="uid://cakmsiye3hjfe" path="res://levels/forest/music.tscn" id="3_n1xsx"]
[ext_resource type="PackedScene" uid="uid://cpm3laywhlbq5" path="res://elements/ring.tscn" id="4_p8yhq"]
[ext_resource type="PackedScene" uid="uid://da6lkdiskdh8v" path="res://elements/tree.tscn" id="6_1e514"]
[ext_resource type="PackedScene" uid="uid://c77bli40240nk" path="res://elements/sign.tscn" id="6_ifogr"]
[sub_resource type="GDScript" id="GDScript_mqwxc"]
script/source = "extends Level
var cycle := 0
func _init() -> void:
self.id = \"forest\"
self.thumbnail = load(\"res://levels/forest/thumbnail.png\")
func _process(_delta: float) -> void:
var moving_ring: Ring = $Rings/Ring18
if !moving_ring.collected:
if moving_ring.position.y < 55 or moving_ring.position.y > 65:
cycle += 1
moving_ring.position.y += 0.5 if cycle % 2 else -0.5
func _on_player_velocity_change(new_velocity: float) -> void:
self.velocity = new_velocity
self.music.adaptInstrumentsToVelocity(new_velocity)
"
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_grcut"]
sky_top_color = Color(0, 0.504542, 0.579919, 1)
sky_horizon_color = Color(9.62615e-08, 0.776269, 0.496436, 1)
@ -29,11 +48,9 @@ ambient_light_color = Color(1, 1, 1, 1)
reflected_light_source = 2
[node name="Forest" type="Node3D"]
script = ExtResource("1_fdxcj")
script = SubResource("GDScript_mqwxc")
metadata/_custom_type_script = "uid://w3fetao1pegm"
[node name="Gravity" parent="." instance=ExtResource("2_gxmta")]
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.920505, 0.195366, -0.338383, 0, 0.866025, 0.5, 0.390731, 0.460252, -0.797181, 0, 0, 0)
shadow_enabled = true
@ -261,4 +278,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -218.208, 206.247, 0)
[node name="Ring23" parent="Rings" instance=ExtResource("4_p8yhq")]
transform = Transform3D(0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 0, 0, 1, -1018.15, -96.9134, 0)
[connection signal="velocity_change" from="Player" to="." method="_on_player_velocity_change"]
[editable path="Sign"]

BIN
levels/forest/thumbnail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dhyxogc13hnbe"
path="res://.godot/imported/thumbnail.png-6909560b6205778a3cb325a584af7ca5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://levels/forest/thumbnail.png"
dest_files=["res://.godot/imported/thumbnail.png-6909560b6205778a3cb325a584af7ca5.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -1,20 +1,47 @@
class_name Level
extends Node3D
signal started_playing
signal gravity_change
signal ring_collected
signal completed
var seconds_spent: float = 0.00
const gravity_strength_normal = 7
const gravity_strength_strong = gravity_strength_normal * 3
## Lowercase, spaceless name of the level
var id: String
var thumbnail: CompressedTexture2D
var player: Node3D
var music: Music
var velocity: float = 0.0
var rings: Array[Ring] = []
var finished_rings_count: int = 0
var velocity: float = 0.0
var has_started_playing: bool = false
func _init() -> void:
assert(len(id) > 0, self.name + " has no id!")
func _ready() -> void:
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR,
Vector3(0, 0, 0)
)
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY,
0
)
var children = get_children()
for child in children:
if is_instance_of(child, Music):
music = child
if child.name == "Player" and is_instance_of(child, Node3D):
player = child
assert(is_instance_valid(music), self.name + " has no music!")
var rings_node = get_node("Rings")
@ -32,7 +59,46 @@ func collect_ring():
if finished_rings_count >= len(rings):
completed.emit()
func _process(delta: float) -> void:
seconds_spent += delta
velocity = abs($Player.velocity.x) + abs($Player.velocity.y)
music.adaptInstrumentsToVelocity(velocity, delta)
func _input(_event: InputEvent) -> void:
if Input.is_action_pressed("gravity_strong") or Input.is_action_just_released("gravity_strong"):
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY,
gravity_strength_strong if Input.is_action_pressed("gravity_strong") else gravity_strength_normal
)
var up := Input.is_action_just_pressed("gravity_up")
var left := Input.is_action_just_pressed("gravity_left")
var right := Input.is_action_just_pressed("gravity_right")
var down := Input.is_action_just_pressed("gravity_down")
if up or left or right or down:
if !has_started_playing:
has_started_playing = true
started_playing.emit()
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY,
gravity_strength_strong if Input.is_action_pressed("gravity_strong") else gravity_strength_normal
)
var direction: Vector3
if up:
direction = Vector3.UP
elif left:
direction = Vector3.LEFT
elif right:
direction = Vector3.RIGHT
elif down:
direction = Vector3.DOWN
if direction && direction != PhysicsServer3D.area_get_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR
):
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR,
direction
)
gravity_change.emit(direction)

View file

@ -40,7 +40,9 @@ func changeVolume(db: float) -> void:
AudioServer.set_bus_volume_db(bus_index, db + Settings.volume_music)
AudioServer.set_bus_mute(bus_index, Settings.volume_music <= -15.0)
func adaptInstrumentsToVelocity(velocity: float, delta: float) -> void:
func adaptInstrumentsToVelocity(velocity: float) -> void:
## temp
const delta = 0.00833333333333
var instruments_needed = floor(velocity / 8)
var instruments_playing = instruments.filter(func(i: AudioStreamPlayer): return i.volume_db > -50)

View file

@ -1,14 +1,27 @@
[gd_scene load_steps=14 format=3 uid="uid://drnqmu4lka22d"]
[gd_scene load_steps=13 format=3 uid="uid://drnqmu4lka22d"]
[ext_resource type="Script" uid="uid://w3fetao1pegm" path="res://levels/level.gd" id="1_3m1pa"]
[ext_resource type="PackedScene" uid="uid://drfy3vhe6skp1" path="res://levels/night/music.tscn" id="1_npc74"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="2_bc1ig"]
[ext_resource type="PackedScene" uid="uid://dw0xl8644x166" path="res://elements/gravity.tscn" id="2_lfplq"]
[ext_resource type="PackedScene" uid="uid://cpm3laywhlbq5" path="res://elements/ring.tscn" id="4_brcr0"]
[ext_resource type="PackedScene" uid="uid://c77bli40240nk" path="res://elements/sign.tscn" id="6_36yav"]
[ext_resource type="PackedScene" uid="uid://b4jtpua36m6b1" path="res://elements/star.tscn" id="7_ltcl0"]
[ext_resource type="PackedScene" uid="uid://da6lkdiskdh8v" path="res://elements/tree.tscn" id="8_kgstj"]
[sub_resource type="GDScript" id="GDScript_akhnt"]
script/source = "extends Level
func _init() -> void:
self.id = \"night\"
func _process(delta: float) -> void:
$Tree.rotate_x(delta / 3)
$Tree.rotate_y(delta)
func _on_player_velocity_change(new_velocity: float) -> void:
self.velocity = new_velocity
self.music.adaptInstrumentsToVelocity(new_velocity * 2)
"
[sub_resource type="Gradient" id="Gradient_x6q8u"]
[sub_resource type="GradientTexture1D" id="GradientTexture1D_lu6nv"]
@ -28,11 +41,9 @@ ambient_light_color = Color(1, 1, 1, 1)
reflected_light_source = 2
[node name="Night" type="Node3D"]
script = ExtResource("1_3m1pa")
script = SubResource("GDScript_akhnt")
metadata/_custom_type_script = "uid://w3fetao1pegm"
[node name="Gravity" parent="." instance=ExtResource("2_lfplq")]
[node name="Environment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_8o42c")
@ -193,7 +204,7 @@ transform = Transform3D(-1.31134e-07, -3, 0, 3, -1.31134e-07, 0, 0, 0, 3, -104.2
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, -12.3896, -2, 0)
[node name="Tree" parent="." instance=ExtResource("8_kgstj")]
transform = Transform3D(-3.79443, -7.04289, 0, 7.04289, -3.79443, 0, 0, 0, 8, 28.958, -413.185, -93.8242)
transform = Transform3D(-3.79271, -7.04382, 0, 7.04382, -3.79271, 0, 0, 0, 8, 28.958, -413.185, -93.8242)
[node name="Signs" type="Node3D" parent="."]
@ -327,6 +338,8 @@ transform = Transform3D(-0.99693, 0.0731445, -0.0279554, -0.0555776, -0.912445,
[node name="Star32" parent="Stars" instance=ExtResource("7_ltcl0")]
transform = Transform3D(-0.99693, 0.0731445, -0.0279554, -0.0555776, -0.912445, -0.405407, -0.055161, -0.402609, 0.913709, 0.952739, 0.24193, 7.10684)
[connection signal="velocity_change" from="Player" to="." method="_on_player_velocity_change"]
[editable path="Signs/Sign"]
[editable path="Signs/Sign4"]
[editable path="Signs/Sign2"]

View file

@ -1,146 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://ikeidrgprk8k"]
[ext_resource type="PackedScene" uid="uid://wlhsarkeqe8r" path="res://menus/main/panel.tscn" id="1_qfa5o"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="2_0jxef"]
[sub_resource type="GDScript" id="GDScript_bt14i"]
script/source = "extends Node3D
signal request_start
var start_menu = preload(\"res://menus/main/start_menu.tscn\")
var save_file_manager = preload(\"res://menus/main/save_file_manager.tscn\")
var settings_menu = preload(\"res://menus/main/settings_menu.tscn\")
func _ready() -> void:
$Menus/Panel2.connect(\"request_return\", func(): rotate_cube_to(0))
$Menus/Panel3.connect(\"request_return\", func(): rotate_cube_to(90))
$Menus/Panel4.connect(\"request_return\", func(): rotate_cube_to(180))
var sm = start_menu.instantiate()
sm.connect(\"request_start\", start)
sm.connect(\"request_settings\", settings)
$Menus/Panel1.change_menu(sm, false)
func start() -> void:
var svm = save_file_manager.instantiate()
svm.connect(\"request_start\", func(): request_start.emit())
$Menus/Panel2.change_menu(svm)
rotate_cube_to(90)
func settings() -> void:
var sm = settings_menu.instantiate()
$Menus/Panel2.change_menu(sm)
rotate_cube_to(90)
func rotate_cube_to(degrees: int) -> void:
if $Menus/AnimationPlayer.is_playing():
return
degrees = -degrees
var animation: Animation = $Menus/AnimationPlayer.get_animation(\"rotate\")
var new_rotation = Vector3(0, degrees, 0)
animation.track_set_key_value(0, 0, $Menus.rotation_degrees)
animation.track_set_key_value(0, 1, new_rotation)
$Menus/AnimationPlayer.play(\"rotate\")
"
[sub_resource type="Animation" id="Animation_0jxef"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:rotation_degrees")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_bt14i"]
resource_name = "rotate"
length = 0.3
step = 0.3
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:rotation_degrees")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, 0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_3bwb0"]
_data = {
&"RESET": SubResource("Animation_0jxef"),
&"rotate": SubResource("Animation_bt14i")
}
[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_bt14i"]
ground_color = Color(0.794232, 0.673177, 0.531056, 1)
[sub_resource type="Sky" id="Sky_0jxef"]
sky_material = SubResource("PhysicalSkyMaterial_bt14i")
[sub_resource type="Environment" id="Environment_qfa5o"]
background_mode = 2
background_color = Color(0.804743, 0.804743, 0.804743, 1)
sky = SubResource("Sky_0jxef")
ambient_light_source = 3
ambient_light_color = Color(0.986752, 0.986752, 0.986752, 1)
reflected_light_source = 2
[node name="Area" type="Node3D"]
process_mode = 3
script = SubResource("GDScript_bt14i")
[node name="Menus" type="CSGBox3D" parent="."]
use_collision = true
[node name="Panel1" parent="Menus" instance=ExtResource("1_qfa5o")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.501)
[node name="Panel2" parent="Menus" instance=ExtResource("1_qfa5o")]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0.501, 0, 0)
[node name="Panel3" parent="Menus" instance=ExtResource("1_qfa5o")]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, -0.501)
[node name="Panel4" parent="Menus" instance=ExtResource("1_qfa5o")]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -0.501, 0, 0)
[node name="AnimationPlayer" type="AnimationPlayer" parent="Menus"]
libraries = {
&"": SubResource("AnimationLibrary_3bwb0")
}
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.5)
current = true
fov = 40.0
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_qfa5o")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.819152, -0.412596, 0.39844, 0, 0.694658, 0.71934, -0.573576, -0.589249, 0.569031, 0, 0, 0)
[node name="Player" parent="." instance=ExtResource("2_0jxef")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.87335, 0)
[node name="Camera" parent="Player" index="0"]
visible = false
current = false
[node name="GPUParticles3D" parent="Player/Sphere" index="2"]
visible = false
[editable path="Player"]

View file

@ -16,7 +16,7 @@ func _on_btn_exit_pressed() -> void:
get_tree().quit()
"
[node name="StartMenu" type="Control"]
[node name="InitialMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0

View file

@ -0,0 +1,152 @@
[gd_scene load_steps=2 format=3 uid="uid://d3b12iqla7uh6"]
[sub_resource type="GDScript" id="GDScript_rmgh7"]
script/source = "extends Control
signal request_play_level
@onready var carousel := $VBoxContainer/Selection/Carousel
@onready var label_time := $VBoxContainer/Presentation/MarginContainer/VBoxContainer/BestTime
const levels = [
\"res://levels/base/level.tscn\",
\"res://levels/forest/level.tscn\",
\"res://levels/night/level.tscn\",
]
var loaded_level: Level
var loaded_level_scene: PackedScene
var loaded_level_path: String
func _enter_tree() -> void:
if len(loaded_level_path):
prepare_level(loaded_level_path)
func _ready() -> void:
var placeholders := carousel.get_children()
for placeholder in placeholders:
placeholder.queue_free()
for i in len(levels):
var btn := Button.new()
btn.text = \"Level \" + str(i + 1)
btn.connect(\"pressed\", func(): prepare_level(levels[i]))
carousel.add_child(btn)
func prepare_level(level_scene_path: String):
if is_instance_valid(loaded_level):
loaded_level.queue_free()
ResourceLoader.load_threaded_request(level_scene_path)
loaded_level_scene = ResourceLoader.load_threaded_get(level_scene_path)
loaded_level = loaded_level_scene.instantiate()
loaded_level_path = level_scene_path
$VBoxContainer/Presentation/Thumbnail.texture = loaded_level.thumbnail
$VBoxContainer/Presentation/MarginContainer/VBoxContainer/LevelName.text = \"The \" + loaded_level.id.capitalize()
$VBoxContainer/Presentation.show()
$VBoxContainer/MarginContainer/PlayButton.show()
display_file_data(SaveFiles.read(SaveFiles.selected_file))
func display_file_data(data: Variant) -> void:
label_time.text = \"Best time: \"
var property_name := loaded_level.id + \"_best_time\"
if data.has(property_name) and data[property_name] is float:
var seconds: float = data[property_name]
var minutes: int = floor(seconds / 60)
label_time.text += (\"%0*d\" % [2, minutes]) + \":\" + (\"%0*.3f\" % [6, seconds - (minutes * 60)])
else:
label_time.text += \"00:00.000\"
func _on_play_button_pressed() -> void:
if is_instance_valid(loaded_level):
request_play_level.emit(loaded_level, loaded_level_scene)
func seconds_to_readable(seconds: float) -> String:
var minutes: int = floor(seconds / 60)
return (\"%0*d\" % [2, minutes]) + \":\" + (\"%0*.3f\" % [6, seconds - (minutes * 60)])
## This prevents a memory leak that is possible when this menu is destroyed
## and the loaded_level becomes unavailable to the rest of the game
## For example in the main menu, go to this menu, select a level,
## press \"return\" and go to this menu again, boom, memory leak prevented by this
func _notification(what: int) -> void:
match what:
NOTIFICATION_PREDELETE:
if is_instance_valid(loaded_level) && !loaded_level.is_inside_tree():
loaded_level.queue_free()
"
[node name="LevelSelectionMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_rmgh7")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Selection" type="ScrollContainer" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 50)
layout_mode = 2
[node name="Carousel" type="HBoxContainer" parent="VBoxContainer/Selection"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
alignment = 1
[node name="Placeholder1" type="Button" parent="VBoxContainer/Selection/Carousel"]
layout_mode = 2
theme_override_constants/icon_max_width = 150
text = "Placeholder"
[node name="Placeholder2" type="Button" parent="VBoxContainer/Selection/Carousel"]
layout_mode = 2
text = "Placeholder"
[node name="Placeholder3" type="Button" parent="VBoxContainer/Selection/Carousel"]
layout_mode = 2
text = "Placeholder"
[node name="Presentation" type="HBoxContainer" parent="VBoxContainer"]
visible = false
layout_mode = 2
[node name="Thumbnail" type="TextureRect" parent="VBoxContainer/Presentation"]
custom_minimum_size = Vector2(150, 150)
layout_mode = 2
expand_mode = 1
stretch_mode = 4
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/Presentation"]
layout_mode = 2
theme_override_constants/margin_left = 5
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Presentation/MarginContainer"]
layout_mode = 2
alignment = 1
[node name="LevelName" type="Label" parent="VBoxContainer/Presentation/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Placeholder"
[node name="BestTime" type="Label" parent="VBoxContainer/Presentation/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "Best time: 00:00.000"
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_top = 15
[node name="PlayButton" type="Button" parent="VBoxContainer/MarginContainer"]
visible = false
layout_mode = 2
text = "Play!"
[connection signal="pressed" from="VBoxContainer/MarginContainer/PlayButton" to="." method="_on_play_button_pressed"]

296
menus/main/main_menu.tscn Normal file
View file

@ -0,0 +1,296 @@
[gd_scene load_steps=16 format=3 uid="uid://ikeidrgprk8k"]
[ext_resource type="PackedScene" uid="uid://wlhsarkeqe8r" path="res://menus/main/panel.tscn" id="1_5vmsf"]
[ext_resource type="PackedScene" uid="uid://cnnvwotv33u1b" path="res://elements/player.tscn" id="2_2rexg"]
[sub_resource type="GDScript" id="GDScript_bt14i"]
script/source = "extends Node3D
signal request_play_level
func _enter_tree() -> void:
$Camera3D/AnimationPlayer.play_backwards(\"camera_pan\")
PhysicsServer3D.area_set_param(
get_viewport().find_world_3d().space,
PhysicsServer3D.AREA_PARAM_GRAVITY_VECTOR,
Vector3.DOWN
)
# Display on Panel 1
const res_initial_menu = preload(\"res://menus/main/initial_menu.tscn\")
func _ready() -> void:
$Menus/Panel2.connect(\"request_return\", func(): rotate_cube_to(0))
$Menus/Panel3.connect(\"request_return\", func(): rotate_cube_to(90))
$Menus/Panel4.connect(\"request_return\", func(): rotate_cube_to(180))
var initial_menu := res_initial_menu.instantiate()
initial_menu.connect(\"request_start\", start)
initial_menu.connect(\"request_settings\", settings)
$Menus/Panel1.change_menu(initial_menu, false)
# Display on Panel 2
const res_settings_menu = preload(\"res://menus/main/settings_menu.tscn\")
func settings() -> void:
var settings_menu := res_settings_menu.instantiate()
$Menus/Panel2.change_menu(settings_menu)
rotate_cube_to(90)
const res_save_file_manager = preload(\"res://menus/main/save_file_manager.tscn\")
func start() -> void:
var save_file_manager := res_save_file_manager.instantiate()
save_file_manager.connect(\"request_start\", level_selection)
$Menus/Panel2.change_menu(save_file_manager)
rotate_cube_to(90)
# Display on Panel 3
const res_level_selection_menu = preload(\"res://menus/main/level_selection_menu.tscn\")
func level_selection() -> void:
var level_selection_menu := res_level_selection_menu.instantiate()
level_selection_menu.connect(\"request_play_level\", func(level: Level, scene: PackedScene):
$Camera3D/AnimationPlayer.play(\"camera_pan\")
await get_tree().create_timer(1.5).timeout
request_play_level.emit(level, scene)
)
$Menus/Panel3.change_menu(level_selection_menu)
rotate_cube_to(180)
# cube
func rotate_cube_to(degrees: int) -> void:
if $Menus/AnimationPlayer.is_playing():
return
var cube_animation: Animation = $Menus/AnimationPlayer.get_animation(\"rotate\")
cube_animation.track_set_key_value(0, 0, $Menus.rotation_degrees)
cube_animation.track_set_key_value(0, 1, Vector3(0, -degrees, 0))
$Menus/AnimationPlayer.play(\"rotate\")
if $AnimationPlayer.is_playing():
await get_tree().create_timer($AnimationPlayer.current_animation_length).timeout
var sky_animation: Animation = $AnimationPlayer.get_animation(\"sky_brightness\")
sky_animation.track_set_key_value(0, 0, $WorldEnvironment.environment.sky.sky_material.energy_multiplier)
sky_animation.track_set_key_value(0, 1, max(90, degrees * 2) / 90)
$AnimationPlayer.play(\"sky_brightness\")
"
[sub_resource type="Animation" id="Animation_0jxef"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:rotation_degrees")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_bt14i"]
resource_name = "rotate"
length = 0.3
step = 0.3
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:rotation_degrees")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, 0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_3bwb0"]
_data = {
&"RESET": SubResource("Animation_0jxef"),
&"rotate": SubResource("Animation_bt14i")
}
[sub_resource type="Animation" id="Animation_2rexg"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("WorldEnvironment:environment:sky:sky_material:energy_multiplier")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [1.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera3D:fov")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [40.0]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Camera3D:position")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 2.5)]
}
[sub_resource type="Animation" id="Animation_jinmx"]
resource_name = "camera_pan"
length = 1.5
step = 0.1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera3D:fov")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1.5),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [40.0, 75.0]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera3D:position")
tracks/1/interp = 2
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 1.5),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 0, 2.5), Vector3(0, 0, 15)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_jinmx"]
_data = {
&"RESET": SubResource("Animation_2rexg"),
&"camera_pan": SubResource("Animation_jinmx")
}
[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_bt14i"]
ground_color = Color(1, 0.484315, 0.700558, 1)
[sub_resource type="Sky" id="Sky_0jxef"]
sky_material = SubResource("PhysicalSkyMaterial_bt14i")
[sub_resource type="Environment" id="Environment_qfa5o"]
background_mode = 2
background_color = Color(0.804743, 0.804743, 0.804743, 1)
sky = SubResource("Sky_0jxef")
ambient_light_source = 3
ambient_light_color = Color(0.986752, 0.986752, 0.986752, 1)
reflected_light_source = 2
[sub_resource type="Animation" id="Animation_j2kye"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("../../WorldEnvironment:environment:sky:sky_material:energy_multiplier")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [1.0]
}
[sub_resource type="Animation" id="Animation_gma2u"]
resource_name = "sky_brightness"
length = 0.3
step = 0.1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("../../WorldEnvironment:environment:sky:sky_material:energy_multiplier")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.3),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [1.0, 1.0]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_wruj6"]
_data = {
&"RESET": SubResource("Animation_j2kye"),
&"sky_brightness": SubResource("Animation_gma2u")
}
[node name="MainMenu" type="Node3D"]
process_mode = 3
script = SubResource("GDScript_bt14i")
[node name="Menus" type="CSGBox3D" parent="."]
use_collision = true
[node name="Panel1" parent="Menus" instance=ExtResource("1_5vmsf")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.501)
[node name="Panel2" parent="Menus" instance=ExtResource("1_5vmsf")]
transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0.501, 0, 0)
[node name="Panel3" parent="Menus" instance=ExtResource("1_5vmsf")]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, -0.501)
[node name="Panel4" parent="Menus" instance=ExtResource("1_5vmsf")]
transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -0.501, 0, 0)
[node name="AnimationPlayer" type="AnimationPlayer" parent="Menus"]
libraries = {
&"": SubResource("AnimationLibrary_3bwb0")
}
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.5)
current = true
fov = 40.0
[node name="AnimationPlayer" type="AnimationPlayer" parent="Camera3D"]
root_node = NodePath("../..")
libraries = {
&"": SubResource("AnimationLibrary_jinmx")
}
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_qfa5o")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.819152, -0.412596, 0.39844, 0, 0.694658, 0.71934, -0.573576, -0.589249, 0.569031, 0, 0, 0)
[node name="Player" parent="." instance=ExtResource("2_2rexg")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0)
[node name="Camera" parent="Player" index="0"]
visible = false
current = false
[node name="GPUParticles3D" parent="Player/Sphere" index="2"]
visible = false
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
root_node = NodePath("../Camera3D/AnimationPlayer")
libraries = {
&"": SubResource("AnimationLibrary_wruj6")
}
[editable path="Player"]

View file

@ -6,21 +6,31 @@ script/source = "extends Control
signal request_start
@onready var label_name = $VBoxContainer/MarginContainer/VBoxContainer/Description/Name
@onready var label_time = $VBoxContainer/MarginContainer/VBoxContainer/Description/Time
func _ready() -> void:
$VBoxContainer/MarginContainer.hide()
## Get the newest \"played_for\" when the main menu appears again
func _enter_tree() -> void:
if len(SaveFiles.selected_file):
display_file_data(SaveFiles.selected_file)
func _on_save_1_pressed() -> void:
label_name.text = $VBoxContainer/Save1.text
display_file_data(SaveFiles.read(SaveFiles.names[0]))
display_file_data(SaveFiles.names[0])
func _on_save_2_pressed() -> void:
label_name.text = $VBoxContainer/Save2.text
display_file_data(SaveFiles.read(SaveFiles.names[1]))
display_file_data(SaveFiles.names[1])
func _on_save_3_pressed() -> void:
label_name.text = $VBoxContainer/Save3.text
display_file_data(SaveFiles.read(SaveFiles.names[2]))
display_file_data(SaveFiles.names[2])
func display_file_data(data: Variant) -> void:
func display_file_data(file_name: String) -> void:
$VBoxContainer/MarginContainer.show()
var label_time := $VBoxContainer/MarginContainer/VBoxContainer/Description/Time
var data = SaveFiles.read(file_name)
$VBoxContainer/MarginContainer.show()
label_time.text = \" | \"
if data.has(\"played_for\") and data.played_for is float:
@ -28,14 +38,14 @@ func display_file_data(data: Variant) -> void:
var minutes: int = floor(seconds / 60)
label_time.text += (\"%0*d\" % [2, minutes]) + \":\" + (\"%0*.3f\" % [6, seconds - (minutes * 60)])
else:
label_time.text += \"00:00\"
label_time.text += \"00:00.000\"
func _on_start_pressed() -> void:
request_start.emit()
func _on_delete_pressed() -> void:
SaveFiles.empty(SaveFiles.selected_file)
display_file_data(SaveFiles.read(SaveFiles.selected_file))
display_file_data(SaveFiles.selected_file)
"
[node name="SaveFileManager" type="Control"]

View file

@ -114,10 +114,12 @@ libraries = {
}
[node name="Label" type="Label" parent="VSplitContainer"]
custom_minimum_size = Vector2(1, 1)
layout_mode = 2
text = "MENU NAME"
label_settings = SubResource("LabelSettings_2d4ws")
horizontal_alignment = 1
autowrap_mode = 3
uppercase = true
[node name="MarginContainer" type="MarginContainer" parent="VSplitContainer"]

View file

@ -13,7 +13,7 @@ config_version=5
config/name="DreamBall"
config/description="Manipulate the gravity to make a ball go through every ring!
https://kitsunes.dev/Taevas/DreamBall"
config/version="20250423.0"
config/version="20250503.0"
run/main_scene="res://index.tscn"
config/features=PackedStringArray("4.4", "GL Compatibility")
boot_splash/show_image=false
@ -78,6 +78,13 @@ restart_level={
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null)
]
}
display_map={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":false,"script":null)
]
}
[physics]
@ -86,6 +93,7 @@ common/physics_ticks_per_second=120
common/max_physics_steps_per_frame=20
3d/physics_engine="GodotPhysics3D"
3d/default_gravity=1.0
3d/default_gravity_vector=Vector3(0, -0.5, 0)
3d/solver/solver_iterations=40
3d/solver/default_contact_bias=1.0
common/physics_interpolation=true

View file

@ -6,7 +6,7 @@ const names := [
"user://save_file_3.save",
]
var selected_file := names[0]
var selected_file: String
func _ready() -> void:
for save_file_name in names:
@ -22,6 +22,7 @@ func empty(save_file_name: String) -> void:
write(JSON.stringify({}), save_file_name)
func read(save_file_name: String) -> Variant:
print("Reading from save file ", save_file_name)
ensure_existence(save_file_name)
selected_file = save_file_name
@ -41,6 +42,7 @@ func write(json_string: String, save_file_name: String) -> void:
save_file.store_line("FOR YOUR SAFETY, ALWAYS CHECK IF THE DATA OF THE FILES YOU DOWNLOAD LOOKS OKAY")
func change_property(property: String, value, save_file_name: String) -> void:
print("Changing property '", property, "' on save file ", save_file_name)
ensure_existence(save_file_name)
var data = read(save_file_name)
data[property] = value