Spawning 3D objects from another Object in Main scene

Godot Version

Godot Engine v4.5.stable.official.876b29033

Question

How to make use on the get_tree().create_timer() to spawn the instantiated scene?

I have the “Spawner” obj in the scene and it needs to “Spawn” a model at a set interval.
Everything works if I use the Tutorial 3D, but as soon as a try to have a “Local timer” attached to the “Spawner” instead of the Main game (Like in the tutorial) it doesn’t do anything.

Current relevent code

extends MeshInstance3D

## enums
## consts

## exports

## public var
var selected:bool = false:
	set(new_value):
		selected = new_value
		if  selected:obj_selection_sprite.show()
		else: obj_selection_sprite.hide()
	get():return selected


## private var
## onready var
@onready var obj_selection_sprite: Sprite3D = $Selected_indicator
@onready var obj_selection_aabb: MeshInstance3D = $SelectionAABB
@onready var obj_tier_1_unit_a:= preload("../tier_1_A.tscn")

## built-in override methods


func _ready() -> void:
	_startup()
	pass


func _process(_delta: float) -> void:
	pass

## public methods

## private methods
func _spawn_timer() -> void:
	var _tier1_a = obj_tier_1_unit_a.instantiate()
	var _spawner_t1_a_location = get_node("Spawner_t1/Spawner_T1_A")
	await get_tree().create_timer(2.0).timeout
	add_child(_tier1_a)

Who and when calls _spawn_timer()?

Keep forgetting to declare when the functions needs to be called (_ready in that case)
Now just need to figure out how to make them spawn outside the spawner

I also made it that each “Spawner” generate its own timer and keeps looping.

func _spawn_timer() -> void:
	var timer_node = Timer.new()
	add_child(timer_node)
	timer_node.timeout.connect(_on_timer_timeout)
	timer_node.one_shot = false
	timer_node.start(5.0)

func _on_timer_timeout():
	var tier1_a = obj_tier_1_unit_a.instantiate()
	var _target_base_position = $"../../Bases/Target_Base".position
	add_child(tier1_a)

Not sure if its the most optimize, but it works right now (Which is the most important step)

There’s a more elegant way to do it:

func spawn_delayed(delay: float) -> void:
	get_tree().create_timer(delay).timeout.connect(
			func():
				var tier1_a = obj_tier_1_unit_a.instantiate()
				var _target_base_position = $"../../Bases/Target_Base".position
				add_child(tier1_a)
	)

You don’t need to cleanup any timer nodes afterwards

1 Like

This is a bit confusing
From my code, it generates a timer to Use and Loop as long as the “Spawner” object is in the scene.
When I try to use your code, it simply does nothing

func _ready() -> void:
	_startup()
	spawn_delayed()
	pass


func _process(_delta: float) -> void:
	pass

## public methods

## private methods
func spawn_delayed(delay: float) -> void:
	get_tree().create_timer(delay).timeout.connect(
			func():
				var tier1_a = obj_tier_1_unit_a.instantiate()
				var _target_base_position = $"../../Bases/Target_Base".position
				add_child(tier1_a)
	)

I feel I’m either not getting a proper delay value or I’m suppose to change the “func()” into “func insert_name” and add that into my _ready function

You didn’t supply the delay argument when calling spawn_delayed()

For completeness, here’s a bulletproof version with default arg value:

func spawn_delayed(delay: float = 1.0) -> void:
	get_tree().create_timer(delay).timeout.connect(
			func():
				var tier1_a = obj_tier_1_unit_a.instantiate()
				var _target_base_position = $"../../Bases/Target_Base".position
				add_child(tier1_a)
	)

func() is an anonymous labmda function. You can use a regular function reference (aka Callable) there instead as well.

func spawn_delayed(delay: float = 1.0) -> void:
	get_tree().create_timer(delay).timeout.connect(_do_spawn)
	
func _do_spawn():
	var tier1_a = obj_tier_1_unit_a.instantiate()
	var _target_base_position = $"../../Bases/Target_Base".position
	add_child(tier1_a)

I think I get it
I was suppose to add a specific value to the (I.E: (delay = 1) arg
The reason we add in “delay: float” is to make sure the delay can read a floating number.

And the “func()” is simply to tell our code to read it as a valid func, specific to the “Parent function” (spawn_delayed in our case), without having to seperate it in its own function.

delay is our function argument. The function expects to get it from the caller:

func _ready():
	spawn(1.0) # calling the function with an argument

func spawn(delay: float):
	print(delay) # prints 1.0, the value of supplied argument
1 Like

Yes it’s a special kind of function that is constructed ad hoc for the single usage as a signal handler, and thus doesn’t require a name.

1 Like

I really like how clean it look (Considering I will not keep the commented part of it :P)
and I love how labmda function allows me to create function within function in some cases

The only current issue is that I can’t really use get_tree().create_timer() since it is a one_shot timer
For my project I need the timer to be active as long as the Entity (Spawner) is active in the scene.

func spawn_delayed(delay: float) -> void:
	var timer_node = Timer.new()
	add_child(timer_node)
	timer_node.start(delay)
	timer_node.timeout.connect(
		func():
				var tier1_a = obj_tier_1_unit_a.instantiate()
				var _target_base_position = $"../../Bases/Target_Base".position
				add_child(tier1_a))
	timer_node.one_shot = false
	#get_tree().create_timer(delay).timeout.connect(
			#func():
				#var tier1_a = obj_tier_1_unit_a.instantiate()
				#var _target_base_position = $"../../Bases/Target_Base".position
				#add_child(tier1_a))
	#get_tree().create_timer(delay).one_shot=false

With the previous, mixed with the lambda function, is seems to work as expected.
Unless there is a way to have “get_tree().create_timer(delay).one_shot=false” possible, it will stay like this so I can move foward with my project.

You can still do it without timer node, using a tween or making a coroutine.

With tween:

func _ready():
	spawn_perpetual(1.0)
	
func spawn_perpetual(delay: float = 1.0) -> void:
	var t = create_tween().set_loops()
	t.tween_interval(delay)
	t.tween_callback(_do_spawn)

Or coroutine:

func _ready():
	spawn_perpetual(1.0)

func spawn_perpetual(delay: float = 1.0) -> void:
	while true:
		await get_tree().create_timer(delay).timeout
		_do_spawn() 
1 Like