this post was submitted on 21 Sep 2023
43 points (100.0% liked)

Godot

5848 readers
35 users here now

Welcome to the programming.dev Godot community!

This is a place where you can discuss about anything relating to the Godot game engine. Feel free to ask questions, post tutorials, show off your godot game, etc.

Make sure to follow the Godot CoC while chatting

We have a matrix room that can be used for chatting with other members of the community here

Links

Other Communities

Rules

We have a four strike system in this community where you get warned the first time you break a rule, then given a week ban, then given a year ban, then a permanent ban. Certain actions may bypass this and go straight to permanent ban if severe enough and done with malicious intent

Wormhole

[email protected]

Credits

founded 1 year ago
MODERATORS
 

Hello all!

Like most people I find myself a recent refugee from the Unity fiasco. I've been trying to prototype a project in Godot and I've been running into an issue I would think would be pretty easy to find a solution to as it seems to be a pretty fundamental building block of any project in Godot. Perhaps I'm misunderstanding how to accomplish this in Godot, but essentially I'm instantiating a number of tiles to be used for a grid system in my game. I want these tiles to be able to emit their index and transform values and then have other scripts pick this information up as needed. From what I've read signals are the way to do this, however whenever I try to send a signal with or without parameters nothing seems to happen. I seem to be able to connect to the signal just fine but the method doesn't seem to be called.

Here's an example of me defining the signal and then emitting it:

signal index_transform()

index_transform.emit()

And here's how I am connecting and attempting to call the method in a secondary script:

func _ready() -> void:
	var hexGrid = get_node("/root/Main/Map/HexGrid")
	hexGrid.index_transform.connect(Callable(self, "_get_hex_index_transform"))

func _get_hex_index_transform():
	print("I'm Connected")

And when I'm passing parameters from what I understand I should only have to include the parameters like so:

signal index_transform(index, transform)

index_transform.emit(tile_index, tile_coordinates)
func _ready() -> void:
	var hexGrid = get_node("/root/Main/Map/HexGrid")
	hexGrid.index_transform.connect(Callable(self, "_get_hex_index_transform"))

func _get_hex_index_transform(index, transform):
	print("I'm Connected")
	print("INDEX: ", index," POS: ", transform)

However neither of these seem to work. What am I doing wrong?

all 21 comments
sorted by: hot top controversial new old
[–] [email protected] 10 points 1 year ago (1 children)

Are you emitting in a function that gets called. If you are not sure, put a breakpoint in that line and see if you stop there. Also check to see if you connect first before emitting. One more thing, you don't need to make a callable, you can just use the function as it is a first class member of the class now, eg:

hexGrid.index_transform.connect(_get_hex_index_transform)

Be sure not to leave in any brackets added after the function that autocomplete likes to add.

[–] [email protected] 1 points 1 year ago (1 children)

I'm emitting in the function that instantiates the tiles so it's definitely getting called. How do I verify that the connection is going through?

Also thanks for letting me know how to format it I was getting conflicting information regarding the syntax from various different tutorials and resources.

[–] [email protected] 3 points 1 year ago* (last edited 1 year ago) (1 children)

I believe there is a property on the signal to see the connections. From what you just said, it is possible that you are emitting before the connection is made. You can verify this using a couple print statements. If this is correct, the solution would be to either move the emission later or move the connection earlier (_init happens before _ready). Depending on what these functions are doing, feel free to pick whatever solution works best for you.

[–] [email protected] 1 points 1 year ago* (last edited 1 year ago)

Hm..I didn't even consider that the emit might be happening before the connection. I have them both running from separate on_ready methods. Perhaps I should be running connection though _init. Typically in unity I would just use C# actions and use OnEnable to establish the "connections." It make sense that the code could be emitting before I even have a chance to connect to it, which is why my test fails.

[–] [email protected] 4 points 1 year ago

You don't call the connected function.

Connect a signal to a function, then use emit()

Make sure you emit somewhere from a process function, otherwise you'll emit once.

Likewise, connect from ready function.

[–] [email protected] 3 points 1 year ago (1 children)

Signals are the same as events. It's just a different name. So, use signals to let other nodes/code know when something has happened (or is about to). It would only make sense to use a signal here if the values were changing and you wanted to let other nodes know about it. Like index_transform_changed(new_value).

I'm not sure what the tiles are for, but they're probably part of a collection or board of some kind. I would make this board it's own scene and have the board manage things. It could also make available transforms and indexes to other nodes, but that seems like something that would be best encapsulated within the board node itself. Then have the board get references to the children is controls via get_node, or using the $ syntax, etc.

[–] [email protected] 1 points 1 year ago (2 children)

Thank you! Okay so the tiles are part of a map, and I have a parent node called map with two separate children: hexgrid (where I'm instantiating the scenes) and spawn_objects( where I'm trying to gain access to the index and transform from hexgrid) my intent is to have hexgrid generate the grid and tiles and have spawn_objects instantiate an object within the tile at a certain position within it. Is this perhaps something I should combine into the same script? I typically like to have things modular and keep each component so a single specific task.

[–] [email protected] 2 points 1 year ago

So, it seems spawn_objects needs a reference to hexgrid? Then you can export a property in the spawn_objects script that takes a reference to hexgrid. And then from the map scene, you set that reference as it has both as children. In Godot 4 you can use "@export var hexgrid: HexGrid" (this assumes you give the hexgrid node script a class_name of HexGrid.) In Godot 3 I think there's a bit more to it as the export is "export var hexgrid:NodePath" (note no @ symbol in Godot 3) and then later you have to use the NodePath to get the node like this "onready var _hexgrid:HexGrid = get_node(hexgrid)" (note the onready here means the get_node call will happen just before the call to func _ready()) You could do the get_node call in func _ready(), but I like the onready better because it makes any code in the ready function that much simpler.

