Todu
Todu

Reputation: 13

Godot 4.1 trouble with signals not connecting in two different scripts

I'm having issues with signals, I'm trying to get my character to adopt a new position when they change scenes, I'm using an Area2D to detect if the player is changing scenes. Here is the code:

scene_change_detection code:

extends Area2D

@export var scene_to_load: String = ""
#initial character position
var character_position: Vector2 = Vector2(0, 0)
@export var change_character_position: Vector2
@export var direction_facing: String = ""
signal scene_changed(change_character_position: Vector2)
signal direction(direction_facing)

func _on_body_entered(body):
    if body is CharacterBody2D:
        if scene_to_load != "":
            character_position = body.global_position
            get_tree().change_scene_to_file(scene_to_load)
            emit_signal("scene_changed", change_character_position):

#getters

func get_character_position() -> Vector2:
    return character_position

Player code:

extends CharacterBody2D

func _ready():
    var scene_change_detection = get_node("/root/SceneChangeDetection")
    scene_change_detection.scene_changed.connect(_on_scene_changed)

func _on_scene_changed(new_character_position: Vector2):
    print("Received scene_changed signal")
    $Dahlia.global_position = get_node("/root/SceneChangeDetection").new_character_position

Problem

No matter what I do _on_scene_changed is never called. I tested with print statements if the emit was called, and it was. Only the connect is giving me issues.

I've tried creating signals in one script and trying to get them to call _on_signal_name() , none of them accomplish that. My Area2D is connected to _on_body_entered My CharacterBody2D isn't connected to anything (I'm not sure if I'm supposed to connect it to something)

I've been stuck on this problem all day, please help.

Upvotes: 1

Views: 1092

Answers (1)

Theraot
Theraot

Reputation: 40295

When a node is freed, all its connections will be disconnected. When the scene changes, all the nodes in the prior scene will be freed.

You, of course, use an autoload to address that, since they survive the scene change, by virtue of not being part of it.

If neither the Area2D or the CharacterBody2D are in autoloads (autoloads can be either scripts or scenes), they won't be able to communicate. As the prior scene is freed before the new one is connected. Yes, even for signals defined in an autoload.


I'll start by assuming your Area2D is an autoload. That leaves two cases about the CharacterBody2D:

  • CharacterBody2D is not an autoload.

    So the CharacterBody2D in the prior scene, and it will be freed before it can react to the signal.

    And the CharacterBody2D in the new scene won't be connected yet when the signal is emitted.

    Either of those, the answer is that you do not need a signal: When Godot calls _ready in CharacterBody2D which is part of a scene, it means said scene is loaded.

    If you need to keep information from one scene to the other, you can store it in the autoload, and then have the CharacterBody2D read it on _ready.

  • CharacterBody2D is in an autoload.

    So it also survives the scene change. And in that case, the signal makes sense, and should work.

A similar argument would go if the Area2D is not an autoload.

Thus, the way I see it, either: they are both autoloads, and the signal makes sense. Or you do not use signals for this communication at all (instead store the information you need to preserve in an autoload, and read it on _ready).

It is also viable to store information in a preloaded custom Resource, or in static variables.


You can have multiple instances of an autoload (they are not singletons, despite the documentation: Singletons (Autoload)).

Whatever you put as an autoload will be loaded before the main scene, and you do not need to write code for that. But nothing precludes the creation of other instances. So when you add an instance of something you have as autoload in your scene... That is another instance, not the same one.

So you might have the character script as an autoload, and also the character in the scene, and those are two different instances. And what happens to one of them does not affect the other (for example, the one on the scene might interact with an area, but not the autoloaded one).

Whatever you autoload will remain available regardless of scene changes (unless you write code to free it). And it will be available using the name you give the autoload in every Node in the scene tree. So any Node of any scene can write code that uses it (to call method, read or write variables, or to emit signals or connect to them).

Thus, it makes sense to have an Area2D in your scene that has an script that sets variables of an autoload, calls a methods of an autoload, or even emits signals of an autoload. And this does not require having an instance of whatever you have autoloaded in the scene.

In fact, if you have instances of whatever you have autoloaded in the scenes, they would be freed with the prior scene, and loaded with the new one, so they do not help. You want to be using the autoloaded instance, not other instances of the same thing but inside the scenes.

You could autoload your character, which will have it load before the main scene (which might not be desirable). You might:

  • Write code that instantiates the player character inside an autoload.
  • Or store the player character in an autoload while you change scenes (this means removing it from the scene, change scene, and then add it again to the scene)
  • Or it might be sufficient to have the player character in every scene, and have it communicate with the autoload in _ready.

If you are not having the character inside the scene, you will need to define where you place it. To do that, I suggest using a Marker2D, either with a particular unique name, or as part of a particular node group, so you can quickly find it from code, and then teleport the character to its position. That way you do not have to hardcode the positions in code.

If there are multiple entrances that should take the player to different positions inside the destination scene, you store in an autoload which entrance is being used before changing scene, and then use that to pick which Marker2D you will teleport the character to.

I know I'm not giving you a recipe. You have plenty of options. Once you are familiar with how the autoload works, picking whatever is your preference would not be a problem.


We have been assuming you use the API Godot has to change scenes. But it is all nodes.

  • You could take control of the change of scenes. See Change scenes manually.
  • Or - if you are like me - you ditch the API to change scenes altogether. Instead you would have one single main scene which is never changed. And this main scene would have code to instantiate other scenes inside of it.

I'll also reiterate that preloaded custom Resource and static variables would also remain loaded.

Upvotes: 0

Related Questions