diff --git a/.godot/editor/editor_layout.cfg b/.godot/editor/editor_layout.cfg index dffe9f5..f165a97 100644 --- a/.godot/editor/editor_layout.cfg +++ b/.godot/editor/editor_layout.cfg @@ -19,7 +19,7 @@ dock_filesystem_split=0 dock_filesystem_display_mode=0 dock_filesystem_file_sort=0 dock_filesystem_file_list_display_mode=1 -dock_filesystem_selected_paths=PackedStringArray("res://") +dock_filesystem_selected_paths=PackedStringArray("res://Player.tscn") dock_filesystem_uncollapsed_paths=PackedStringArray("Favorites", "res://") dock_3="Scene,Import" dock_4="FileSystem" @@ -27,17 +27,17 @@ dock_5="Inspector,Node,History" [EditorNode] -open_scenes=PackedStringArray("res://Main.tscn") -current_scene="res://Main.tscn" +open_scenes=PackedStringArray("res://Main.tscn", "res://Player.tscn") +current_scene="res://Player.tscn" center_split_offset=0 selected_default_debugger_tab_idx=0 selected_main_editor_idx=2 -selected_bottom_panel_item=1 +selected_bottom_panel_item=0 [ScriptEditor] -open_scripts=["res://Camera2D.gd", "res://Player.gd"] -selected_script="res://Camera2D.gd" +open_scripts=["res://Main.gd", "res://Player.gd", "res://Tower.gd", "res://UI.gd"] +selected_script="res://Player.gd" open_help=[] script_split_offset=70 list_split_offset=0 diff --git a/.godot/editor/filesystem_cache8 b/.godot/editor/filesystem_cache8 index 9e03851..a5beb8a 100644 --- a/.godot/editor/filesystem_cache8 +++ b/.godot/editor/filesystem_cache8 @@ -1,14 +1,14 @@ ea4bc82a6ad023ab7ee23ee620429895 -::res://::1713198935 -Camera2D.gd::GDScript::-1::1713198916::0::1::::<>Node<>:: -default_env.tres::Environment::-1::1713198916::0::1::::<><>:: -icon.png::CompressedTexture2D::224231617940432086::1713198714::1713198936::1::::<><>:: -Main.tscn::PackedScene::-1::1713198916::0::1::::<><>::res://Camera2D.gd<>res://Player.tscn<>res://res/sprites.png<>res://UI.tscn -Player.gd::GDScript::-1::1713198916::0::1::::<>Area2D<>:: -Player.tscn::PackedScene::-1::1713198916::0::1::::<><>::res://Player.gd<>res://res/sprites.png -Tower.gd::GDScript::-1::1713198916::0::1::::<>Node2D<>:: -Tower.tscn::PackedScene::-1::1713198916::0::1::::<><>::res://Tower.gd<>res://res/sprites.png -UI.gd::GDScript::-1::1713198916::0::1::::<>CanvasLayer<>:: -UI.tscn::PackedScene::-1::1713198916::0::1::::<><>::res://UI.gd -::res://res/::1713198714 -sprites.png::CompressedTexture2D::1870513174833790166::1713198714::1713198936::1::::<><>:: +::res://::1713445642 +default_env.tres::Environment::2706437284641004306::1713444527::0::1::::<><>:: +icon.png::CompressedTexture2D::224231617940432086::1713444527::1713444527::1::::<><>:: +Main.gd::GDScript::-1::1713444527::0::1::::<>Node<>:: +Main.tscn::PackedScene::798496173305188108::1713445635::0::1::::<><>::res://Main.gd<>res://Player.tscn<>uid://1wdcw8ne7j5w::::res://res/sprites.png<>res://UI.tscn +Player.gd::GDScript::-1::1713444527::0::1::::<>Area2D<>:: +Player.tscn::PackedScene::-1::1713444527::0::1::::<><>::res://Player.gd<>res://res/sprites.png +Tower.gd::GDScript::-1::1713444527::0::1::::<>Node2D<>:: +Tower.tscn::PackedScene::-1::1713444527::0::1::::<><>::res://Tower.gd<>res://res/sprites.png +UI.gd::GDScript::-1::1713444527::0::1::::<>CanvasLayer<>:: +UI.tscn::PackedScene::-1::1713444527::0::1::::<><>::res://UI.gd +::res://res/::1713444527 +sprites.png::CompressedTexture2D::1870513174833790166::1713444527::1713444527::1::::<><>:: diff --git a/.godot/editor/filesystem_update4 b/.godot/editor/filesystem_update4 index f32698d..bd345ee 100644 --- a/.godot/editor/filesystem_update4 +++ b/.godot/editor/filesystem_update4 @@ -1,4 +1,6 @@ res://default_env.tres res://Main.tscn -res://Camera2D.gd +res://Player.tscn res://Player.gd +res://Tower.gd +res://Main.gd diff --git a/.godot/editor/project_metadata.cfg b/.godot/editor/project_metadata.cfg index 2d06c85..46f9a34 100644 --- a/.godot/editor/project_metadata.cfg +++ b/.godot/editor/project_metadata.cfg @@ -1,6 +1,6 @@ [editor_metadata] -executable_path="C:/Users/Gebruiker/Desktop/Godot_v4.2.1-stable_win64.exe/Godot_v4.2.1-stable_win64.exe" +executable_path="/home/vincent/Documents/programming/godot/Godot_v4.2.1-stable_linux.x86_64" [debug_options] @@ -9,8 +9,8 @@ run_reload_scripts=true [recent_files] -scenes=["res://Main.tscn"] -scripts=["res://Player.gd", "res://Camera2D.gd"] +scenes=["res://Player.tscn", "res://Main.tscn"] +scripts=["res://UI.gd", "res://Main.gd", "res://Tower.gd", "res://Player.gd", "res://Camera2D.gd"] [linked_properties] @@ -18,3 +18,4 @@ Node2D:scale=true Sprite2D:scale=true ParallaxLayer:motion_scale=true ParallaxLayer:scale=true +Area2D:scale=true diff --git a/.godot/editor/script_editor_cache.cfg b/.godot/editor/script_editor_cache.cfg index 0367be0..defda25 100644 --- a/.godot/editor/script_editor_cache.cfg +++ b/.godot/editor/script_editor_cache.cfg @@ -1,26 +1,54 @@ -[res://Camera2D.gd] - -state={ -"bookmarks": PackedInt32Array(), -"breakpoints": PackedInt32Array(), -"column": 12, -"folded_lines": Array[int]([]), -"h_scroll_position": 0, -"row": 41, -"scroll_position": 0.0, -"selection": false, -"syntax_highlighter": "GDScript" -} - [res://Player.gd] state={ "bookmarks": PackedInt32Array(), "breakpoints": PackedInt32Array(), -"column": 46, +"column": 1, "folded_lines": Array[int]([]), "h_scroll_position": 0, -"row": 8, +"row": 68, +"scroll_position": 25.0, +"selection": false, +"syntax_highlighter": "GDScript" +} + +[res://Tower.gd] + +state={ +"bookmarks": PackedInt32Array(), +"breakpoints": PackedInt32Array(), +"column": 0, +"folded_lines": Array[int]([]), +"h_scroll_position": 0, +"row": 16, +"scroll_position": 0.0, +"selection": false, +"syntax_highlighter": "GDScript" +} + +[res://Main.gd] + +state={ +"bookmarks": PackedInt32Array(), +"breakpoints": PackedInt32Array(), +"column": 0, +"folded_lines": Array[int]([]), +"h_scroll_position": 0, +"row": 25, +"scroll_position": 0.0, +"selection": false, +"syntax_highlighter": "GDScript" +} + +[res://UI.gd] + +state={ +"bookmarks": PackedInt32Array(), +"breakpoints": PackedInt32Array(), +"column": 0, +"folded_lines": Array[int]([]), +"h_scroll_position": 0, +"row": 19, "scroll_position": 0.0, "selection": false, "syntax_highlighter": "GDScript" diff --git a/.godot/uid_cache.bin b/.godot/uid_cache.bin index 7a0b4b4..e6f1a93 100644 Binary files a/.godot/uid_cache.bin and b/.godot/uid_cache.bin differ diff --git a/Camera2D.gd b/Main.gd similarity index 78% rename from Camera2D.gd rename to Main.gd index 5498259..26c5d57 100644 --- a/Camera2D.gd +++ b/Main.gd @@ -1,17 +1,16 @@ extends Node -@export var Tower = preload("res://Tower.tscn"); - @onready var screen_size = Vector2(ProjectSettings.get("display/window/size/viewport_width"), ProjectSettings.get("display/window/size/viewport_height")) -@export var tower_density = 250 - -signal spawn - -var last_built = 300 - +# Tower variables +@export var Tower = preload("res://Tower.tscn"); # Tower scene +@export var tower_density = 250 # How often a tower should be added +var last_built = 300 # Helper variable to adhear the tower density var towers = [] +signal spawn # A tower is spawned in + +# Build a single tower func build_tower(): var tower = Tower.instantiate() tower.connect("exit", Callable(self, "_on_Tower_exit")) @@ -25,6 +24,7 @@ func build_tower(): add_child(tower) last_built += tower_density +# Build all towers func build_all_towers(): get_tree().call_group("tower", "queue_free") last_built = 300 @@ -32,6 +32,7 @@ func build_all_towers(): build_tower() func _ready(): + # Connect signals var ui = get_node("UI") var player = get_node("Player") player.connect("start", Callable(self, "build_all_towers")) diff --git a/Main.tscn b/Main.tscn index 771a6e8..f5790e1 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=6 format=3 uid="uid://lm2d6kimhksi"] -[ext_resource type="Script" path="res://Camera2D.gd" id="1"] -[ext_resource type="PackedScene" path="res://Player.tscn" id="2"] +[ext_resource type="Script" path="res://Main.gd" id="1"] +[ext_resource type="PackedScene" uid="uid://bkppr8vsso41l" path="res://Player.tscn" id="2"] [ext_resource type="Texture2D" uid="uid://1wdcw8ne7j5w" path="res://res/sprites.png" id="3"] [ext_resource type="PackedScene" path="res://UI.tscn" id="4"] diff --git a/Player.gd b/Player.gd index c103144..d30e672 100644 --- a/Player.gd +++ b/Player.gd @@ -25,9 +25,9 @@ func start_game(): # Called when the node enters the scene tree for the first time. func _ready(): screen_size = get_viewport_rect().size - # x_offset = get_global_transform().get_origin().x -func _process(delta): +# Called every frame +func _process(_delta): if Input.is_action_just_pressed("ui_accept"): if not playing: start_game() @@ -40,6 +40,7 @@ func _process(delta): if not playing: return + # Adjust the birds position speed_y += speed_inc position += Vector2(speed, speed_y * speed) @@ -49,17 +50,18 @@ func _process(delta): emit_signal("passed") tower_locations.pop_front() + # Rotate the bird update_rotation() if position.y > screen_size.y: emit_signal("hit") - print(position) playing = false func update_rotation(): rotation = atan(speed_y / speed) -func _on_Player_body_entered(body): +# Hit detection +func _on_Player_body_entered(_body): playing = false start_game() diff --git a/Player.tscn b/Player.tscn index a3746be..55ed7df 100644 --- a/Player.tscn +++ b/Player.tscn @@ -1,60 +1,64 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=7 format=3 uid="uid://bkppr8vsso41l"] -[ext_resource path="res://Player.gd" type="Script" id=1] -[ext_resource path="res://res/sprites.png" type="Texture2D" id=2] +[ext_resource type="Script" path="res://Player.gd" id="1"] +[ext_resource type="Texture2D" uid="uid://1wdcw8ne7j5w" path="res://res/sprites.png" id="2"] -[sub_resource type="AtlasTexture" id=1] -atlas = ExtResource( 2 ) -region = Rect2( -4, 488, 84, 16 ) +[sub_resource type="AtlasTexture" id="1"] +atlas = ExtResource("2") +region = Rect2(-4, 488, 84, 16) -[sub_resource type="Animation" id=2] +[sub_resource type="Animation" id="2"] length = 0.8 -loop = true +loop_mode = 1 step = 0.2 tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true tracks/0/path = NodePath("Node2D/Sprite2D:frame") tracks/0/interp = 1 tracks/0/loop_wrap = true -tracks/0/imported = false -tracks/0/enabled = true tracks/0/keys = { -"times": PackedFloat32Array( 0, 0.2, 0.4, 0.6 ), -"transitions": PackedFloat32Array( 1, 1, 1, 1 ), +"times": PackedFloat32Array(0, 0.2, 0.4, 0.6), +"transitions": PackedFloat32Array(1, 1, 1, 1), "update": 1, -"values": [ 0, 1, 2, 1 ] +"values": [0, 1, 2, 1] } -[sub_resource type="CapsuleShape2D" id=3] -radius = 12.8837 +[sub_resource type="AnimationLibrary" id="AnimationLibrary_s168r"] +_data = { +"fly": SubResource("2") +} + +[sub_resource type="CapsuleShape2D" id="3"] +radius = 2.03742 height = 4.07484 [node name="Area2D" type="Area2D"] -position = Vector2( 9.25964, 6.09653 ) -scale = Vector2( 1, 1.02134 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_group_": true -} +position = Vector2(9.25964, 6.09653) +scale = Vector2(1, 1.02134) +script = ExtResource("1") [node name="Node2D" type="Node2D" parent="."] [node name="Sprite2D" type="Sprite2D" parent="Node2D"] -position = Vector2( -3.46641, -2.14102 ) -scale = Vector2( 2.14565, 2.14019 ) -texture = SubResource( 1 ) +position = Vector2(-3.46641, -2.14102) +scale = Vector2(2.14565, 2.14019) +texture = SubResource("1") hframes = 3 [node name="AnimationPlayer" type="AnimationPlayer" parent="Node2D"] root_node = NodePath("../..") +libraries = { +"": SubResource("AnimationLibrary_s168r") +} autoplay = "fly" -anims/fly = SubResource( 2 ) [node name="CollisionShape2D" type="CollisionShape2D" parent="."] rotation = 1.5708 -shape = SubResource( 3 ) +shape = SubResource("3") [node name="Camera2D" type="Camera2D" parent="."] -current = true limit_top = 0 limit_bottom = 0 + [connection signal="body_entered" from="." to="." method="_on_Player_body_entered"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d285e64 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Flappy Bird + +This repository contains an example solution for the [Flappy Bird game](https://git.zeus.gent/godot-introduction/getting-started/src/branch/master/flappy-bird). + +It's important to note that this is not the only possible solution; yours might differ. + +Now that you have starting solution, it's time to add some new and exciting features! +However ,before you start adding new features, it might be a good idea to fix all lingering bugs. + +## Bugs + +It's a good exercise to try and find a solution on your own; however, if you're really stuck we've added some pointers in the `bugs_spoilers.md` file. + +1. It's possible to jump above the pipes. By spamming SPACE you'll never die. +2. When you die because of hitting a pipe, the bird gets reset to the starting position. It would be nicer if it had the same type of ending screen as when you hit the gound, which is the last frame you see. + +## New Features + +We're using the same method as for the bugs. You can find some pointers, if necessary, on where to begin in `features_spoilers.md`. + +### Basic + +1. Randomize the color of the pipes +2. Give the player 3 lives. +3. Increase the speed of the bird as the game progresses + +### Medium + +1. Enchance the UI +2. Add Coins +3. Randomize the size of the gap between pipes + +### Advanced + +1. Support multiple players +2. Powerups +3. Add a new type of pipe that moves + +### Super Advanced + +1. Monetize the game by adding a battlepass \ No newline at end of file diff --git a/Tower.gd b/Tower.gd index cb49cc6..70f97fb 100644 --- a/Tower.gd +++ b/Tower.gd @@ -8,7 +8,7 @@ func _ready(): add_to_group("tower") pass # Replace with function body. -func _on_Tower_body_entered(body): +func _on_Tower_body_entered(_body): emit_signal("hit") func _on_Visibility_screen_exited(): diff --git a/bugs_spoilers.md b/bugs_spoilers.md new file mode 100644 index 0000000..17c8314 --- /dev/null +++ b/bugs_spoilers.md @@ -0,0 +1,17 @@ +# Bugs Solution Pointers + +As bugs are unique to a certain solution, we'll make references to the code of the example solution. If your own solution has the same bug, the general fix idea can be reused. + +## 1. No height limit + +As you might have noticed, the game ends when you hit the floor. This gives you the opportunity to recycle some code! + +You can look at how that is done in `Player.gd`. + + +## 2. Ending Screen + +Again, you can look at already existing code. Try to look at the differences when you hit the floor and when you hit a tower. + +You already know where the check is done if you hit the floor. Checking if a tower is hit is done in the same file. +Hint: Select the Area2D node and look at the attached signals diff --git a/features_spoiler.md b/features_spoiler.md new file mode 100644 index 0000000..2e7370f --- /dev/null +++ b/features_spoiler.md @@ -0,0 +1,87 @@ +# Features Solution Pointers + +As we want to support self-made solutions as well as the example solution, we're going to give pointers on where to start to implement these features. However, we'll avoid hard references to the example solution. + +## Basic + +### 1. Randomize Pipe Color + +You should have a Sprite node for your towers. Presumably, that node has the tower texture, which you can see on the right-hand side of your screen in the `Inspector` tab. Look for the `visibility` item underneath `CanvasItem`. In there, you'll find `Modulate`. You can use modulate to apply a filter over the texture. Select a color by clicking on the white color picker. Load up your game, and you'll see that the colors of all the pipes have changed! + +Now it's up to you to convert it to code and to randomize the value for each pipe :) + +### 2. 3 Lives + +This feature consists of 3 subproblems: + +1. How do you count the number of times the bird has died? +2. How do you show this information to the user? +3. What do you do when someone dies but still has a life left? + +The first problem (1) is the easiest. You can use a variable to keep track of the number of lives. +The second problem (2) is already a bit trickier. You'll have to add UI to display this information. You should already have a way to display the current and best score. You can add the number of lives to that scene. +The most difficult problem is the last one. There are multiple possible solutions, but we'll only go over one in detail: invincibility. +A solution is to make the user invincible for the next x seconds. We recommend using a Timer node. There's only one problem left: what if the bird died because they hit the ground or ceiling? Do you teleport them to the center of the screen? Do you restrict it so that they can't go out of the screen, or do you allow them to go out of the screen and deduct another life when the invincibility timer ends? +We'll let you decide; part of game-making is deciding which mechanics and game logic make the most sense for your project. + +### 3. Speed Increase + +This one sounds very easy, but multiplying the speed variable by a percentage every time `_process` is called isn't the right way. That particular function might get called more often on your PC than on mine. Try to think of a fair way to increase the speed at the same rate for everyone! + +## Medium + +### 1. UI + +It's time to enhance the UI! +Do you want to go for an old-school UI or a more modern layout? It's all up to you! +You can make it as difficult as you like. +Some ideas are: + +- Display icons for the amount of lives left. +- Add a startup screen. +- Support a toggleable night mode background (sprite is included). +- Use sprite numbers to show the current score. + +### 2. Coins + +Depending on your implementation of Flappy Bird, you might need to add a new scene. +Think about what nodes the coins should exist. It should have a sprite, and you should be able to detect any collisions. Therefore, we suggest using a `Sprite2D` and a `CollisionShape2D` inside an `Area2D`. Once you have your scene layout, it's time to think about the signals. You can get away with only one signal, `area_exited`. It's up to you if you want to connect to the signal from the `Coin` scene or the `Player` scene. +The last two steps are spawning in the coins and showing the data. We'll let solving those parts over to you. + +### 3. Random Pipe Gaps + +Depending on your implementation, this might be very easy or might prove to be quite difficult. We're going to assume you're using the same logic as the example implementation. + +## Advanced + +If you've reached this point, we're assuming that you're almost a Godot master. Unfortunately, this means the tips on how to achieve the following features will be almost nonexistent. + +### 1. Multiplayer + +As the author of this document has 0 experience with multiplayer, you're unfortunately on your own! But don't be afraid; I've heard it's easier than you would think... + +### 2. Powerups + +Be creative! You can make this as easy or as hard as you want. Some ideas are: + +- Extra life. +- Speed boost + invincibility. +- Coin magnet. +- Ability to knock down pipes for a certain period. + +### 3. Add a new type of pipe that moves + +The biggest challenge might be deciding which node to use. Choose carefully between + +- Area2D +- StaticBody2D +- CharacterBody2D +- RigidBody2D + +This [part of the documentation](https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html) will help + +## Super Advanced + +### 1. Battlepass + +Please don't.