You are using Godot Signals in the wrong way!

preview_player
Показать описание
Godot Signals are like Unity Events, BUT this is NOT the whole story! A design pattern disaster is what you get if you misuse them. This video is a short introduction to the concept. We’ll examine a very simple example of using Signals. Both the wrong way and the right way will be explained.

**********************************************************************************
NOTE: DO read the pinned comment... I've got important news to share with you!
**********************************************************************************

However, I suppose you are already familiar with Unity Events, or generally, Events in programming. If you are an absolute beginner to Events/Signals, here’s a simple explanation which enables you to understand the video better:

In programming, Events (and in Godot, Signals) happen when some condition is met. The cool thing about them is that they can be “connected” to one or more functions. If they happen (in other words, if they are emitted), any function connected to them will be called automatically. That’s all you need to know for the moment.

This video provides the foundation necessary to understand the contents I teach in the next tutorials. The practical way of using Nodes, Scenes and Signals inside Godot Engine will be covered in the next videos. So if after watching this video you still kinda feel confused, don't worry. All the pieces will fit together when we make an actual game. Subscribe and hit the bell so you don’t miss what’s coming next.

If you haven’t watched the previous tutorials in the series, here’s the link:

And also DO watch the next tutorials in the series because what is offered in this video is just a simplified example and doesn't cover many-to-many relations. I hope you find the content useful. :)
Рекомендации по теме
Комментарии
Автор

I'm so excited to inform you that I've just released my Custom OOP Solution for Godot 4.2, called "ReuseLogic Nexus" addon.

Based on Observer Pattern, Mediator Pattern, Singleton Pattern, and “Signal-Driven” State Machines, this addon is all you need to “Object Orientalize” your projects, reducing the amount of code needed, enabling you to reuse your modules/objects/systems in all your projects with almost no modification.

Check this video out for more info and the download link:

whilefree
Автор

"It's his body's responsibility to die"
This is low key such a raw line

Niohimself
Автор

This is arguably worse, it doesn't decouple anything, it just couples stuff in the opposite direction
now _everything_ that _can die_ needs to be coupled to _everything_ that _can kill_
so if you have 2 death triggers, you need to link both of their collision signals to the player, and if you have 2 players or just 2 things that can die, you need to connect 2 death triggers to 2 objects, leading to 4 total signal links, and it just multiplies for every object and trigger in the scene
If you had 10 zombies and 10 spike traps, you'd have to connect 100 signals together total
and that's only for a static scene in the editor, if you play your zombie game and the player has a machine gun, then that machine gun is gonna have to connect it's kill signal to all zombies present, and do this every time the player puts down a turret or something similar, and if a new zombie spawns it'll also probably have to connect it's kill function to all killing objects in the game

This means that you'd need a way to reliably link together all objects that can die with all objects that can kill, which would probably become a nested for-loop that might already depend on the object specifics anyways, so at the end of it all you've gone from a pretty simple but shotty 1-way dependency into an O(n^2) web of signal connections that just moves the dependencies somewhere else

So what's the actual _right_ way to use signals here? Easy, _you don't_
As an actual example, here's some code for a "LazerGun" class from one of my first game jam entries:

signal hit(object)

func fire():
SFX_Player.play()
# is_instance_valid()
if targeting_object and
emit_signal("hit", targeting_object)
if (targeting_object as Node).has_method("hit"):
targeting_object.call("hit", self)


it still emits a hit signal in case i wanted to use it, but what's important is that it doesn't use signals _at all_, it just checks if it hit something and if that something can be hit via a "hit" function, and then it just directly calls that function, completely decoupled from the object itself
Additionally, it even passes itself to whatever got hit, so it can be like "Hey, you just got hit by me, do with that what you will"
Maybe if we had a cyborg zombie that's immune to the machine gun, the hit function could look like this:

func hit(thing):
if thing is RegularLameGun:
print("lol no")
else if thing is LazerGun:
get_damaged(10)

each killable object can decide what it'll do with each killing object without needing to couple to it head of time
i really like this pattern, and although i'm not sure if it's the _proper_ way to do it, i think it's a significant improvement over using signals for the same thing

DexeloperGames
Автор

