2
\$\begingroup\$

I'd like events/actions (e.g. pressing a button, defeating a mini-boss, etc.) in one area of the game to affect things (e.g. unlock/open door, enable boss spawn) in other areas that are distinct/distant enough to exist in another scene.

When the trigger and target are in the same scene (e.g. switch opens door in the same room), it's easy enough to add export(NodePath) var door_to_open to the switch's script so that it has a direct reference to the door and can tell it to open. If I move/rename the door node, I just have to update the NodePath, and immediately get an error if I don't (e.g. target_door = get_node(door_to_open) as Door and assert(target_door != null) in the switch's _readyfunction).

When they are in different scenes, though, such a link isn't possible (since the target node isn't loaded). I've handled this so far by creating an Autoload/Singleton for tracking door state. When triggered, the switches pass in the name of the door they open (something unique but kinda ugly, like "Building1-Floor7-Door3") and it gets included in a list of open doors. In the _ready function of the door script, I call into the same Singleton to check whether the door should be open or closed. This works, but is quickly becoming a maintenance headache. Moving things in the scene tree and changing the node path isn't an issue, since each door has a name property associated with it. However, changing the names (infrequent, but happens as I'm building things) breaks the link and requires changing the name in both locations (the door in once scene getting opened, and the switch in some other scene that opens it). The worst part is that forgetting to change it (or just making a typo mistake) just means that the names used by the two ends of the set/get are different, and it just looks like the door is not open yet and no error happens (it's perfectly fine for a door's name to not be in the list; it just means it hasn't been opened). Makes testing/debugging more difficult.

Is there a more straightforward way to establish a (one-way) link between two nodes in different scenes? Or at least to consolidate the name variable so that there aren't two independent String declarations (switch, door) that might get mismatched at some point?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Autoload

I believe the Autoload approach is correct. In particular if you need to persist the state of the doors, or the door you are opening might not be loaded yet (which - if I understand correctly - is the case).

Perhaps you can work on a naming convention that is more resistant to changes. What comes to mind is to name the door based on the rooms the door connects (you could give code names to the rooms).


Is there a more straightforward way to establish a (one-way) link between two nodes in different scenes?

You can use a signal bus (event bus). That is, you declare signals in an Autoload, and other nodes can connect to it, or emit it (yes, the script of an object can emit signals of another object). However, it does not persist any state, and requires the node on the other side to be loaded and connected to have any effect.


Resource based communication

Or at least to consolidate the name variable so that there aren't two independent String declarations (switch, door) that might get mismatched at some point?

There is a way: use resources based node communication.

For example, you can create a new script from the File System panel by choosing "New Script...", and there declare a DoorLink (or however you want to call it) class that extends Resource.

Once you have the resource class, you can make resources of that type from the File System panel (on the context menu choose "New Resource…" and pick your class when asked).

Then you can either export a resource variable (which, to be fair, has lack luster support, see proposal 18), so you can set your resource there (you can drag it from the File System panel to the Inspector panel), and Godot will keep track of it if you rename the resource. To be clear: drag the resource file, not the script with the resource class.

Or you can preload the resource file, and Godot will report a parse error if the path is not correct.

And, as a bonus, they all get the same object. Everywhere you have set the same resource will have the same object. So you can use it for communication. I suggest to declare a property with a signal when it changes in the resource class.

For example:

door_link.gd

class_name DoorLink
extends Resource

signal is_open_changed(new_value)

export var is_open:bool setget set_is_open

func set_is_open(new_value:bool) -> void:
    if is_open == new_value:
        return

    is_open = new_value
    emit_signal("is_open_changed", new_value)
    emit_signal("changed")

By the way, the Resource class defines a "changed" signal, which is meant to be emitted when the properties of the resource. We have to do that manually for custom resource classes. And so I did in the code above.

The main drawback is, of course, that you would have a bunch of resource files.

\$\endgroup\$
1
  • \$\begingroup\$ Yeah, the 4096 proposal doesn't apply because I'm specifically concerned about the case where I need to affect something in a scene that is not currently loaded in the tree. I'll have to think about the resource approach. Maybe that would be a way to simplify things. \$\endgroup\$
    – yoozer8
    Mar 5, 2022 at 4:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .