How to pass arguments from _init() to _ready()?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By JumpJet27
:warning: Old Version Published before Godot 3 was released.

I’m trying to implement a component architecture for scripts for my project. I’d like for each scene to have just one script at its root that creates each script (behavior) and passes the necessary node paths (resources). Example:

# scene_specific_script.gd
extends Node

export(NodePath) var resource_node_path

func _ready():
    add_child(load('res://generic_behavior_script.gd').new(resource_node_path))

The behavior script then has access to whatever resource nodes it needs; agnostic of the scene’s structure:

# generic_behavior_script.gd
extends Node

var resource_node

func _init(resource_node_path):
    resource_node = get_node(resource_node_path)

# Perform operations using resource_node

The issue is that get_node() must be called after the node has entered the tree, as in, from _ready() and not _init(). So, how do I resolve this while still being able to pass arguments through new()? I know I could just store the path in a class variable, but I’d rather not have this kind of temporary value cluttering the namespace. Alternatively, is there a better approach to reusing generic behavior scripts?

:bust_in_silhouette: Reply From: eons

You can do everything on _ready or _enter_tree, the architecture sounds a bit strange if a node needs to access the tree when is outside of it.

There are many other ways to do that like using groups or a specific autoloaded script or scene that takes care of organizing and giving a list of required resources to new nodes as they enter the tree (using notifications, signals).

ps: there is no class/static variables in Godot, only methods.

Well no, the node is expected to access the tree once it has entered. The question is how to wait until that time to call get_node(). I could create an autoloaded script that holds a list to resources, but that seems messy, and defeats the organization of the tree and self-contained scenes.

I guess I meant instance variables.

JumpJet27 | 2017-07-31 13:49

A manager looks messy on the usual way (real singletons) but on Godot are just scenes with special behaviour and location on the tree, you can have many for different uses, like managing connections, pauses, saves and other top level stuff, if well organized (with groups, notifications, etc.), the self-contained approach should not be affected by those utility scenes, on the contrary, that will prevent it because depending on external nodes breaks that containment directly.


The time to call get_node is from _enter_tree and until _exit_tree, on _ready the Node/scene should be fully initialized in the tree so is usually safe to use it there (normally there is no need to process nodes before ready).

If you want to postpone the call (you may need a node that is not -yet - on the tree), call_deferred should help, to assign to a variable do call_deferred on a function that process the initialization part that depends on the get_node (maybe with has_node>get_node and call deferred again if fail).


Adding all to a group and letting something else notify all of them for a special set-up (notify_group/call_group) may be cleaner IMHO.

More or less like the answer of avencherus.

eons | 2017-08-01 00:07

Ah, I guess I didn’t realize autoload could be used on an entire scene. That’s helpful, thank you!

JumpJet27 | 2017-08-01 22:44

:bust_in_silhouette: Reply From: avencherus

Hard to say what you’re going for, but you can always defer function calls to the next frame. Though be mindful of it, as it can become a bit of a timing nightmare very easily.

func _init(path):
	call_deferred("delayed_init", path)
	
func delayed_init(path):
	print(get_node(path))

Aside from that, assuming that you want to do something that falls outside of the default events of Godot, you may want to manage things yourself, create your own init type functions. Something like a setup() and just make sure you have it called from outside during the time you want it to happen.

You would need some parent node or global manager that will use all of these standard functions to query the state of the scene. Then you create the objects, keeping a reference in the manager code. Don’t do anything in their init, ready, enter, or exit. When the manager sees that you’re on the frame you want to be on, or when the tree is all setup the way you want it, loop through them and call your setup function.

As an example, I had a project where there were a bunch of nodes needing other nodes to be setup completely. It would vary if they were 1, 2, sometimes 3 frames after enter or ready happened. My solution was the manager, that would loop in fixed_process() until it could verify all the nodes were in the state I needed them in. Only then would it announce to all the other nodes they could begin doing their setups.

Thanks, I think call_deferred() is the best answer to the question asked.

JumpJet27 | 2017-08-01 22:42