That's just how I would do it given what I think I know. Now that you have these ideas, you can play with them and decide what you like best. Hope it helps!

[–] [email protected] 2 points 1 year ago (1 children)

Ok, tell me if I got it right. Your node structure is currently like this:

> Map
>> hexgrid
>> spawn_objects

And you want things to happen in this order:

  • Hexgrid instantiates the grid and tiles
  • spawn_objects will run for certain tiles

If I got it right, then this is a situation where signals are not needed. What I would do:

  • On the Hexgrid scene, add spawn_objects as a child node
> Hexgrid
>> spawn_objects
  • Within the Hexgrid script, when the desired tile needs to spawn something, call the function from the node. Either $spawn_objects.spawn_this_thing() or declaring the node as an @onready var spawner = $spawn_objects (...) spawner.spawn_this_thing()

Since you'll be calling the spawn code from within the hexgrid, you'll have full access to both nodes' variables, parameters and functions. If you needed to get a "spawn_here" variable from the parent while within the child, you could use get_parent().spawn_here

[–] [email protected] 1 points 1 year ago (1 children)

Interesting!! Yeah that's exactly what my node structure is. So basically instead of using signals and sending from one Map child to the other, use spawn_object as a child from hexgrid and then just call the spawn object function as needed since the hexgrid script already has a reference to the index and location of each tile. Thanks so much for the advice I'll give it a shot! I ended up working out the signals issue I was having anyways but it seems like your suggestion is a cleaner solution!

[–] [email protected] 2 points 1 year ago

I hope it works! If it doesn't, just reply back and I'll see if I can help further

[–] [email protected] 1 points 1 year ago (1 children)

Signals are best used when you use the prebuilt ones for each node (on the editor, it's a tab on the top right side, besides the node's parameters). For most cases, a signal is emitted when a certain event happens, like _on_animation_changed(): for an AnimationPlayer node.

I never checked the prebuilt signals for a TileMap (which I'm assuming you're using), but my suggestion is for you to check when you need the function to happen. If you want it to fire once another entity enters it (say, a player controlled character came in), it's likely you'll want to use an Area2D + CollisionShape2D nodes, the signal firing from the Area2D with _on_body_entered(body) - In this case, the body parameter is a pointer to the entity. You can call any functions it has and you can directly alter any of its variables, so you could hit a player from there.

The trick is really in the "when" you need to access the data. So, when will you actually need it those index numbers?

[–] [email protected] 5 points 1 year ago (1 children)

Signals are best used when you use the prebuilt ones for each node

Thas not true at all, I use custom signals all the time.

However, custom signal are not particularly useful within a class. They are useful for passing information up the node hierarchy. As Kids Can Code says: get_node down the tree, signal up the tree.

[–] [email protected] 1 points 1 year ago (3 children)

Is this the intended purpose for signals? Moving info up and down the tree? If so how are we supposed to send information from one script to another if one is not directly a child of the other? Do we send it to a shared parent node then send the info back down to the secondary script?

[–] [email protected] 3 points 1 year ago (1 children)

I don't know if it the intended purpose, but it's a very useful one.

If so how are we supposed to send information from one script to another if one is not directly a child of the other?

Signals are good for that. It's just generally easier to maintain than many calls to get_node. Signals help to decouple a scene from other scenes, so when you instantiate it you just connect the signals to some callbacks that can be in any script anywhere. If you use get_node all the time you end up having one scene relying on a specific node hierarchy in another scene, which is just a big headache.

[–] [email protected] 1 points 1 year ago* (last edited 1 year ago) (2 children)

I see okay so I understand that the intent is to decouple scenes from each other, however from the tutorials I've seen they typically say that to establish the connection you need to run get_node() from the script you are establishing the connection to. So for example if you have:

*Parent
    **Child 1
    **Child 2

You would emit from Child1, then establish the connection from Child2 using:

var script = get_node("root/Parent/Child1")
script.some_signal.connect(some_function)

Is that the correct interpretation? Or am I misunderstanding? Thanks in advance btw I appreciate all the help in understanding this!

[–] [email protected] 4 points 1 year ago* (last edited 1 year ago)

@plixel @Rodeo if the parent is creating the children at runtime then that's the time to connect signals in code.

If the child already exists as part of the parent's scene you could connect it's signals in the editor (built-in signals or your own signals). Then in code just run singal_name.emit(args) when needed. You can still connect them in code if you want [e.g. iterate over get_children()].

[–] [email protected] 1 points 1 year ago (1 children)

@plixel @Rodeo
The way I'm doing in my game is by

  1. Using a Signals.gd script

  2. Add that script to Project settings > Autoload, this makes the script globally accessible (singleton)

  3. Then from the scene I want to send the signal, I do
    Signals.my_signal_was_triggered.emit()

  4. Then on the scene/node (can by multiple) I want to receive the signal emission, I do
    Signals.my_signal_was_triggered.connect(my_function)

  5. Create the function "my_function"

[–] [email protected] 2 points 1 year ago

This works but now your signals are not part of the class which they act for, and are also in global scope rather than scoped within the class they belong to.

Generally it's a good idea to keep everything in the smallest scope possible. But this can work for smaller projects where the total number of signals is manageable on its own.

[–] [email protected] 2 points 1 year ago

It's a good rule of thumb, but Godot gives you enough rope to hang yourself with ... at least until the next update 😀

If you need to pass information around to different scripts and/or scenes then you may wish to employ Singletons.