What Are Godot Signals? signal, connect, and emit Explained

A practical explanation of Godot signals using a coin collection example: what signal means, how to connect signals, when to emit them, and how Main can receive Coin's collected signal to update the score.

In Godot, a signal is a notification sent by a node.

Something happened. Whoever cares about it can respond.

Examples:

  • Button: I was pressed.
  • Coin: I was collected by the player.
  • Player: My health changed.
  • Enemy: I died.
  • Timer: Time is up.

Other nodes can connect to these signals and run a function when the signal is emitted. The sender does not need to directly find the receiver, which reduces coupling between nodes.

The three core steps

A complete signal flow usually has three steps:

  1. Declare the signal.
  2. Connect the signal.
  3. Emit the signal.

Think of it like a doorbell:

Concept Analogy
signal Install the doorbell
connect Wire the doorbell to the indoor chime
emit Press the doorbell
Receiver function Someone hears it and opens the door

The key memory aid is:

1
2
3
signal  = define the notification
connect = assign a receiver
emit    = send the notification

Two kinds of signals in Godot

Godot signals commonly fall into two types: built-in signals and custom signals.

Built-in signals

Built-in signals are signals already provided by Godot nodes.

Common examples:

  • Button.pressed
  • Timer.timeout
  • Area2D.body_entered
  • Area2D.body_exited

If a coin node extends Area2D, it already has the body_entered signal. When a CharacterBody2D enters the coin’s collision area, Godot emits that signal automatically.

Custom signals

Custom signals are declared by us.

For example, a coin being collected:

1
signal collected

This collected signal is not built into Godot. It is a notification type we define ourselves.

Two signal layers in the coin example

A simple coin script can look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extends Area2D

signal collected


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit()
    queue_free()

This script actually uses two signal layers.

First layer: body_entered

This line:

1
body_entered.connect(_on_body_entered)

means:

When a physics object enters Coin’s Area2D, call _on_body_entered().

The flow is:

1
2
3
4
5
Player enters the coin collision area
Area2D emits body_entered
_on_body_entered(body) runs

The body parameter is the node that entered the coin area. If Player enters the area, then:

1
body == Player

Second layer: collected

This line:

1
signal collected

declares a signal, meaning Coin can send a notification called collected.

Then:

1
collected.emit()

means:

This coin has been collected.

But remember: emitting a signal does not guarantee that anyone receives it.

If no node is connected to collected, the signal is still emitted, but nothing else happens. The coin disappears because of this line:

1
queue_free()

It does not disappear automatically because of collected.emit().

Full communication flow

When the player touches the coin, the full flow is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Player enters Coin
Coin's Area2D emits body_entered
Coin runs _on_body_entered()
It confirms the entering node is Player
Coin emits collected
Main receives collected
Main increases the score
Coin runs queue_free()
The coin disappears

There are two senders here:

1
2
Area2D → emits body_entered
Coin   → emits collected

body_entered tells Coin: “Something entered your area.”

collected tells Main: “This coin was collected.”

How Main receives the coin signal

Assume this scene structure:

1
2
3
4
5
Main  Node2D
├─ Player
├─ Coin
└─ CanvasLayer
   └─ ScoreLabel

You can connect the signal in the editor or in code.

Method 1: connect in the Godot editor

Open main.tscn.

First, select this node in the scene tree:

1
Coin

Second, switch the right panel from Inspector to:

1
Signals

In the Godot UI this panel is:

1
Node

Third, find the custom signal:

1
collected()

Double-click it.

Fourth, choose the receiver node:

1
Main

Click Connect. Godot will generate a function similar to this in Main’s script:

1
2
func _on_coin_collected() -> void:
    pass

Change it to:

1
2
3
4
5
6
var score: int = 0


func _on_coin_collected() -> void:
    score += 1
    print("Current score:", score)

When you run the game and collect the coin, it prints:

1
Current score:1

Method 2: connect signals in code

You can also connect it directly in main.gd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
extends Node2D

var score: int = 0

@onready var coin: Area2D = $Coin


func _ready() -> void:
    coin.collected.connect(_on_coin_collected)


func _on_coin_collected() -> void:
    score += 1
    print("Current score:", score)

The key line is:

1
coin.collected.connect(_on_coin_collected)

It means:

1
2
3
Sender: coin
Signal: collected
Receiver function: _on_coin_collected

In Godot 4, the recommended style is to call connect() through the Signal object:

1
coin.collected.connect(_on_coin_collected)

Do not blindly copy old Godot 3 tutorials that use older connection syntax.

Let signals carry data

Signals can do more than say “something happened.” They can carry data too.

For example, different coins can have different values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
extends Area2D

signal collected(value: int)

@export var value: int = 1


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit(value)
    queue_free()

A normal coin can use:

1
value = 1

A large coin can use:

1
value = 5

Main must receive the parameter too:

1
2
3
4
5
6
7
var score: int = 0


func _on_coin_collected(value: int) -> void:
    score += value
    print("Gained:", value)
    print("Total score:", score)

The communication becomes:

1
2
3
4
5
Coin: I was collected, and my value is 5
Main: received value = 5
score += 5

Why Coin should not directly modify the UI

This is not recommended:

1
2
3
func _on_body_entered(body: Node2D) -> void:
    get_node("../CanvasLayer/ScoreLabel").text = "1"
    queue_free()

Because Coin now depends on Main’s exact node structure:

  • Coin must know where ScoreLabel is.
  • Coin must know how the score is stored.
  • Coin must know how the UI is updated.

If you move the UI node, Coin’s path may break.

A better structure is:

1
2
3
4
5
6
7
8
Coin only says:
"I was collected."

Main handles:
"Increase the score."

UI handles:
"Display the score."

In other words:

1
2
3
4
5
6
7
Coin
  │ collected(value)
Main
  │ update score
ScoreLabel

This is how signals reduce coupling between nodes.

Three concepts that are easy to mix up

signal

Declare a signal:

1
signal collected

This creates a notification type.

connect()

Specify which function should run after the signal is emitted:

1
collected.connect(_on_collected)

emit()

Actually emit the signal:

1
collected.emit()

Memory aid:

1
2
3
signal  = define the notification
connect = assign a receiver
emit    = send the notification

coin.gd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
extends Area2D

signal collected(value: int)

@export var value: int = 1


func _ready() -> void:
    body_entered.connect(_on_body_entered)


func _on_body_entered(body: Node2D) -> void:
    if body.name != "Player":
        return

    collected.emit(value)
    queue_free()

main.gd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
extends Node2D

var score: int = 0

@onready var score_label: Label = $CanvasLayer/ScoreLabel
@onready var coin: Area2D = $Coin


func _ready() -> void:
    coin.collected.connect(_on_coin_collected)
    update_score_label()


func _on_coin_collected(value: int) -> void:
    score += value
    update_score_label()


func update_score_label() -> void:
    score_label.text = "Score: %d" % score

The key idea is:

Signals notify that something happened. They do not decide everything every other node should do next.

Once you understand this, buttons, coins, enemies, timers, health changes, and UI updates in Godot become much easier to organize.

记录并分享
Built with Hugo
Theme Stack designed by Jimmy