GDScript Godot Signals In Depth Guide

Game dev general presentation image

Signals are a very important part of Godot and GDScript. Signals allow nodes in Godot to communicate with one another. Signals are very important for witting flexible, decoupled and maintainable code.

bug free code intro to gdscript signals in depth guide
I like my bugs outside my computer.

In this article we’ll deal with scripts using only code. Personally I think it’s best to keep the signal management inside your scripts. You can of course connect signals using the editor GUI if you wish.

Gdscript godot double click to connect signal using GUI
double click on the desired signal to connect it.

If you don’t know how to use signals, don’t worry! I’ll teach you the most important things to know about proper signals usage! I’ll start with simple concepts and build slowly upon that!

You need to be at least familiar with GDScript before reading this article!

Simple GDScript Signal Emitting

Let’s dive straight in with a simple example.

# player.gd
signal zero_life
var myhealth=100

func _process(delta):
    if myhealth<=0:
        emit_signal("zero_life")

We have a script called player.gd. We start by defining a signal called zero_life. During process we keep on checking for the value of myhealth (not the best way to do things but works as an example).

If myhealth smaller than zero emit the signal “zero_life”

Some things to keep in mind:

  • We need to define the signal somewhere in the script before emitting it. At the top of the script is a good place.
  • We emit the signal using…emit_signal. Obvious, right?
  • There’s nothing fancy about a signal. It doesn’t have any intrinsic properties (yet) aside from it’s name.

One thing that kinda sucks is that we’ll be emitting that signal ad nauseam (until we’ll get sick) in the _process function.

Stagger, Dizziness, Balance, Smiley gdscript ad nauseam
Oh NO! I’ve emitted a signal thousands of times in a minute! I’m dizzy!

Not a good idea usually – this type of signal we want to emit only once (but other types might be emitted at higher rates)

# player.gd
signal zero_life
var signal_emitted=false
var myhealth=100

func _process(delta):
    if not signal_emitted and myhealth<=0:
        emit_signal("zero_life")
        signal_emitted=true

This is like the first example with some minor differences. First we define a variable to check if we have emitted the signal.

In the _process() we start by checking if our variable (signal_emitted) is false. If it is false and myhealth smaller than zero, proceed.

We emit the signal, same as before. But we make signal_emitted true so that the next update this block of code won’t be called.

Sign, Caution, Warning, Danger, Safety, Hazard, Risk
Important Note!

(we could’ve written if myhealth<=0 and not signal_emitted too. But this way is just a bit more effficient since we’ll only do one check, not two. If not_signal_emmited – which will probably be the case after myhealth is less than zero, only then check for myhealth. But if we write it the other way around we would keep checking for both myhealth and signal_emitted)

How Are GDScript Signals Useful

Alright, so we’re emitting a signal in the player.gd. What’s the big deal? Who cares, huh?

Well, other nodes/scripts might care.

Let’s say we have a game_manager.gd script that monitors the game’s state. If the player life is below zero trigger certain events (like show some UI elements, load another scene, etc). So we need to know when that particular event happens.

Tic Tac Toe, Heart, Game, Chalk, Love gdscript event waiting
An important gamedev event happened

How to do that?

We connect game_manager.gd to player.gd life_zero signal. When player.gd emits life_zero signal, call a specific function in game_manager.gd.

Now you’re probably just shaking your head. “Why bother with signal and stuff when we can simply call game_manager.gd function directly from player.gd?

Heck, we save ourselves the trouble and just write:

# player.gd
var func_called=false
var myhealth=100

func _process(delta):
    if not func_called and myhealth<=0:
        get_node("game_manager").on_player_zero_life()
        func_called=true

Or do we??

Code Without GDScript Signals Is Bad

At first glance maybe. But consider the following things:

Tight coupling – BAD!
Cycle, Recycling, Recycle, Arrows, tight coupling not good gdscript
Coupled too tight, many dependencies

Now player.gd is tightly coupled with game_manager.gd. Delete game_manager.gd and player.gd will throw an error complaining it can’t find the node. Move game_manager.gd and you’ll have to update it’s location in the player.gd code too. The life and fate of player.gd is tightly coupled to game_manager.gd

And granted, it’s not a big deal if only game_manager.gd is coupled with player.gd. But what if you have 3 or 5 or 10 other scripts which need to be notified? The trouble just gets 3x, 5x, 10x worse.

Very low re-usability – BAD!
Connect, Connection, Cooperation, Hands, low reusability gdscript
low re-usability is bad

Let’s say that somehow you can live with the tight coupling between player.gd and all the other dependent scripts.

You use player.gd in a scene where all those dependent nodes exist (at the exact paths defined in player.gd). Very well. But now you need to re-use player.gd in another scene where some of those nodes don’t exist. BUMMER!

