diff --git a/assets/media/images/car_sprite.png b/assets/media/images/car_sprite.png
new file mode 100644
index 0000000..ad690d5
Binary files /dev/null and b/assets/media/images/car_sprite.png differ
diff --git a/assets/media/images/car_sprite.png.import b/assets/media/images/car_sprite.png.import
new file mode 100644
index 0000000..801f339
--- /dev/null
+++ b/assets/media/images/car_sprite.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cys6q5fgkhf8v"
+path="res://.godot/imported/car_sprite.png-6749c796738ab73a3c043ac1e444c50b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/media/images/car_sprite.png"
+dest_files=["res://.godot/imported/car_sprite.png-6749c796738ab73a3c043ac1e444c50b.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
diff --git a/assets/media/images/sound-loud.svg b/assets/media/images/sound-loud.svg
new file mode 100644
index 0000000..c3fb03c
--- /dev/null
+++ b/assets/media/images/sound-loud.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/assets/media/images/sound-loud.svg.import b/assets/media/images/sound-loud.svg.import
new file mode 100644
index 0000000..22aee7b
--- /dev/null
+++ b/assets/media/images/sound-loud.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://du5pfobp663lk"
+path="res://.godot/imported/sound-loud.svg-333e34fba7f1fc42a8539247f3d18be7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/media/images/sound-loud.svg"
+dest_files=["res://.godot/imported/sound-loud.svg-333e34fba7f1fc42a8539247f3d18be7.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
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/assets/media/images/sound-off.svg b/assets/media/images/sound-off.svg
new file mode 100644
index 0000000..3c9b09b
--- /dev/null
+++ b/assets/media/images/sound-off.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/assets/media/images/sound-off.svg.import b/assets/media/images/sound-off.svg.import
new file mode 100644
index 0000000..1c506d5
--- /dev/null
+++ b/assets/media/images/sound-off.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://15k2jvk1jxr2"
+path="res://.godot/imported/sound-off.svg-285eff611e322220e8bf9e813ec81196.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/media/images/sound-off.svg"
+dest_files=["res://.godot/imported/sound-off.svg-285eff611e322220e8bf9e813ec81196.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
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/fps.gd b/fps.gd
new file mode 100644
index 0000000..e8a0b69
--- /dev/null
+++ b/fps.gd
@@ -0,0 +1,10 @@
+extends CanvasLayer
+
+
+func _process(_delta: float) -> void:
+ $Label.text = "FPS: " + str(Engine.get_frames_per_second())
+
+
+func _input(event: InputEvent) -> void:
+ if event.is_action_pressed("toggle_fps"):
+ visible = not visible
diff --git a/fps.gd.uid b/fps.gd.uid
new file mode 100644
index 0000000..3424f8c
--- /dev/null
+++ b/fps.gd.uid
@@ -0,0 +1 @@
+uid://dby7rbf2klgm0
diff --git a/levels/level_01.tscn b/levels/level_01.tscn
index 6b3db9d..5a073a6 100644
--- a/levels/level_01.tscn
+++ b/levels/level_01.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=6 format=4 uid="uid://dsf218g5whvei"]
+[gd_scene load_steps=7 format=4 uid="uid://dsf218g5whvei"]
[ext_resource type="Script" uid="uid://cq6hvd85q87qa" path="res://scripts/level.gd" id="1_mckh2"]
[ext_resource type="Texture2D" uid="uid://c8so6h2ldkd2c" path="res://assets/media/images/tileset.jpeg" id="1_ty3hu"]
+[ext_resource type="PackedScene" uid="uid://1jwlt8wyr0ea" path="res://scenes/car.tscn" id="3_81a5f"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_mckh2"]
vertices = PackedVector2Array(422, 810.227, 410, 810.297, 410, 810, 422, 810, 74, 326, -55.9375, 326, -55.4375, 266, 86, 266, 86, 746, 74, 758, 422, 758, 410, 746, 422, 314, 410, 326, 262, 314, 186, 326, 186, -21.0781, 262, -20.7969)
@@ -904,5 +905,8 @@ position = Vector2(296, 320)
[node name="SAida3" type="Node2D" parent="SaidasLabirinto"]
position = Vector2(418, 416)
+[node name="car" parent="." instance=ExtResource("3_81a5f")]
+position = Vector2(97, 117)
+
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
[connection signal="timeout" from="Spawn" to="." method="_on_spawn_timeout"]
diff --git a/levels/level_02.tscn b/levels/level_02.tscn
index d870f27..cd47c1b 100644
--- a/levels/level_02.tscn
+++ b/levels/level_02.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=6 format=4 uid="uid://d3w1e0xaquuqx"]
+[gd_scene load_steps=7 format=4 uid="uid://d3w1e0xaquuqx"]
[ext_resource type="Script" uid="uid://cq6hvd85q87qa" path="res://scripts/level.gd" id="1_g685p"]
[ext_resource type="Texture2D" uid="uid://c8so6h2ldkd2c" path="res://assets/media/images/tileset.jpeg" id="1_nnn84"]
+[ext_resource type="PackedScene" uid="uid://1jwlt8wyr0ea" path="res://scenes/car.tscn" id="3_8yqd8"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_g685p"]
vertices = PackedVector2Array(525.039, 877.82, -222.969, 865.18, -127.953, -90, 528.961, -90)
@@ -533,5 +534,8 @@ position = Vector2(247, 599)
[node name="SAida3" type="Node2D" parent="SaidasLabirinto"]
position = Vector2(404, 765)
+[node name="car" parent="." instance=ExtResource("3_8yqd8")]
+position = Vector2(220, 275)
+
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
[connection signal="timeout" from="Spawn" to="." method="_on_spawn_timeout"]
diff --git a/levels/level_03.tscn b/levels/level_03.tscn
index a1515e2..83e93c2 100644
--- a/levels/level_03.tscn
+++ b/levels/level_03.tscn
@@ -1,7 +1,8 @@
-[gd_scene load_steps=6 format=4 uid="uid://mux8il3lbk2v"]
+[gd_scene load_steps=7 format=4 uid="uid://mux8il3lbk2v"]
[ext_resource type="Script" uid="uid://cq6hvd85q87qa" path="res://scripts/level.gd" id="1_lear0"]
[ext_resource type="Texture2D" uid="uid://c8so6h2ldkd2c" path="res://assets/media/images/tileset.jpeg" id="2_85vqe"]
+[ext_resource type="PackedScene" uid="uid://1jwlt8wyr0ea" path="res://scenes/car.tscn" id="3_85vqe"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_mckh2"]
vertices = PackedVector2Array(262, 1323.37, 186, 1323.66, 186, 326, 262, 326, 22, 1324.25, -54, 1324.54, -54, 326, 22, 326, 518, 1322.43, 442, 1322.71, 442, 326, 518, 326, 186, 266, 186, -20.0938, 262, -19.9297, 262, 266, -54, 266, -54, -20.6016, 22, -20.4375, 22, 266, 582, 266, 582, 326, -358, 326, -358, 266)
@@ -904,5 +905,8 @@ position = Vector2(213, 1299)
[node name="SAida3" type="Node2D" parent="SaidasLabirinto"]
position = Vector2(472, 1298)
+[node name="car" parent="." instance=ExtResource("3_85vqe")]
+position = Vector2(-12, 179)
+
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
[connection signal="timeout" from="Spawn" to="." method="_on_spawn_timeout"]
diff --git a/project.godot b/project.godot
index 87060e7..4caa497 100644
--- a/project.godot
+++ b/project.godot
@@ -18,6 +18,7 @@ config/icon="res://icon.svg"
[autoload]
Controller="*res://scripts/controller.gd"
+Fps="*res://scenes/fps.tscn"
[display]
@@ -74,6 +75,11 @@ pause={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
+toggle_fps={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null)
+]
+}
[rendering]
diff --git a/scenes/achievement_popup.tscn b/scenes/achievement_popup.tscn
new file mode 100644
index 0000000..691ca19
--- /dev/null
+++ b/scenes/achievement_popup.tscn
@@ -0,0 +1,47 @@
+[gd_scene load_steps=2 format=3 uid="uid://wuftlm7jlgu1"]
+
+[ext_resource type="Script" uid="uid://cq5wwed4giwid" path="res://scripts/achievement_popup.gd" id="1_nkkgm"]
+
+[node name="achievement_popup" type="CanvasLayer"]
+process_mode = 3
+script = ExtResource("1_nkkgm")
+
+[node name="ColorRect" type="ColorRect" parent="."]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0.131126, 0.155787, 0.131127, 0.603922)
+
+[node name="Panel" type="Panel" parent="."]
+modulate = Color(0.443137, 0.823529, 0, 1)
+z_index = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 57.0
+grow_horizontal = 2
+
+[node name="AchievementTitleLabel" type="Label" parent="Panel/VBoxContainer"]
+layout_mode = 2
+text = "Nível desbloqueado!"
+horizontal_alignment = 1
+
+[node name="AchievementOkButton" type="Button" parent="Panel/VBoxContainer"]
+layout_mode = 2
+text = "ok
+"
+
+[connection signal="pressed" from="Panel/VBoxContainer/AchievementOkButton" to="." method="_on_achievement_ok_button_pressed"]
diff --git a/scenes/car.tscn b/scenes/car.tscn
new file mode 100644
index 0000000..993bebd
--- /dev/null
+++ b/scenes/car.tscn
@@ -0,0 +1,41 @@
+[gd_scene load_steps=5 format=3 uid="uid://1jwlt8wyr0ea"]
+
+[ext_resource type="Texture2D" uid="uid://cys6q5fgkhf8v" path="res://assets/media/images/car_sprite.png" id="1_c35m8"]
+[ext_resource type="Script" uid="uid://cm0b1p0yaiy77" path="res://scripts/car.gd" id="1_qt2eu"]
+
+[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_qt2eu"]
+radius = 7.10637
+height = 22.7064
+
+[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_50v30"]
+radius = 7.0
+height = 26.0
+
+[node name="car" type="CharacterBody2D"]
+z_index = 10
+script = ExtResource("1_qt2eu")
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture = ExtResource("1_c35m8")
+hframes = 5
+vframes = 5
+frame = 10
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2(5, 0)
+rotation = -0.690893
+scale = Vector2(5.04, 5.04)
+shape = SubResource("CapsuleShape2D_qt2eu")
+
+[node name="ClickArea" type="Area2D" parent="."]
+position = Vector2(19, 17)
+rotation = -0.733553
+scale = Vector2(4.4, 4.89964)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ClickArea"]
+position = Vector2(0, -5)
+shape = SubResource("CapsuleShape2D_50v30")
+
+[node name="DragTimer" type="Timer" parent="."]
+wait_time = 15.0
+autostart = true
diff --git a/scenes/fps.tscn b/scenes/fps.tscn
new file mode 100644
index 0000000..78f104c
--- /dev/null
+++ b/scenes/fps.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=2 format=3 uid="uid://umm2j5dxjro3"]
+
+[ext_resource type="Script" uid="uid://dby7rbf2klgm0" path="res://fps.gd" id="1_jtrh3"]
+
+[node name="FPS" type="CanvasLayer"]
+visible = false
+script = ExtResource("1_jtrh3")
+
+[node name="Label" type="Label" parent="."]
+modulate = Color(0.956863, 0.333333, 0, 1)
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -21.0
+offset_top = -33.5
+offset_right = 21.0
+grow_horizontal = 2
+grow_vertical = 0
+theme_override_font_sizes/font_size = 24
+text = "FPS"
diff --git a/scenes/ui.tscn b/scenes/ui.tscn
index 65e28b4..75602e3 100644
--- a/scenes/ui.tscn
+++ b/scenes/ui.tscn
@@ -1,4 +1,4 @@
-[gd_scene load_steps=10 format=3 uid="uid://42oeppt28feq"]
+[gd_scene load_steps=13 format=3 uid="uid://42oeppt28feq"]
[ext_resource type="Script" uid="uid://bcsw0mt21tpsw" path="res://scripts/ui.gd" id="1_nt7q6"]
[ext_resource type="VideoStream" uid="uid://bbup00575hgcg" path="res://assets/media/videos/patriotas_final_1.ogv" id="2_p7vwb"]
@@ -7,6 +7,7 @@
[ext_resource type="Theme" uid="uid://dmhlpo4s3t401" path="res://assets/themes/default_menu_theme.tres" id="3_yev5y"]
[ext_resource type="Texture2D" uid="uid://d1ar24frbwesc" path="res://assets/media/images/logo-converter.png" id="4_wm3ai"]
[ext_resource type="FontFile" uid="uid://cul3qtiyor1u1" path="res://assets/fonts/Early GameBoy.ttf" id="5_8dubc"]
+[ext_resource type="Texture2D" uid="uid://du5pfobp663lk" path="res://assets/media/images/sound-loud.svg" id="8_ktti3"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_p7vwb"]
content_margin_left = 3.0
@@ -24,6 +25,29 @@ corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7dvkv"]
+bg_color = Color(0.854902, 0, 0.137255, 0.862745)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_vdcm2"]
+bg_color = Color(0, 1, 0.443137, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.8, 0.270588, 0, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
[sub_resource type="LabelSettings" id="LabelSettings_p7vwb"]
font = ExtResource("5_8dubc")
font_size = 32
@@ -94,7 +118,7 @@ layout_mode = 2
size_flags_horizontal = 2
size_flags_vertical = 4
-[node name="Label" type="Label" parent="header/HBoxContainer/MarginContainer"]
+[node name="HighScoreLabel" type="Label" parent="header/HBoxContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 4
theme_override_colors/font_color = Color(0.960784, 0.960784, 0.960784, 1)
@@ -114,7 +138,43 @@ theme_override_fonts/font = ExtResource("5_8dubc")
theme_override_font_sizes/font_size = 14
theme_override_styles/normal = SubResource("StyleBoxFlat_p7vwb")
+[node name="VBoxContainer" type="VBoxContainer" parent="header"]
+anchors_preset = 6
+anchor_left = 1.0
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_left = -113.0
+offset_top = -310.0
+offset_right = 85.0
+offset_bottom = -251.0
+grow_horizontal = 0
+grow_vertical = 2
+scale = Vector2(0.56, 0.56)
+alignment = 1
+
+[node name="MarginContainer" type="MarginContainer" parent="header/VBoxContainer"]
+layout_mode = 2
+theme = ExtResource("3_yev5y")
+theme_override_constants/margin_bottom = 0
+
+[node name="DragTimerLabel" type="Label" parent="header/VBoxContainer/MarginContainer"]
+modulate = Color(0, 0, 0, 1)
+layout_mode = 2
+size_flags_vertical = 1
+text = "Drag: --"
+
+[node name="ShotProgressBar" type="ProgressBar" parent="header/VBoxContainer"]
+custom_minimum_size = Vector2(0, 18)
+layout_mode = 2
+theme_override_styles/background = SubResource("StyleBoxFlat_7dvkv")
+theme_override_styles/fill = SubResource("StyleBoxFlat_vdcm2")
+max_value = 1.0
+value = 1.0
+show_percentage = false
+
[node name="main_menu" type="CanvasLayer" parent="."]
+visible = false
[node name="ColorRect" type="ColorRect" parent="main_menu"]
anchors_preset = 15
@@ -333,6 +393,27 @@ text = "Back to main menu"
layout_mode = 2
text = "Quit"
+[node name="sound_icons" type="CanvasLayer" parent="."]
+
+[node name="VBoxContainer" type="VBoxContainer" parent="sound_icons"]
+anchors_preset = 6
+anchor_left = 1.0
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_left = -30.0
+offset_top = -346.0
+offset_right = 34.0
+offset_bottom = -282.0
+grow_horizontal = 0
+grow_vertical = 2
+scale = Vector2(0.439999, 0.439999)
+
+[node name="sound_toggle_button" type="TextureButton" parent="sound_icons/VBoxContainer"]
+layout_mode = 2
+texture_normal = ExtResource("8_ktti3")
+stretch_mode = 0
+
[connection signal="pressed" from="intro_container/VBoxContainer/SetupMarginContainer/SkipIntroButton" to="." method="_on_skip_intro_button_pressed"]
[connection signal="pressed" from="main_menu/VBoxContainer/ChooseLevelButton" to="." method="_on_choose_level_button_pressed"]
[connection signal="pressed" from="main_menu/VBoxContainer/SetupMarginContainer/VBoxContainer/Button_Quit_From_Main_Menu" to="." method="_on_button_quit_from_main_menu_pressed"]
@@ -344,3 +425,4 @@ text = "Quit"
[connection signal="pressed" from="game_over_menu/VBoxContainer/Button_Retry" to="." method="_on_button_retry_pressed"]
[connection signal="pressed" from="game_over_menu/VBoxContainer/Button_Back_To_Main_menu" to="." method="_on_button_back_to_main_menu_pressed"]
[connection signal="pressed" from="game_over_menu/VBoxContainer/Button_Quit_From_Game_Over" to="." method="_on_button_quit_from_game_over_pressed"]
+[connection signal="pressed" from="sound_icons/VBoxContainer/sound_toggle_button" to="." method="_on_sound_toggle_button_pressed"]
diff --git a/scripts/achievement_popup.gd b/scripts/achievement_popup.gd
new file mode 100644
index 0000000..8c25f37
--- /dev/null
+++ b/scripts/achievement_popup.gd
@@ -0,0 +1,20 @@
+extends CanvasLayer
+
+@onready var achievement_title_label: Label = $Panel/VBoxContainer/AchievementTitleLabel
+
+
+func _ready():
+ visible = false
+
+
+func show_achievement(level_name: String):
+ print("Exibindo popup de conquista:", level_name)
+ achievement_title_label.text = "Novo nível desbloqueado:\n" + level_name
+ visible = true
+ get_tree().paused = true
+
+
+func _on_achievement_ok_button_pressed() -> void:
+ visible = false
+ get_tree().paused = false
+ queue_free()
diff --git a/scripts/achievement_popup.gd.uid b/scripts/achievement_popup.gd.uid
new file mode 100644
index 0000000..a582e9f
--- /dev/null
+++ b/scripts/achievement_popup.gd.uid
@@ -0,0 +1 @@
+uid://cq5wwed4giwid
diff --git a/scripts/camera_2d.gd b/scripts/camera_2d.gd
index fe4cda4..c5240b0 100644
--- a/scripts/camera_2d.gd
+++ b/scripts/camera_2d.gd
@@ -15,7 +15,7 @@ func reset_camera():
zoom.y = 1
-func _process(delta: float) -> void:
+func _process(_delta: float) -> void:
if Input.is_action_pressed("up"):
position.y -= CameraPanSpeed
if Input.is_action_pressed("down"):
@@ -36,12 +36,16 @@ func _unhandled_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT:
if event.pressed:
+ if Controller.car_dragging:
+ dragging = false
+ return
dragging = true
last_mouse_position = get_viewport().get_mouse_position()
else:
dragging = false
- if event is InputEventMouseMotion and dragging:
+ # Só move a câmera se não estiver arrastando o carro
+ if event is InputEventMouseMotion and dragging and not Controller.car_dragging:
var current_mouse_position = get_viewport().get_mouse_position()
var delta = last_mouse_position - current_mouse_position
position += delta * zoom
diff --git a/scripts/car.gd b/scripts/car.gd
new file mode 100644
index 0000000..46aa4fa
--- /dev/null
+++ b/scripts/car.gd
@@ -0,0 +1,74 @@
+class_name Car
+extends CharacterBody2D
+
+@onready var click_area: Area2D = $ClickArea
+@onready var sprite_2d: Sprite2D = $Sprite2D
+@onready var shape: CollisionShape2D = $CollisionShape2D
+
+var dragging: bool = false
+var drag_enabled: bool = true
+var drag_time: float = 3.0
+var wait_time: float = 15.0
+
+
+func _ready():
+ click_area.input_event.connect(_on_click_area_input_event)
+
+
+func _process(_delta):
+ if dragging:
+ var mouse_pos = get_viewport().get_camera_2d().get_global_mouse_position()
+
+ global_position = mouse_pos
+ sprite_2d.scale = Vector2(1.15, 1.15)
+ shape.disabled = true
+
+
+func _on_click_area_input_event(_viewport, event, _shape_idx):
+ if event is InputEventMouseButton or event is InputEventScreenTouch:
+ if event.button_index == MOUSE_BUTTON_RIGHT:
+ if event.pressed:
+ if drag_enabled:
+ start_drag()
+ else:
+ if dragging:
+ stop_drag()
+
+
+func start_drag():
+ dragging = true
+ Controller.car_dragging = true
+ drag_enabled = true
+ await drag_duration()
+
+
+func stop_drag():
+ dragging = false
+ Controller.car_dragging = false
+ sprite_2d.scale = Vector2(1, 1)
+ shape.disabled = false
+ drag_enabled = false
+ await wait_duration()
+
+
+func drag_duration():
+ var total = drag_time
+ for i in range(total as int, 0, -1):
+ if Controller.ui:
+ Controller.ui.set_drag_timer_text("Drag: %ds" % i, Color.LIGHT_GREEN)
+ await get_tree().create_timer(1.0).timeout
+
+ if dragging:
+ stop_drag()
+
+
+func wait_duration():
+ var total = wait_time
+ for i in range(total as int, 0, -1):
+ if Controller.ui:
+ Controller.ui.set_drag_timer_text("Espera: %ds" % i, Color.SALMON)
+ await get_tree().create_timer(1.0).timeout
+
+ drag_enabled = true
+ if Controller.ui:
+ Controller.ui.set_drag_timer_text("Drag!", Color.GREEN)
diff --git a/scripts/car.gd.uid b/scripts/car.gd.uid
new file mode 100644
index 0000000..f4fab1c
--- /dev/null
+++ b/scripts/car.gd.uid
@@ -0,0 +1 @@
+uid://cm0b1p0yaiy77
diff --git a/scripts/controller.gd b/scripts/controller.gd
index 9b5f243..7842b11 100644
--- a/scripts/controller.gd
+++ b/scripts/controller.gd
@@ -10,9 +10,9 @@ var current_level: String
var current_level_path: String
var current_score := 0
var high_scores: Dictionary = {}
-
-@onready var patriota = preload("res://scenes/patriota.tscn")
-@onready var particle = preload("res://scenes/DeathParticlesRayExplosion.tscn")
+var sound_muted: bool = false
+var car_dragging: bool = false
+var can_shoot: bool = true
var levels: Dictionary = {
@@ -33,17 +33,20 @@ var levels: Dictionary = {
}
}
+@onready var patriota = preload("res://scenes/patriota.tscn")
+@onready var particle = preload("res://scenes/DeathParticlesRayExplosion.tscn")
+
func _level01() -> bool:
return true
func _level02() -> bool:
- return high_scores.get("level01", 0) >= 5
+ return high_scores.get("level01", 0) >= 1
func _level03() -> bool:
- return high_scores.get("level02", 0) >= 10
+ return high_scores.get("level02", 0) >= 1
func _ready():
@@ -64,11 +67,13 @@ func start_level():
level.timer.start()
level.spawn.start()
ui.main_menu.visible = false
- ui.label.text = "Bom jogo!"
camera_2d.reset_camera()
+ ui.header.visible = true
current_score = 0
ui.update_score(current_score)
+
+ ui.show_high_score( update_high_score() )
func restart_level():
@@ -79,7 +84,12 @@ func restart_level():
func change_level(load_level):
- var level_path = "res://levels/" + load_level
+ var level_path: String
+ if load_level.begins_with("res://"):
+ level_path = load_level
+ else:
+ level_path = "res://levels/" + load_level
+
var new_level = load(level_path).instantiate()
if level:
@@ -92,13 +102,14 @@ func change_level(load_level):
if levels[level_id]["url"] == load_level:
current_level = level_id
break
-
+
current_level_path = level_path
load_high_scores()
update_high_score()
ui._generate_level_buttons()
+
func spawn_patriota():
if total_patriotas_generated >= max_patriotas_by_level:
level.spawn.stop()
@@ -117,17 +128,10 @@ func spawn_patriota():
total_patriotas_generated += 1
-func add_point():
- current_score += 1
- ui.update_score(current_score)
-
-
func game_over():
- print("game_over")
level.visible = false
level.timer.stop()
- ui.label.text = "Game Over"
ui.pause_menu.visible = false
ui.main_menu.visible = false
ui.game_over_menu.visible = true
@@ -135,30 +139,43 @@ func game_over():
update_high_score()
ui._generate_level_buttons()
-
+ ui.show_high_score(update_high_score())
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("pause"):
toggle_pause()
-
+
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT:
- var click_position = get_viewport().get_camera_2d().get_global_mouse_position()
-
- # Dispara o som do tiro
- if ui:
- scene_manager.ray_shot.play()
+ if not can_shoot:
+ return
- # Instancia explosão onde o jogador clicou
- var new_particle = particle.instantiate()
- new_particle.global_position = click_position
- new_particle.emitting = true
- get_tree().current_scene.add_child(new_particle)
+ fire_shot()
+
+
+func fire_shot():
+ can_shoot = false
+
+ var click_position = get_viewport().get_camera_2d().get_global_mouse_position()
+
+ # Som do tiro
+ if ui:
+ scene_manager.ray_shot.play()
+
+ # Instancia explosão
+ var new_particle = particle.instantiate()
+ new_particle.global_position = click_position
+ new_particle.emitting = true
+ get_tree().current_scene.add_child(new_particle)
+
+ ui.animate_shoot_cooldown(0.45)
+ await get_tree().create_timer(0.5).timeout
+ can_shoot = true
func toggle_pause():
- if ui.main_menu.visible || ui.game_over_menu.visible:
+ if ui.main_menu.visible || ui.game_over_menu.visible || ui.intro_container.visible:
return
if ui.is_paused:
@@ -177,7 +194,6 @@ func back_to_main_menu():
get_tree().paused = false
ui.main_menu.visible = true
- ui.label.text = "Bem-vindo de volta!"
camera_2d.reset_camera()
update_high_score()
ui._generate_level_buttons()
@@ -189,12 +205,66 @@ func load_high_scores():
high_scores = file.get_var()
+func add_point():
+ current_score += 1
+ ui.update_score(current_score)
+ update_high_score()
+ check_for_unlocked_levels()
+
+
func update_high_score():
var level_id = current_level.get_basename()
if not high_scores.has(level_id):
high_scores[level_id] = 0
-
- if current_score > high_scores[level_id]:
+
+ var old_score = high_scores[level_id]
+
+ if current_score > old_score:
high_scores[level_id] = current_score
var file = FileAccess.open("user://high_scores.save", FileAccess.WRITE)
file.store_var(high_scores)
+
+ check_for_unlocked_levels()
+
+ return high_scores[level_id]
+
+
+func check_for_unlocked_levels():
+ var next_level = get_next_level_id(current_level)
+ if next_level == "":
+ return
+
+ var data = levels[next_level]
+ var unlocked = is_unlocked(next_level)
+ var already_seen = high_scores.has("_seen_" + next_level)
+
+ if unlocked and not already_seen:
+ high_scores["_seen_" + next_level] = true
+ var file = FileAccess.open("user://high_scores.save", FileAccess.WRITE)
+ file.store_var(high_scores)
+
+ # Instancia o popup dentro do level atual
+ var popup_scene = preload("res://scenes/achievement_popup.tscn")
+ var popup = popup_scene.instantiate()
+ level.add_child(popup)
+ popup.show_achievement(data["label"])
+ get_tree().paused = true
+
+
+func get_next_level_id(current: String) -> String:
+ var keys = levels.keys()
+ var current_index = keys.find(current)
+ if current_index == -1 or current_index >= keys.size() - 1:
+ return ""
+ return keys[current_index + 1]
+
+
+## Sounds
+func toggle_sound() -> void:
+ sound_muted = !sound_muted
+ AudioServer.set_bus_mute(AudioServer.get_bus_index("Master"), sound_muted)
+
+ if sound_muted:
+ ui.sound_toggle_button.texture_normal = preload("res://assets/media/images/sound-off.svg")
+ else:
+ ui.sound_toggle_button.texture_normal = preload("res://assets/media/images/sound-loud.svg")
diff --git a/scripts/level.gd b/scripts/level.gd
index d88780a..43cd3eb 100644
--- a/scripts/level.gd
+++ b/scripts/level.gd
@@ -6,12 +6,15 @@ class_name Level extends Node2D
@export var max_patriotas: int = 10
+
func _ready():
Controller.level = self
Controller.start_level()
+
func _on_timer_timeout() -> void:
Controller.game_over()
+
func _on_spawn_timeout() -> void:
Controller.spawn_patriota()
diff --git a/scripts/patriota.gd b/scripts/patriota.gd
index e9b9a03..cd702cc 100644
--- a/scripts/patriota.gd
+++ b/scripts/patriota.gd
@@ -15,7 +15,7 @@ func _ready():
click_area.input_event.connect(_on_click)
-func _on_click(viewport:Node, event:InputEvent, shape_idx:int):
+func _on_click(_viewport:Node, event:InputEvent, _shape_idx:int):
if (event is InputEventMouseButton or event is InputEventScreenTouch) and event.pressed:
if event.button_index == MOUSE_BUTTON_LEFT:
plock_sound.play()
@@ -42,7 +42,7 @@ func kill():
var _particle = preload("res://scenes/DeathParticlesBloodExplosion.tscn").instantiate()
animation_player.stop()
- click_area.set_deferred("disabled", true) # evita múltiplos cliques
+ click_area.set_deferred("disabled", true)
set_physics_process(false)
await get_tree().create_timer(0.15).timeout
diff --git a/scripts/scene_manager.gd b/scripts/scene_manager.gd
index 80a2ba0..98375c1 100644
--- a/scripts/scene_manager.gd
+++ b/scripts/scene_manager.gd
@@ -3,6 +3,7 @@ class_name SceneManager extends Node2D
@onready var sound_track: AudioStreamPlayer = $SoundPlayer/SoundTrack
@onready var ray_shot: AudioStreamPlayer = $SoundPlayer/RayShot
+
func _ready():
Controller.scene_manager = self
diff --git a/scripts/ui.gd b/scripts/ui.gd
index 5b5667e..c56798d 100644
--- a/scripts/ui.gd
+++ b/scripts/ui.gd
@@ -2,7 +2,7 @@ class_name UI extends Node2D
@onready var main_menu: CanvasLayer = $main_menu
@onready var header: CanvasLayer = $header
-@onready var label: Label = $header/HBoxContainer/MarginContainer/Label
+@onready var high_score_label: Label = $header/HBoxContainer/MarginContainer/HighScoreLabel
@onready var level_menu: CanvasLayer = $level_menu
@onready var pause_menu: CanvasLayer = $pause_menu
@onready var game_over_menu: CanvasLayer = $game_over_menu
@@ -10,6 +10,9 @@ class_name UI extends Node2D
@onready var intro_1: VideoStreamPlayer = $intro_container/intro1
@onready var intro_2: VideoStreamPlayer = $intro_container/intro2
@onready var score_label: Label = $header/HBoxContainer/MarginContainer2/ScoreLabel
+@onready var sound_toggle_button: TextureButton = $sound_icons/VBoxContainer/sound_toggle_button
+@onready var drag_timer_label: Label = $header/VBoxContainer/MarginContainer/DragTimerLabel
+@onready var shot_progress_bar: ProgressBar = $header/VBoxContainer/ShotProgressBar
var is_paused: bool = false
@@ -19,6 +22,7 @@ func _ready():
main_menu.visible = false
game_over_menu.visible = false
+ header.visible = false
pause_menu.process_mode = Node.PROCESS_MODE_ALWAYS
hide_pause_menu()
@@ -32,6 +36,7 @@ func _ready():
_generate_level_buttons()
Controller.load_high_scores()
+
## Intro
@@ -54,11 +59,6 @@ func _on_skip_intro_button_pressed() -> void:
main_menu.visible = true
-## Score
-func update_score(current_score):
- score_label.text = "Kills: %d" % current_score
-
-
## Main menu
func _on_choose_level_button_pressed() -> void:
main_menu.visible = false
@@ -149,16 +149,15 @@ func _generate_level_buttons():
for level_id in level_keys:
var level_data = Controller.levels[level_id]
var label = level_data["label"]
- var level_path = "res://levels/" + level_data["url"]
+ var _level_path = "res://levels/" + level_data["url"]
var button = Button.new()
- var high_score = Controller.high_scores.get(level_id, 0)
var is_unlocked = Controller.is_unlocked(level_id)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
if is_unlocked:
- button.text = " %s - High Score: %d" % [label, high_score]
+ button.text = " " + label + " "
button.disabled = false
button.pressed.connect(func():
level_menu.visible = false
@@ -176,3 +175,33 @@ func _on_back_to_main_menu_pressed() -> void:
level_menu.visible = false
get_tree().paused = false
Controller.back_to_main_menu()
+
+
+## Score
+func update_score(current_score):
+ score_label.text = "Score: %d" % current_score
+
+
+func show_high_score(high_score):
+ high_score_label.text = "High Score: %d" % high_score
+
+
+## Sounds
+func _on_sound_toggle_button_pressed() -> void:
+ Controller.toggle_sound()
+
+
+func set_drag_timer_text(text: String, color: Color = Color.WHITE):
+ drag_timer_label.text = text
+ drag_timer_label.modulate = color
+
+
+func animate_shoot_cooldown(duration: float) -> void:
+ shot_progress_bar.value = 0.0
+
+ var steps := 10
+ for i in range(steps + 1):
+ shot_progress_bar.value = i / float(steps)
+ await get_tree().create_timer(duration / steps).timeout
+
+ shot_progress_bar.value = 1.0