Does a signal affects/is attached to all instances of an instanced scene ?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By devAS

If I make a signal on a node of a scene, and instance this scene in another scene via code. Will the signal be working on all instances or only the first one ?

:bust_in_silhouette: Reply From: AlexTheRegent

Yes, signals are attached to all instances of instanced scenes. But signal connections are instance-based. So if you have scene (enemy) with signal, one instance (enemy1) that is connected via code to another scene (player) and second instance of enemy scene (enemy2) without connection to player. Then, when enemy2 will emit signal, player will not receive it, because it does not listen for signals from enemy2.

Then, when enemy2 will emit signal, player will not receive it,
because it does not listen for signals from enemy2.

Exactly this, so how how do I do that? Is it possible, because emission of signal from the many instances of a scene created through code during runtime is needed for expected functioning in my case. How do I do this, if possible?
OR
How do I make it such that every instance of the original/ connected scene automatically has connection to the scene

devAS | 2021-01-06 14:29

Optimal solution is depend on your situation. If you have multiple enemies and one player, you can modify _ready() function of Enemy to automatically connect signals to player.

connect("hit", get_tree().root.find_node("Player"), "on_enemy_hit", [self])

Or if all signal’s calls must be received by one node (i.e. “many-to-one” relation), you can do it without signals, by calling function directly, something like this:

# emit_signal("hit", player) # this can be done by calling method on player
get_tree().root.find_node("Player").on_enemy_hit(self)

If you have many players and many enemies, you can use groups to notify one group. When you instance scene, you add it to group using add_to_group. When you need to notify all nodes in group, you use get_tree().call_group method.

And there are many other solutions. But, as I already said, optimal solution is depend on your situation.

AlexTheRegent | 2021-01-06 15:07

Let me explain again, I think that’s not the error for not getting expected result or maybe it is, I’m confused.
So, I have a Mainscene.tscn and a coin.tscn.
I have instanced coin scene by once dragging and dropping it into the Scenetree from the Filesystem.
Then, I have made it so that the res://coin.tscn is instanced over (above) every tile I have for my player to land on/interact with.
The manually instanced coin, I’ve used that to edit and connect signal connections to the game’s main script.
In the coin.tscn’s script I’ve made my custom signal for when player’s kinematic body will enter coin’s area2D.
That is connected to mainscript - main.gd’s receiver method
(say, func _on_receive():)
I also have a variable for score initiated 0, which is set to increase by +1 in the receiver method of mainscript.

Now, what I want is that the coins instanced through code should also behave the same as the one coin manually instanced.

But the problem is that when my player enters the manually instanced coin’s area it increases score by +1 but never does this when player enters areas those of its instances created via code.

I tried to check whether or not the signal was being emitted when player enter the coin instanced created through code by adding a print(collected) statement. And that is printed errorlessly, equal times as there are coins collected.

So, that’s why I think there’s not the signal problem. Just the score doesn’t increase, it turns 1 when manually instanced coin is collected and never increases again when coins instanced through code are collected.

Scenetree:
Mainscene.tscn → main.gd → receive signal “collected” and then increase score = score + 1
^
| coin scene instanced to mainscene
|
coin.tscn and all instances → coin.gd → emit signal “collected”

I hope I explained the ‘My Case’ deep and better.

I can post you the code snippets if you want.

devAS | 2021-01-06 16:21

You can add function to main.gd to spawn coin and connect it to signal:

func spawn_coin(position):
    var coin = load("path/to/coin.tscn").instance()
    coin.connect("collected", this, "_on_receive")
    add_child(coin) # add_child to node where you keep your coins

This way all created coins by this function will be automatically connected to your main.gd _on_receive method.

AlexTheRegent | 2021-01-07 08:58

Cool, that works fine. But I had to replace this with self 'cuz it said this is not declared in the current scope. Other than that it all works fine.

But I have an array consisting of two types of collectibles, that spawn randomly and each’s score is independently saved. And I use a randi() to randomly pick a type of collectible.
So, your code works on only if there is one collectible. How would I do if it were two or say more.
(Sorry, if I sound too kiddish but I’m new to game dev in general :sweat_smile: )

Thanks for your help @AlexTheRegent.

devAS | 2021-01-07 10:41

Yes, this should be self, I’m so used to C++ and I’m writing examples outside of editor, so my snippets may contain small errors.

You can add second argument to your function as type of coin. Then you can use this variable to select required scene and callback.

func spawn_coin(position, type):
    var coin

    if type == 0:
        coin = load("path/to/silver/coin.tscn").instance()
        coin.connect("collected", self, "_on_silver_coin_collected")
    elif type == 1:
        coin = load("path/to/gold/coin.tscn").instance()
        coin.connect("collected", self, "_on_gold_coin_collected")
    
    coin.global_position = position
    $Coins.add_child(coin) # add_child to node where you keep your coins

Note that this approach is used when you have manageable amount of similar objects. If you will make something like different usable items, this function will grow quickly and it will be hard to maintain it. To solve this problem, config files with path to item’s init scripts might used, instead of hardcoding all objects into one function. But in your case with coins hardcoding approach will work just fine.

