Asynchronous loading of main game level at game start

Godot Version

Godot v 4.1.1

I’m trying to load in my main game node, as well as my player node as soon as the application begins. The only way currently I can get them to work is if I use call_deferred() on adding them to the root tree as children. However, when I try to use await world.get_tree().create_timer(1.0).timeout after the world and player are added to the root node in the _ready function, I get a nil reference. I know this is because the world and player only get added at the end of the frame because they are deferred to the end of the frame. But I’ve been really confused about how to implement it since I have to instantiate my world nodes in code on application start, or on a new game button click, since I randomly position things.

Essentially I’m trying to figure out what the best solution is for using an autoload singleton to create the main game world on application start. Thanks.

extends Node

@onready var enemies: Array[Node2D] = []
@onready var nearest_enemies: Array[Node2D] = []

@onready var max_enemies: int = 20

var player: Node2D = preload("res://scenes/player.tscn").instantiate()
var world: Node2D = preload("res://scenes/game.tscn").instantiate()


@onready var enemy: PackedScene = preload("res://scenes/enemy_rectangle.tscn")

var top_left_spawn_point: Vector2 = Vector2.ZERO
var top_right_spawn_point: Vector2 = Vector2.ZERO
var bottom_left_spawn_point: Vector2 = Vector2.ZERO
var bottom_right_spawn_point: Vector2 = Vector2.ZERO

func _ready():
	get_window().add_child.call_deferred(world)
	world.add_child.call_deferred(player)
	#await world.get_tree().create_timer(1.0).timeout
	spawn_enemy_wave.call_deferred()

func find_nearest_enemies():
	pass
	
func spawn_enemy_wave():
	var screen_rect: Rect2 = player.get_viewport_rect()
	screen_rect.size = screen_rect.size / 2
	
	top_left_spawn_point = player.position + Vector2(-screen_rect.size.x, -screen_rect.size.y)
	top_right_spawn_point = player.position + Vector2(screen_rect.size.x, -screen_rect.size.y)
	bottom_left_spawn_point = player.position + Vector2(-screen_rect.size.x, screen_rect.size.y)
	bottom_right_spawn_point = player.position + Vector2(screen_rect.size.x, screen_rect.size.y)
	
	var side_array: Array[String] = ["top", "bottom", "left", "right"]
	var start_point = Vector2.ZERO
	var end_point = Vector2.ZERO
	
	while enemies.size() <= 20:
		#print("ENEMY SPAWNED")
		var new_enemy = enemy.instantiate()
		enemies.append(new_enemy)
		world.add_child(new_enemy)
		var spawn_side: String = side_array.pick_random()
		match spawn_side:
			"top":
				start_point = top_left_spawn_point
				end_point = top_right_spawn_point
			"bottom":
				start_point = bottom_left_spawn_point
				end_point = bottom_right_spawn_point
			"left":
				start_point = top_left_spawn_point
				end_point = bottom_left_spawn_point
			"right":
				start_point = top_right_spawn_point
				end_point = bottom_right_spawn_point
		new_enemy.position = Vector2(randf_range(start_point.x, end_point.x), randf_range(start_point.y, end_point.y))
		
		
func add_enemy(spawn_position: Vector2):
	pass

func remove_enemy():
	pass

Usually any asynchronous loading involves threads.

Thanks. I think I phrased the question wrong. I didn’t quite mean asynchronous, more like using a delay after instantiating nodes and how to ensure that they’re instantiated and not null before trying to access them.

Check notifications.

Specifically _enter_tree()

Ok thanks I’ll give it a try