You could jump through hoops and add conditionals and error checking. It’ll probably work in a way – but your code will become complex, more unreadable and hard to maintain.

According to Godot official docs recommended best practices scenes should have no dependencies.

That is – ideally you can drop a scene into any other scene and it will work. Sometimes that will more challenging but you should always strive towards it.

Simple Connect To Emitted GDScript Signal

So what’s a better way? Well, signals, of course. Let’s see how to actually connect to signals

Godfather, Portrait, Actors, Man, gdscript function emitting listen
I’m a function and I’m listening to a signal being emitted
# game_manager.gd
func on_player_zero_life():
    print("Starting game over sequence")

func _ready():
	var player_node=get_node("player")
	player_node.connect("zero_life", self, "on_player_zero_life")

Let’s dissect the code:

We define a function to be called when signal is emitted on_player_zero_life().

In _ready() we do the actual connecting. First we get the player node. This is important. The signal belongs to the player node. To connect to that signal we need a reference to the player node. Once we get a reference to the player.gd we call the connect function (belonging to player.gd)

Take a look at the parameters of the function connect.

The first parameter is the signal we’re connecting too. Easy.

Third parameter (on_player_zero_life) is the function name to be called when signal is emitted.

Second parameter is the object to whom the function (on_player_zero_life) belongs. self means on the callee script itself.

Use Middleman To Connect GDScript Signal

Man, Person, Offer, Bargain, Sale , middleman managing gdscript signals
I’m a middleman and I manage deals between nodes.