AlexTheRegent | 2021-01-07 12:40

Actual code snippets from my project :

  func coinspawner():
	var rcoll = (collectibles[randi()%collectibles.size()]).instance()
	if rcoll == collectibles[0].instance():
		rcoll.connect("coin_collected", self, "_on_coin_collected")
	else:
		print("carrot collected !")
		rcoll.connect("coin2_collected", self, "_on_coin2_collected")

Now, my problem is that the condition if rcoll == collectibles[0].instance(): is not being evaluated. Just the else part is executed straightaway. Why? Or is the entire code wrong or messed up? IJDK ¯\(ツ)

  func _on_coin_collected():
      coinscore = coinscore + 1
      coinl.text = str(coinscore)

  func _on_coin2_collected():
      coin2score = coin2score + 1
      coin2l.text = str(coin2score)

coinscore and coin2core are variables initiated 0 and collectibles is an array as I said in my earlier post = [coin, coin2] , where
var coin = load("res://assets/coin.tscn")
var coin2 = load("res://assets/coin2.tscn") all at the beginning of the file.

Now that you know the exact situation, Any help would be greatly appreciated !

devAS | 2021-01-07 14:05

There is a big difference between scenes and instances. Imagine that you like Apples (scene in current context). This is just word, that describes whole class of apples. Now when you instancing an instance, in words of apples you take one particular apple Apple1 from basket. And when you instancing second apple, you get Apple2. It is obvious that Apple1 can not be Apple2 at the same time (you already removed Apple1 from basket, you can’t get it again from basket). This is reason why your condition always false. You need to compare either scenes or primitive values (strings, floats, ints, etc), but not instances (they are always different). So you can fix your code by next changes:

func coinspawner():
  var rcoll = (collectibles[randi()%collectibles.size()])
  var coin = rcoll.instance()
  if rcoll == collectibles[0]:
      coin.connect("coin_collected", self, "_on_coin_collected")
  else:
      print("carrot spawned!")
      coin.connect("coin2_collected", self, "_on_coin2_collected")

Also if your difference in coins only in amount of points given, you can use one callback for both types of coins using params.

coin.connect("coin_collected", self, "_on_coin_collected", [1]) # for 1 coin
...
coin.connect("coin_collected", self, "_on_coin_collected", [5]) # for 2 coin

Then your callback will look like

func _on_coin_collected(points):
    score += points

Then first type of coins will give 1 point, and second type will give 5 points. But this is optimal solution if difference is only in amount of points given.

AlexTheRegent | 2021-01-07 14:31

No, no, no, Sorry if I’m troubling you a lot. But I think you misunderstood, it is that coin has a separate score and coin2 has its own separate score. It’s not that coin has +1 and coin2 has +5 score; both have +1 score in their own two distinct score counter variables, namely coinscore and coin2score.

devAS | 2021-01-07 15:22

Then use first code example

  func coinspawner():
  var rcoll = (collectibles[randi()%collectibles.size()])
  var coin = rcoll.instance()
  if rcoll == collectibles[0]:
      coin.connect("coin_collected", self, "_on_coin_collected")
  else:
      print("carrot spawned!")
      coin.connect("coin2_collected", self, "_on_coin2_collected")

AlexTheRegent | 2021-01-07 15:29

It says Invalid call. Nonexistent function 'instance' in base 'Node2D (collections.gd)'.
for var coin = rcoll.instance().
I think because rcoll itself is an instance. Then What, Sir ?

devAS | 2021-01-07 15:49

How can I simply check if an instance is of a specific scene ?

devAS | 2021-01-07 16:05

Okay so thanks a lot Sir, I finally solved the problem with this little piece of code:

    var rcoll = (collectibles[randi()%collectibles.size()]).instance()
 	#if rcoll == (collectibles[0]).instance():            #I tried these 
	#if rcoll is coins:                                  #but none worked

	if rcoll.get_filename() == coins.get_path():
		rcoll.connect("coin_collected", self, "_on_coin_collected")
	else:
		rcoll.connect("carrot_collected", self, "_on_carrot_collected")

I hope this approach has no problems. plz reply.
and marked your answer as best 'cuz it solved the initial question I had asked.
Thanks again @AlexTheRegent

devAS | 2021-01-07 16:42

If your collectables is array of load("*.tscn") scenes, it can’t be Node2D. When load loads scene, it will be PackedScene that have instance method.
As for coin spawning function, I am still suggesting usage of int variable as switcher because it is faster than checking types of scenes (your method is working, but it is not optimal).

var coin_type = randi()%collectibles.size()
var coin = collectibles[coin_type].instance()
if coin_type == 0:
    coin.connect("coin_collected", self, "_on_coin_collected")
else:
    coin.connect("carrot_collected", self, "_on_carrot_collected")

This code is simply cleaner and faster.

AlexTheRegent | 2021-01-07 23:33