Signals are Godot's version of the observer pattern. They allow a node to send out a message that other nodes can listen for and respond to. For example, rather than continuously checking a button to see if it's being pressed, the button can emit a signal when it's pressed.
There are to types of signals in Godot:
Built-in. These signals comes with Godot and you don't need to create them. They emit the signal by default and you can connect to them in two ways:
From the editor (Scene/Node -> Node/Signal -> <signal> -> Connect)
From code: <source_node>.<signal_name>.connect(target_function_name>)
Example of connecting to the body_entered signal from a CharacterBody2D scene node called Zone:
$Zone.body_entered.connect(_on_zone_body_entered)
Note 1: If the node that connect to the signal is in the same scene, we can reference it by $ or %.
Note 2: If the node that connect to the signal is in a different sceneÂ
Custom. You can create your own signals. They need the following:
A name: signal my_signal
An emitter: my_signal.emit()
Example:
signal my_signal
func _ready():
my_signal.emit()
You can connect to your custom signal as you will do with a built in signal:
node_name.my_signal.connect(_on_my_signal_function)
signal my_signal
my_signal.emit(arg1)
node_name.my_signal.connect(_on_my_signal)
func _on_my_signal(arg1):
Preferred:
@onready var projectile = preload("res://projectile.tscn")
func _ready():
var new_projectile = projectile.instantiate()
new_projectile.my_signal.connect(_on_my_signal_function)
Less preferred:
get_parent().get_node("Zone").body_entered.connect(_on_Zone_body_entered)
or
get_tree().get_root().get_find("Zone", true, false).body_entered.connect(_on_Zone_body_entered)
You can use either get_node() or find_node(). Note that get_node() doesn't need the three arguments that get_find needs.
func _on_Zone_body_entered():
if body.name == "Player":
print("%s detected" % body.get_name())
You can also use either of these:
if body.is_in_group("players"):
if body.has_method("player_spotted"):
if body.variable == "holy"
References:
https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html
https://docs.godotengine.org/en/stable/tutorials/misc/instancing_with_signals.html
https://godot-es-docs.readthedocs.io/en/latest/getting_started/step_by_step/signals.html
https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/event-bus/
This example creates a Zone that can be traversed by Player and Objeto, When this happens, Panel logs each trespass in two counters, one for Player and one for Objeto.
Zone.gd creates the Godot's built-in signal body_entered via code, instead of creating it in the editor. Zone listen to its own body_entered signal with the function _on_Zone_body_entered that alerts when an object enters in the zone.
Player.gd can move using the WASD keys. Player also listen to Zone's body_entered signal and creates his own reaction function player_spotted to alert of their own intrusion in the zone.
Objeto.gd can move by point and click with the mouse. Objeto is not interested in listening to the Zone's body_entered signal, so it is not aware that is being monitored when entering in the zone.
Panel.gd listen to Zone's body_entered signal and updates the trespass counter accordingly by using the function increase_counter.
extends Area2D
func _ready():
# We connect to the built-in signal body_entered via code instead of using the editor
body_entered.connect(on_zone_body_entered) // No need to add node name if we connect to ourselves
func _on_zone_body_entered(body):
if body.is_in_group("players"):
print("Zone's message: Player entered the ZONE")
else:
print("Zone's message: Unknown object entered the ZONE")
extends KinematicBody2D
export (int) var speed = 200
var velocity = Vector2()
func _ready():
add_to_group("players")
var zone = get_tree().get_root().find_node("Zone", true, false)
zone.body_entered.connect(player_spotted)
func player_spotted(body):
if body.is_in_group("players"):
print("Player's message: The Zone knows that I entered the ZONE")
func get_input():
velocity = Vector2()
if Input.is_action_pressed('right'):
velocity.x += 1
if Input.is_action_pressed('left'):
velocity.x -= 1
if Input.is_action_pressed('down'):
velocity.y += 1
if Input.is_action_pressed('up'):
velocity.y -= 1
velocity = velocity.normalized() * speed
func _physics_process(_delta):
get_input()
velocity = move_and_slide(velocity)
extends KinematicBody2D
func _input(event):
if event.is_action_pressed('click'):
target = get_global_mouse_position()
var target = Vector2()
const FOLLOW_SPEED = 4.0
func _physics_process(delta):
self.position = self.position.linear_interpolate(target, delta * FOLLOW_SPEED)
extends Control
var pcount = 0
var ocount = 0
# Called when the node enters the scene tree for the first time.
func _ready():
var zone = get_tree().get_root().find_node("Zone", true, false)
zone.body_entered.connect("increase_counter")
get_node("LPlayer").text = str(pcount)
get_node("LOther").text = str(ocount)
func increase_counter(body):
if body.is_in_group("players"):
pcount += 1
get_node("LPlayer").text = str(pcount)
else:
ocount += 1
get_node("LOther").text = str(ocount)