Instantiate scenes with factory like pattern and singleton lifetime behavior

Godot Version

4.2.1 stable

Question

I’ve discovered an behavior, which I can’t really explain.

I have a scene, which is created using ".instantiate()".
Contrary to my expectations, I have already learned in Github that scenes, which are created ".instantiate()", the pseudoconstructor "._init()" is not executed.

Therefore u aren’t able to pass parameters while instantiating scenes/nodes using this method.
Heres the link: Make it possible to pass arguments to `PackedScene.instance()` · Issue #1513 · godotengine/godot-proposals · GitHub

Since it’s not a solution for me to fill the properties by hand, after every ".instantiate()", of the respective scene, i’d like to have a clean API for instantiation.
I tried to create a method that i could just call myself (something like my own “pseudoconstructor”).
While this solution is not optimal, as it does not get called automatically it’s better than nothing.

However, I encountered strange behavior that I can’t explain.

Here is a simple example:

laser.gd

class_name Laser
extends Area2D

var speed: int = 0
var direction: Vector2 = Vector2(0,0)

# expected to be a instance method
func custom_psuedoconstructor(spawn_position, direction, speed):
	print(spawn_position) # (268.7128, 598.5436)
	print(direction) # (-0.973798, 0.227414)
	print(speed) # 3000
	
	position = spawn_position
	direction = direction
	speed = speed
	rotation_degrees = rad_to_deg(direction.angle())

# should process with values from member variables but it isn't
func _process(delta):
	print(speed) # 0
	print(direction) # (0, 0)
	
	position += direction * speed * delta

level.gd


extends Node2D

# Import the laser scene
var laser_scene: PackedScene = preload("res://scenes/projectiles/laser.tscn")

func _ready():
	# Changes the arrow shape of the cursor.
	Input.set_custom_mouse_cursor(arrow)

func _on_player_player_laser_shooted(pos, direction):
	var laser = laser_scene.instantiate() as Laser
	
	# This works as expected and changes the member variables that are used
	# by the instance
	#laser.position = pos
	#laser.rotation_degrees = rad_to_deg(direction.angle())
	#laser.direction = direction
	
	# This does not work as expected and leads to the behavior described
	laser.custom_psuedoconstructor(pos, direction, 3000)
	$Projectiles.add_child(laser)
	

I assumed that the variables and functions that are within a script are created at object or instance level and are not created as global Functions and variables.

I couldn’t find andy information in the GDScript documentation about whether there is a distinction between instance methods, member variables, and and singletons vs global variables, functions and multiples.

I’m still a beginner in Godot and GDScript and I think I’m doing something fundamentally wrong here.

Can someone help me and explain how to use Global Functions or Instance Methods and member variables in GDScript?

Thank you in advance - Danloc

You named the method parameters with the same name as the properties of the class and you are assigning the method parameters to the method parameters:

# expected to be a instance method
func custom_psuedoconstructor(spawn_position, direction, speed):
	print(spawn_position) # (268.7128, 598.5436)
	print(direction) # (-0.973798, 0.227414)
	print(speed) # 3000
	
	position = spawn_position 
	direction = direction # <---- HERE
	speed = speed # <---- HERE
	rotation_degrees = rad_to_deg(direction.angle())

If you want to assign it to the properties of the class then you need to use self:

# expected to be a instance method
func custom_psuedoconstructor(spawn_position, direction, speed):
	print(spawn_position) # (268.7128, 598.5436)
	print(direction) # (-0.973798, 0.227414)
	print(speed) # 3000
	
	position = spawn_position 
	self.direction = direction # <---- Assigning the method parameter 'direction' to the property of the class 'direction'
	self.speed = speed # <---- Assigning the method parameter 'speed' to the property of the class 'speed'
	rotation_degrees = rad_to_deg(direction.angle())

Not useful to your issue but _init() gets called when using instantiate(), it just can’t have any required parameter or it will fail as the engine call the method with no parameters.

Thanks that works! And thanks for the clarification.

could you provide a link in the docs where i can read more about it?

Is there a possibility in GDScript to have global functions instead of instance methods?

Here’s the GDScript documentation GDScript — Godot Engine (stable) documentation in English