Yep, I already understand signals. I had more trouble understanding the mistake than the solution

jaredjones
Автор

Wayyyy too fast! I didn't catch what was the wrong way to use signals. If I were to put an advice, put less memes and explain more clearly in a better, slower pace your message.

gokudomatic
Автор

While I do agree it's the player's body responsability to tell you if they'll die, if you just gave a class_name to the ball and you checked the type of the body instead of it's name, you could change the name of the node and it would still remain usable either way

charlesabju
Автор

Finally nice tuts about good architecture in Godot, thanks for sharing your knowledge sir!

luckyknot
Автор

The function inside the video can be simplified from:

func _die_on_contact(body):
if body.name == name:
#body.queue_free()
print("player dies!")

to:

func _die_on_contact(body):
if body == self:
#body.queue_free()
print("player dies!")

No need to check for names!

By the way, I appreciate all the people who are sharing their insight about the content of the video. In my opinion the comments section of each tutorial video is as important as the video itself, even more important. Keep up the good work! :)

whilefree
Автор

gj highlighting the importance of keeping responsibilities where they belong. it's key for efficiency and maintaining a clean project structure

joggerjoe
Автор

Another way to use signals to create connections instead of manually can be done in multiple ways, for example you can check if a button has been pressed by connecting it directly to a function in the script like:

func _ready():


func ButtonPressed():
#Code goes here

Another way is to do it directly in the script, like so:


#Your
#Code
#Goes
#Here
)

I also recommend using Unique Access Names (Right click the node to see the option) when creating scenes and creating them for your objects, so instead of writing out an entire path like you can use %Button instead.

koresaliva
Автор

Fantastic video. I am a newbie (kind of) but was able to follow with some pausing, rewinding, and watching some portions at lower speed. You got yourself a new subscriber.

SlyPearTree
Автор

A polymorphic/duck-typing approach would be nice - bullet connects to its own function which says, if the body has the method "damage", call it (with a constant damage value amount or with a reference to itself so the entity can decide how much damage it takes, or both!)

amaryllis
Автор

Sorry but I just want to point out for people new to the engine that your recommended approach is actually much worse, and the correct way IS actually the way you said is incorrect. There are a few reasons why we do it this way:

1) Think about the logic behind your proposed method. You are telling the player object to listen to every time a body enters the bullet. So if that bullet flies through trees, bushes, enemies, allies, etc the player is going to hear about it. In fact, everything subscribed to the bullet will hear about every other thing passing through and you will have a million unnecessary function calls.

2) The creators of Godot also don’t want you to do it this way, because the callback sends the foreign body as an arg, not the body itself. This means that in your example, you have severed all knowledge of the bullet and what it’s data is, most important of which is it’s damage. You now have no way of programming the bullet to do a variable amount of damage to the player, you have coupled it to only kill the player. So if you want anything else to happen to the player, you need to create a million more functions on the player for each amount of damage you want to inflict on the player in case it crosses paths with a bullet.

The best way to do something like this is to: export a dmg property on the bullet, connect the body entered signal to the bullet itself, perform a TYPE check in the callback (not a name check), call a take_hit(dmg) on the player and pass in the dmg from the exported field. This allows the designer to balance the dmg to any amount they want, giving WAY more freedom without needing to modify code. And if you want it to be a one-shot kill then pass in the players current health as the dmg of the bullet.
It doesn’t logically make sense to say as the player “A body has entered a random bullet, was it myself? Okay kill myself”. It’s much better to say as the bullet, “A body has entered myself, was it something I can damage? Okay do damage”.

Ryan-lpqw
Автор

Good video! Loved the lil jokes in the middle, very witty!

kartopod
Автор

my favorite part of this video is the part where the gun fires the bullet AND the cartridge

johnwildauer
Автор

anyone have a slowed down step by step version of this concept

drawtheword
Автор

What about objects that don't exist in the scene?

GodsAutobiography
Автор

That guy must have been really mad, firing a whole cartridge instead of just the bullets :D

akirayatagama
Автор

I have been wondering about the same thing. Great video!

yess.
Автор

I just try to cast the object into a player and call the die()/hurt() whatever function on it

hansane_dev