Let`s see an example where that second parameter might be different:

#game_manager.gd
func _ready():
	var player_node=get_node("player")
	var some_ui_node=get_node("../some_ui_node")
	player_node.connect("zero_life", some_ui_node, "on_player_zero_life")

We start as usual and get the player_node. Then we get another node called some_ui_node. game_manager is the middle man, connecting some_ui_node to players node signal. Note how game_manager doesn’t have anymore on_player_zero_life function.

That’s because this function should exist in some_ui_node.gd script

#some_ui_node.gd
func on_player_zero_life():
    print("Updating an UI element")

When Using A Middleman To Connect Signals Is A Good Idea

Purchase, Sale, Shop, Woman, Inside gdscript middleman signals good idea
I’m a middleman connecting nodes. I’m a GOOD idea!

This setup could work well in certain situations. What situations? Here’s an example:

  • you have multiple (different) player scenes, each one with different functionality. The one thing uniting those scenes is that they all emit a signal calle zero_life. (for example player_red, player_blue, player_npc – all inheriting code from player or implementing common functionality)
  • you have a single some_ui_node in your game. But you need to connect it to a certain player script depending on the game scene. In one scene you need to connect it to player_red. In another to player_npc. What to do? Using conditionals in the function that does the connect is ugly. It would look something like this:
#some_ui_node_name.gd
func _ready():
	var player_red=get_node("player_red")
	var player_npc=get_node("player_npc")
	if player_npc and not player_red:
		player_npc.connect("zero_life",self,"on_player_zero_life")
	elif player_red and not player_npc:
		player_red.connect("zero_life",self,"on_player_zero_life")

Ugly, right? And as the numbers of nodes to check increases, so does complexity!

Instead if you use a middle man (game_manager) you don’t need to worry about that in some_ui_node. It’s the game_manager job to connect some_ui_node to the correct player node.

#game_manager.gd
func _ready():
	var some_ui_node=get_node("../some_ui_node")

	if check_scene("player_npc"):
		player_npc.connect("zero_life", some_ui_node, "on_player_zero_life")
	elif check_scene("player_red"):
		player_red.connect("zero_life", some_ui_node, "on_player_zero_life")

Using A Middleman Might Lead To Clean Code

Lego, Legomaennchen, Male, Workers, Work middleman gdscript node
Cleaner code with middleman signal manager nodes.

At this point you might scoff. It seems that we just moved functionality from some_ui_node to game_manager. We’re just using check_scene() function inside game_manager and if player_npc and not player_red: conditional inside some_ui_node.

And what’s up with that? Why are the functionalities different?
They’re different because game_manager will probably have access to more data and children nodes then some_ui_node.

After all children nodes should know as little as possible about their parents/sibling nodes. It makes for decoupled, maintainable and flexible code. One manager object knowing about 100 children nodes is acceptable. 100 children nodes knowing about each other it’s nightmare on Code Street.

But that’s not all! There’s another golden rule in programming called DRY! Don’t Repeat Yourself!

Desert, Drought, Dehydrated, Arid, dry principle gdscript
I’m dry but I’m not DRY (Don’t Repeat Yourself)

Suppose you have another UI node that needs to be connected to the zero_life signal. If you’re checking inside the UI nodes, you’ll have to do the checking twice! Once or each node! Probably the exact same piece of code.

If you need to connect multiple UI nodes the repeated codes multiplies across each UI node. I’m shivering just thinking at maintaining such a mess.

If you’re using a manager on the other hand you’re doing the checking once. Then you connect all the needed UI nodes.

#game_manager.gd
func _ready():
	var some_ui_node=get_node("../some_ui_node")
	var another_ui_node=get_node("../another_ui_node")

	if check_scene("player_npc"): #checking once
		player_npc.connect("zero_life", some_ui_node, "on_player_zero_life")
		player_npc.connect("zero_life", another_ui_node, "on_player_zero_life")
	elif check_scene("player_red"): # checking once
		player_red.connect("zero_life", some_ui_node, "on_player_zero_life")
		player_red.connect("zero_life", another_ui_node, "on_player_zero_life")

Now you know quite a lot about properly connecting signals to nodes! Bravo! But wait…there’s more. Good stuff too!

Pass Parameters To GDScript Signals – Emitting

Rainbow, Colors, Diodes, Electronic, gdscript signals and parameters emitting
Let’s emit signal AND parameters, yay!

What if you need to pass an argument alongside your emitted signal? Let’s say you want to pass the enemy_node that killed the player? Might be useful to the connected nodes (that receive the zero_life signal)

# player.gd
signal zero_life(killer_enemy)

var signal_emitted=false
var myhealth=100

func _process(delta):
    if not signal_emitted and myhealth<=0:
		var killer_enemy=get_killer_enemy_node()
        emit_signal("zero_life",killer_enemy)
        signal_emitted=true

We’re back at editing player.gd. It’s same old same old with some differences. Start by noting the declaration of the signal. It looks like a function call and inside it we use a parameter called killer_enemy.

This means that when we emit the signal we’ll also pass an argument. The argument is killer_enemy node. The functions that connect to this signal need to be declared accordingly

Pass Parameters To GDScript Signals – Connecting

Tv, Television, Technology, Screen, GDScript receiving signals and parameters
I receive both signals and parameters.

Let’s suppose some_ui_node will connect directly to the player_node (no need for a manager object to simplify the example)

#some_ui_node_name.gd

func _ready():
	var player=get_node("player")
	player.connect("zero_life",self,"on_zero_player_life")

func on_zero_player_life(killer_enemy):
	print("I was killed by %s" % killer_enemy)

The function connected to the emitted signal needs to have a killer_enemy parameter defined. Omitting the parameter from the function definition might result in an error. You can also get an error if you call emit_signal("zero_life") but don’t use the argument killer_enemy.

gdscript godot error when not supplying argument to the emit signal function

Remember when I told you that checking for myhealth inside process it’s bad practice? Let me show you a better way.

A Better Way To Emit GDScript Signal When Variable Has Value X

Honey, Bee, Flying, Insect, Honeybee, clean code writing better example for emitting signal.
I’m a smart programming bee! I write clean extensible code!

Here’s player.gd

# player.gd
signal zero_life(killer_enemy)

var myhealth=100 setget set_myhealth

func set_myhealth(val):
	myhealth=val
	if myhealth<=0:
		var killer_enemy=get_killer_enemy()
		emit_signal("zero_life",killer_enemy)

Now we don’t need to bother to check within _process() function. We’ll just use a setget. What’s a setget? It’s way of using setters and getters. Setters are functions that get called when a value is set. Getters are functions that get called when a value is queried (read from somewhere else in the code)

Here we have a simple setter function called set_myhealth. When someone sets this variable (like player.myhealth=43) this function will get called.

We start by actually doing the most basic of jobs – updating the myhealth value to the passed value. I mean, we don’t have to do this. We can just ignore the passed value. Or we can modify it in a way (like myhealth=val*100). Etc.

Next we check if myhealth is less or equal than 0. If it is, get killer_enemy node and emit zero_life signal.
Easy peasy!

Of course, this could still be called multiple times if enemies aren’t aware the player health is zero. Such additional checking can be implemented on the enemies or the player itself. That is not relevant for the current subject.

Advanced GDScript Signal Usage – Bind Array

Tied Up, Togetherness, Tying, Teamwork, Business, Old, bind array for signals advanced usage
I bind things together!

So far you saw how to add a parameter to an emitted signal. That parameter originates with the node doing the actual emitting.

You also saw the middle man connecting emitted signals with other nodes. Now suppose you want to pass some extra parameters. But this time these parameters will originate with the middleman.

Why would you want that? Well, maybe because the node emitting the signal doesn’t have all the information needed. Going back to our example we pass the value of the enemy that killed the player. Cool.

The some_ui_node needs that information – maybe it will print the name of the killer enemy. But it may need another piece of information, such as the status of the game save. Was the game saved?

If the game was saved then display the name of the killer enemy. If the game wasn’t saved then show a message prompting the player to save.

Who’s going to send that info to some_ui_node? The player node could but it’s not a good idea. Each node should know as little as possible about its environment. The player should have access to data about enemy that killed it. But the status of the game save? No way Jose, it doesn’t need access to that data.

game_manager on the other hand probably has access to that data. It would be cool to pass this extra info to some_ui_node

#game_manager.gd
var game_saved=true

func _ready():
	var player=get_node("player")
	var some_ui_node=get_node("some_ui_node")
	player.connect("zero_life",some_ui_node,"on_zero_player_life",[game_saved,42])

In the code above we are doing just that. Note the fourth argument to connect() function – an array. This is called a bind array. It’s a way of saying that we’re passing some extra parameters, nicely packed in an array.

We’re passing two extra parameters – game_saved status and an constant number (just to show you that it is possible).

Meanwhile, this is how some_ui_node looks like:

#some_ui_node.gd
func on_zero_player_life(killer_enemy,game_saved,secret_of_life):
	print("I was killed by %s" % killer_enemy)
	print("game saved status %s" % game_saved)
	print("The secret of life is %s" % secret_of_life)

We declare on_zero_player_life function as usual. The first parameter remains the killer_enemy – it will passed from the node emitting the signal. The second and third parameters are those from the bind array, from the middleman node.

It’s just some extra functionality that might come in handy sometime!

Advanced GDScript Signal Usage – Fun with Flags!

European Union, European Parliament
We’re flags and we can be written as integers (we’re secretly enums, don’t tell anyone). Call us together with the | operator.

This is the fifth and final argument you can pass to the connect() function. Flags!

Flags are a way to signal some special things about how signal behave.

According to Godot docs you can pass these flags:

● CONNECT_DEFERRED = 1
Connects a signal in deferred mode. This way, signal emissions are stored in a queue, then set on idle time.
● CONNECT_PERSIST = 2
Persisting connections are saved when the object is serialized to file.
● CONNECT_ONESHOT = 4
One-shot connections disconnect themselves after emission.
● CONNECT_REFERENCE_COUNTED = 8


Connect a signal as reference-counted. This means that a given signal can be connected several times to the same target, and will only be fully disconnected once no references are left.

You can pass multiple flags using | operator. Like so: connect("my_signal",self,"my_func",[],CONNECT_ONESHOT | CONNECT_PERSIST)

Two flags seem to have immediate utility: CONNECT_PERSIST and CONNECT_ONESHOT.

You should use CONNECT_PERSIST if you want your connections to be saved when serializing your objects.

The other interesting flag is CONNECT_ONESHOT. After the signal is emitted the connection is broken right away. Here’s an example:

You have an explosion particle you want to show when the player dies. It wouldn’t really make sense to show it multiple times.

#game_manager.gd
func _ready():
	var player=get_node("player")
	var explosion_effect=get_node("explosion_effect")
	player.connect("zero_life",explosion_effect,"on_zero_player_life",[],CONNECT_ONESHOT)

Note how we don’t pass a bind array anymore. We do in a way, but it’s empty. The explosion_effect node doesn’t need to know about those variables.

After the signal is emitted the connection is broken. Even if an enemy hits the player again while he’s dead (thus possibly triggering on_zero_player_life signal again) the explosion won’t know about it.

Of course, you’ll need to reconnect explosion_effect node to zero_life signal on game restart. That is when the player is revived or a new game starts.

GDSCript Signals – Conclusion

Finish, End, Completed, Completion, gdscript signals finish
You reached the finish line, congratulations!

Wow, what a journey! You now know quite a lot about signals and their usage. You saw both simple and advanced use cases for their usage.

You can connect to emitted signals directly from the node doing the connecting. Or you can use a middleman node to connect signals to other nodes.

You can pass parameters from an emitted signal. You can also pass extra parameters from the node doing the connecting. Those extra parameters are packaged into a bind array.

You wen’t even deeper into the rabbit hole with flags! You use flags to have special signal functionality, such as disconnecting the connected node after signal was emitted once.

By now you should be ready to use signals in all kinds of situations. As you see you can build some pretty advanced game functionality with them.

Signals will keep your code clean, flexible and easy to read and maintain. Use them while developing games! Take advantage of signal’s power!

1 thought on “GDScript Godot Signals In Depth Guide”

  1. This tutorial is FIRE. I did not understand much the official Godot tutorial about signals. but now everything is clear.

Leave a Reply

Your email address will not be published. Required fields are marked *