Pick up coins and watch a counter climb. Introduces signals: one event, two different files reacting to it.
Loading the game engine. This takes a moment the first time.
game_events.gd creates it, with one line: signal coin_collected(value: int).hud.gd and this file each call .connect() one time, naming a function to run: .connect(_on_coin_collected).coin.gd calls .emit(value), Godot runs the function each file named. One emit, two functions run.To trace any signal: highlight its name and press Ctrl + Shift + F.
None of the earlier lessons use these. Each one shows the line where it appears here.
Writes to the Output panel at the bottom of the Godot editor. Handy for checking what a value really is.
print("All coins collected!")True when the node has that group ticked in the Node dock. It is how a coin spots the player.
if body.is_in_group("Player"):■ UI node · ■ 2D node · % unique name · .tscn instanced scene
game_events.gd
Switch it on once, under Project > Project Settings, on the Globals tab. Not made yet? Press Create Script to make it right there. Already made it? Press Select Script/Scene, pick it, and press Add. Its Name column should say GameEvents, which Godot fills in for you. That's the name your code uses.
extends Node2D
## How many coins are in this level. Change this if you add coins!
@export var total_coins: int = 3
var collected_coins: int = 0
func _ready() -> void:
# We call ".connect" on the signal "coin_collected", and we put the name
# "_on_coin_collected" inside it. That is the function Godot will run,
# written on line 35 below.
# hud.gd connects to this same signal, but names its own function.
GameEvents.coin_collected.connect(_on_coin_collected)
# The "_" in "_value" means we ignore it. hud.gd is the file that uses "value".
func _on_coin_collected(_value: int) -> void:
collected_coins += 1
# "print()" shows up in Godot's Output panel. The player never sees it.
if collected_coins >= total_coins:
print("All coins collected!")extends Area2D
## How many points this coin is worth.
@export var value: int = 1
func _ready() -> void:
# "body_entered" is a SIGNAL that Godot builds into every Area2D.
# The coin emits it whenever something touches the coin.
#
# "connect" is a function that every signal has. Piece by piece:
# body_entered the signal we are waiting for
# .connect run that signal's connect function
# (_on_body_entered) our function, written on line 32 below
# No "()" after "_on_body_entered": we hand Godot the NAME so it
# can run it later. Adding "()" would run it right now instead.
body_entered.connect(_on_body_entered)
# "body" is whatever touched the coin. Godot fills it in for us when
# it emits "body_entered".
func _on_body_entered(body: Node2D) -> void:
# "Player" is a group ticked on the player's root node (Node dock >
# Groups). Anything else that bumps the coin is not in it, so it is
# ignored.
if body.is_in_group("Player"):
# "coin_collected" is a signal we wrote ourselves, over in
# game_events.gd. ".emit(value)" sends it out, carrying "value".
# Two files connect to it: hud.gd (adds the score) and
# game.gd (counts how many are left).
GameEvents.coin_collected.emit(value)
# "queue_free()" erases the coin. Emit first, erase second, or
# the coin is gone before it can send the signal.
queue_free()extends CanvasLayer
# This file owns "score". The coins never touch this number.
var score: int = 0
# "%ScoreLabel" means "the node named ScoreLabel in this scene."
@onready var score_label: Label = %ScoreLabel
@onready var home_button: Button = %HomeButton
func _ready() -> void:
# "coin_collected" is a signal we wrote ourselves in game_events.gd.
# coin.gd emits it. We call ".connect" on it here to wait for it: when
# it arrives, run our "_on_coin_collected" function on line 33.
GameEvents.coin_collected.connect(_on_coin_collected)
# "pressed" is a signal Godot builds into every Button. Same pattern:
# when HomeButton emits "pressed", run "_on_home_pressed" on line 29.
home_button.pressed.connect(_on_home_pressed)
_update_label() # show "Coins: 0" before anything is picked up
# Loads the lesson list screen, which replaces this whole scene.
func _on_home_pressed() -> void:
get_tree().change_scene_to_file("res://shared/ui/lesson_select.tscn")
# "value" is the number coin.gd sent along with the signal. It is 1.
func _on_coin_collected(value: int) -> void:
score += value # "+=" means add this on to what we already had
_update_label()
# "_update_label()" is its own function because "_ready()" and
# "_on_coin_collected()" both need it.
func _update_label() -> void:
# "%d" is a blank spot the number drops into. Score of 2 = "Coins: 2"
score_label.text = "Coins: %d" % scoreclass_name Player
extends CharacterBody2D
## How fast the player moves. "@export" puts "speed" at the top of the
## Inspector panel, so you can change it there instead of in this file.
@export var speed: float = 200.0
@onready var sprite: Sprite2D = $Sprite2D
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# "Input.get_vector(...)" checks all four keys at once and hands back
# an arrow pointing where to go. The four names in quotes are ours.
# See them under Project > Project Settings > Input Map.
var direction: Vector2 = Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
# "direction" is only WHICH WAY (its length is always 1).
# "direction * speed" stretches it, so "velocity" is also HOW FAST.
velocity = direction * speed
# Nothing moves until "move_and_slide()" runs.
# "Slide" means we slide along walls instead of sticking to them.
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if direction.x != 0.0:
sprite.flip_h = direction.x < 0.0extends CharacterBody2D
## How fast the player runs left and right.
@export var speed: float = 200.0
## How hard the player jumps. NEGATIVE because in Godot up is the negative
## direction. The top of the screen is 0 and the numbers grow going down.
@export var jump_velocity: float = -620.0
## How hard gravity pulls down. A bigger number means you fall faster.
@export var gravity: float = 1800.0
@onready var sprite: Sprite2D = $Sprite2D
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
velocity.x = Input.get_axis("move_left", "move_right") * speed
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if velocity.x != 0.0:
sprite.flip_h = velocity.x < 0.0extends Node
# All of the game's signals are declared below, grouped by the lesson
# that uses them. A signal with brackets, like coin_collected(value: int),
# passes that value to every function connected to it.
# This shows only the signals this lesson uses. The full file declares
# every lesson's signals.
# Coin Pickup lesson
signal coin_collected(value: int)