Welcome to the third lesson. A scrolling background is great, but we need actual gameplay. In this module, we will create the iconic green pipes, spawn them continuously at randomized heights, and detect when the bird successfully navigates through them.
1. Designing the Obstacle Scene#
First, we need to create the pipe object that we will clone (instance) repeatedly via code.
- Create a new scene with a Node2D as the root and name it
PipeSet. - Add two StaticBody2D nodes as children. Name one
TopPipeand the otherBottomPipe. - For each
StaticBody2D, add a Sprite2D and a CollisionShape2D (use aRectangleShape2D). Position them so there is a gap in the middle for the bird to fly through.
Creating the Score Zone#
We need to know when the player successfully passes a pipe.
- Add an Area2D node to the
PipeSetroot and name itScoreZone. - Add a CollisionShape2D to it, covering the empty gap between the top and bottom pipes.
- Connect the
body_enteredsignal of theScoreZoneto thePipeSetscript to emit a customscoredsignal.
Adding Game Juice: To make scoring feel rewarding, add a CPUParticles2D node to the ScoreZone. Set it to emit briefly (One Shot) when the bird passes through. CPU particles are highly performant for 2D UI and simple effects like bursting confetti or stars upon scoring.
2. The Spawner Script#
Now, head back to your Main scene. We need a system to generate these PipeSet scenes at regular intervals.
- Add a Timer node to the
Mainscene. Name itSpawnTimer, set its Wait Time to1.5seconds, and check Autostart. - Create a new Marker2D node named
SpawnPosition. Place it just outside the right edge of your viewport (e.g.,x: 520).
Attach a script to your Main node to handle the logic:
extends Node2D
@export var pipe_scene: PackedScene
@onready var spawn_timer: Timer = $SpawnTimer
@onready var spawn_position: Marker2D = $SpawnPosition
func _ready() -> void:
# Connect the timer's timeout signal
spawn_timer.timeout.connect(_on_spawn_timer_timeout)
func _on_spawn_timer_timeout() -> void:
var new_pipe = pipe_scene.instantiate()
# Randomize the Y position for varied gameplay
var random_y = randf_range(200.0, 600.0)
new_pipe.position = Vector2(spawn_position.position.x, random_y)
# Add the pipe to the scene tree
add_child(new_pipe)
3. Memory Management#
If we keep spawning pipes infinitely, our game will eventually crash from memory exhaustion. We must delete pipes once they leave the screen.
- Open your
PipeSetscene. - Add a VisibleOnScreenNotifier2D node.
- Stretch its bounding box to cover the entire pipe structure.
- Connect its
screen_exitedsignal to thePipeSetscript and callqueue_free().
extends Node2D
# ... score logic ...
func _on_visible_on_screen_notifier_2d_screen_exited() -> void:
queue_free() # Safely deletes the pipe from memory
Your game now endlessly generates obstacles and cleans them up automatically!
.webp&w=1920&q=75)
