"Attempt to connect nonexistent signal" error with custom signals

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

I have a player class with a simple signal:

extends KinematicBody2D
...
signal player_moved(position, facing)

I also have a WarpAtBoundaries class which tries to connect to this signal:

extends Node

func _ready():
	print("Auto-warp reporting")
	# Called when the node is added to the scene for the first time.
	# Initialization here
	self.connect("player_moved", self, "_on_player_moved")

func _on_player_moved(position, facing):
	print("Player moved to " + str(position) + " and is facing " + facing)

In the debugger, I see an error that Attempt to connect nonexistent signal "player_moved" to .... I see the auto-warp reporting message, but not the print of the player’s coordinates.

My hierarchy for these two nodes:

  • Start.tscn is a node with a Player instance
  • Start.gd calls load(...).instance() to create an instance of a map
  • That map (eg. Meadow1.gd) calls load("WarpAtBoundaries.gd").new()

I don’t understand why the signal isn’t being defined / being connected correctly. I can add print statements and see the event is firing, but I can’t connect/receive it.

I tried calling load("Player.gd") in the warp class, and adding a singleton “SignalManager” class like this post, thinking that the warp class doesn’t know about the signal; but the error persists.

:bust_in_silhouette: Reply From: Xrayez

You need to fetch the Player instance first, and connect its signal into WarpAtBoundaries instance:

onready var player = load("Player.gd").new()

func _ready():
    player.connect("player_moved", self, "_on_player_moved")

self is WarpAtBoundaries instance, and because it doesn’t define player_moved signal, you get the described error.

As you stated, you have your Startscene which acts as a main scene I suppose. If you already instance a player in that scene:

onready var player = $player # or get_node("player") in Godot 2

Or in a singleton like Characters:

const Player = preload("player.gd")
var player = Player.new()

And then in your Start scene:

Characters.player.connect("player_moved", self, "_on_player_moved")

Depending on whether the character can be removed from the scene, you need to connect the signal dynamically if you want to add new player. For that, the SceneTree (fetched with get_tree()) has node_added(node) signal which can be connected to your Start scene like this:

func _ready():
    get_tree().connect("node_added", self, "_on_node_added")

 func _on_node_added(node):
    if node is Characters.Player:
        var player = node
        player.connect("player_moved", WarpAtBoundaries, "_on_player_moved")

The reference to the player will be always kept in the singleton unless you explicitly queue_free() it or replace it with the new player.

This might be over-complicated but it really depends how big your game is going to be.

Okay, this makes sense.

I don’t want to create a new Player instance. because I have a pre-existing one. This brings me full-circle to a previous problem: how can I elegantly handle keeping a single(ton) reference of Player? Godot doesn’t support static variables from what I understand.

nightblade9 | 2018-07-10 13:42

See my updated answer :slight_smile:

Xrayez | 2018-07-10 14:02

That’s cool, thanks for this. I think the linked code is more like what I want (global signal handling); I’m still confused why SignalManager.connect("player_death", "self", "on_player_death") gave the same error. I’ll try this tonight and let you know how it works out. Thanks again!

nightblade9 | 2018-07-10 15:08

Thanks for your help. I ultimately went with a simpler approach of centralizing my signal management into a single class.

nightblade9 | 2018-07-11 03:26

:bust_in_silhouette: Reply From: nightblade9

While Xrayez’s answer was critical to understanding the syntax of emit_signal and connect, ultimately, I went with a completely different approach:

  1. I created a SignalManager class which contains all my signals, eg. player_moved
  2. I configured my Godot project to AutoLoad this script as a singleton
  3. Whenever the player moves, I call SignalManager.emit_signal("player_moved", ...)
  4. In other classes that care, I call SignalManager.connect("player_moved", self, "_on_player_moved")
  5. I add a method _on_player_moved to that class
  6. Profit

Just would like to show my gratitude for your response, helped me a lot!

Kidoncio | 2019-10-24 02:07

Glad to hear it helped someone else! Ultimately, it’s not how Godot’s designed to work, and it has some downsides like global state, but IMO it’s much simpler to use overall.

nightblade9 | 2019-10-24 11:47

Your solution was the best I ever seen. Helped me a lot!
Thanks.

Iderval | 2021-04-28 16:00

Love this method to painlessly manage signals! Signed up just to upvote!

CriticalMammal | 2023-05-02 00:14