Autoload and Scene change

Godot Version

4.2

Question

I need some direction please. I have an autoload scrip that loads the player and a couple of other things. Now when i add in a main menu screen this will cause all sorts of issues, i.e. player fails to load or the player loads but the other objects that are looking for the play wont load correctly.

I currently have a script that runs everything from the top node but this also causes issues with spawning the player or it spawns the player and things fail to find the player or the player doesnt spawn at all.

what i want to do is have a main menu and a auto load script which runs everything for each level as it loads.

What is the best way to do this please? Thanks!

Have you thought about signals? You can define lifecycle signals, e.g., “player_loaded” and only then load the player in your other nodes, when the signal has been called.

Do you mind giving me an example please?

Make an autoload file SignalManager

define a signal in it

signal player_loaded(your_player)

When you have loaded/initialized your player

SignalManager.player_loaded.emit(your_player_var)

In your nodes that require player

SignalManager.player_loaded.connect(_on_player_loaded)

func _on_player_loaded(player):
… your logic

You’ll need to expand on this and read up on the docs, Using signals — Godot Engine (stable) documentation in English

I think you are missing some Godot knowledge.

1 Like

If you have an autoload that needs to load resources, can’t you just add the line
func _ready() -> void: await self.ready
and see if this works. Maybe the autoload just needs a TINY bit of time in order to be ready for its variables to be accessible.
This can also be extended to the scenes you want to load, maybe the scenes are inheriting variables from its parent scene which causes loading issues because Godot thinks everything must be ready and loaded on frame1

1 Like

Project version 0.1 (no autoload script)

structure:
Root Node → Level1 Node

level1 script

func set_player_position(_new_pos: Vector2) -> void:
	if player == null:
		player = PLAYER.instantiate()
		var level = get_tree().root.get_node("Level1")
		level.call_deferred("add_child", player)
		level.call_deferred("move_child", player, 0)
	player.global_position = _new_pos

This spawns the player on the spawnpoint, however, other objects looking for “Player” node are now stating the following error.

E 0:00:00:0990 doorsstairs.gd:6 @ _ready(): Node not found: “…/…/Player” (relative to “/root/Level1/Door Container/DoorStairs_a1”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
doorsstairs.gd:6 @ _ready()

When running the game the Player is there and the path as far as i can tell is …/…/Player. I check this via the remote node view.

So this method here does not use autoload script to spawn the player but when i click start game it switches scene to Level1 and then begins to load its nodes/objects.

Project version 0.2 (autoload script)

structure:
Root Node → Menu Node → main_game node

Menu Scene
Two buttons Play and Quit. Press Play and load the main_game node

Global_game_manager script

extends Node

const PLAYER = preload("res://Scenes/Player/Player.tscn")

var player: Player
var player_spawned: bool = false

func _ready():
	add_player_instance()

func add_player_instance() -> void:
	player = PLAYER.instantiate()
	var main_game = get_tree().root.get_node("MainGame")
	main_game.add_child(player)

func set_player_position(_new_pos: Vector2) -> void:
	player.global_position = _new_pos

When I run the project i get an error,

Attempt to call function ‘add_child’ in base ‘null instance’ on a null instance.

Now I am guessing this has something to do with the autoload script trying to do what I am telling it to do while trying to load the first scene which is the Menu scene. Menu scene does not load or anything else.

Is a signal bets used to delay the autoload script or use the following to wait?

func _ready() -> void: await self.ready

Or am i looking to do something completely different all together?

Project version 0.2 (autoload script)
I believe your autoscript is loading first and your main_game second, that’s why var main_game = get_tree().root.get_node("MainGame") returns null.

Could we maybe go about it in another way?
In your MainGame node, there should be a func _ready() method.
Because your GameManager is a Global Autoload, you could then add
GameManager.add_player_instance() which will make sure the Player is added after your MainGame is ready.

Currently your func _ready() method in your GameManager may be a bit too hasty.

1 Like

Okay I tried this.

Maingame script

func _ready():
	GlobalGameManager.add_player_instance() 
	pass

Invalid set index ‘global_position’ (on base: ‘Nil’) with value of type ‘Vector2’.

The above error is now upset with the following code within the GGM script.

func set_player_position(_new_pos: Vector2) -> void:
	player.global_position = _new_pos

I have fixed this is once before but i have no idea how i did the first time round… Any ideas?

Thanks for help folks!

I mean, this just means your GlobalGameManager.add_player_instance() is somehow executed after your set_player_position() method.

Based on our conversation, I sadly have no idea where and at what point you use that method. Is your project uploaded onto github or somewhere else?

It is not but It wont be difficult to explain.

node order
image

Code within the spawnplayer node

func _ready():
	#sets the nodes visibilty to false!
	visible = false
	if GlobalGameManager.player_spawned == false:
		GlobalGameManager.set_player_position(global_position)
		GlobalGameManager.player_spawned = true

I had this issue when i was first trying to put this together and it was the order in which they were called.

Going to have a sleep on it and see if Monday sheds some light, however, if you have any ideas let me know.

to be honest, I find the code procedure a bit confusing.
I would have thought that the SpawnerPlayer is responsible for spawning the player, but it only sets the position of the already instanced player.
Also the spawnplayer node only checks on _ready() if the player is spawned? I thought it should always happen whenever a new level is loaded.
Anyways, I would enforce a code order.

  1. Levelscene is created
  2. Player is instanced by GlobalGameManager
  3. SpawnPlayer sets the location of the instanced player

How I would do that is to create a signal in GlobalGameManager
signal player_instanced and at the end of add_player_instance() you add the following line: player_instanced.emit()

in your spawnplayer, you write this

func _ready():
	#sets the nodes visibilty to false!
	visible = false
	GlobalGameManager.player_instanced.connect(_on_player_instanced)

func _on_player_instanced() -> void:
	if GlobalGameManager.player_spawned == false:
		GlobalGameManager.set_player_position(global_position)
		GlobalGameManager.player_spawned = true

What it does is that the SpawnPlayer node now waits for the signal of the GlobalGameManager that ensures that the player is actually instanced. Right now I am unsure what GlobalGameManager.player_spawned actually does but I will leave this to your freedom.

Doing what you have said now causes a null ref when loading the first scene which is fine as its trying to load the player.

SpawnerPlayer node is just a node used to spawn the player to using the nodes vector2 position.

I think i need to ide step here and start again.

What is in my head,

  • Title Menu scene - Press start and load level 1
  • Level 1 - loads the scene
    • instantiate player
    • load all other nodes

Is an auto script worth doing or should the Level 1 node have a script that runs all this code that a autoload would use? This is where Project version 0.1 (no autoload script) comes in and the error where nothing can find the Player.

Defo confused myself here…

Doing what you have said now causes a null ref when loading the first scene which is fine as its trying to load the player.

From what I understand after reading your scene progression, I believe you mean when your Title Menu scene loads, the PlayerSpawner node causes a null ref because it tries to place the player that’s not actually instanced yet?

This is strange because via Signal the PlayerSpawner should only place the player if it receives the Signal from the level.

At this point, I think I would just ask you to upload the project somewhere or a repository so I can check out the code for myself.

I will need time to get the project uploaded. Not really wanting it in a public space so may need to send you a private link.

GlobalGameManager script

extends Node

const PLAYER = preload("res://Scenes/Player/Player.tscn")

signal player_instanced

var player: Player
var player_spawned: bool = false

func _ready():
	add_player_instance()
	player_instanced.emit()
	pass

func add_player_instance() -> void:
	player = PLAYER.instantiate()
	var main_game = get_tree().root.get_node("MainGame")
	main_game.add_child(player)


func set_player_position(_new_pos: Vector2) -> void:
	player.global_position = _new_pos

SpawnerPlayer node script

func _ready():
	#sets the nodes visibilty to false!
	visible = false
	GlobalGameManager.player_instanced.connect(_on_player_instanced)

func _on_player_instanced() -> void:
	if GlobalGameManager.player_spawned == false:
		GlobalGameManager.set_player_position(global_position)
		GlobalGameManager.player_spawned = true

So i am causing the issue as the autoload is trying to spawn player and not even getting to the playerspawner signal.

Please adjust your GlobalGameManager script

func _ready():
    pass # You can delete your _ready() method

func add_player_instance() -> void:
	player = PLAYER.instantiate()
	var main_game = get_tree().root.get_node("MainGame")
	main_game.add_child(player)
	player_instanced.emit() # Only emit once the player was instanced

Okay chnages have been made. Nodes load in order and player is added to main_game scene, however, I have come full circle to my problem described above with project version 0.1.

all other nodes within the project that have scrips looking for the player state the following error.

E 0:00:02:0395 doorsstairs.gd:5 @ _ready(): Node not found: “…/…/Player” (relative to “/root/MainGame/Door Container/DoorStairs_a1”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
doorsstairs.gd:5 @ _ready()

DoorStairs Script example.

class_name doorsstairs extends Area2D

@onready var player = $"../../Player"

My question would be why the doorsstairs need to know the player node onready because I believe the player is instantiated only after the level is ready.

For a workable workaround solution, readjust the Doorsstairs script like this (why the double ss):

class_name doorsstairs extends Area2D

var player

func _ready() -> void:
    GlobalGameManager.player_instanced.connect(func(): player = GlobalGameManager.player)

This will ensure that the doorsstairs will listen to when the player is actually instanced and added to the scene.


The provided solution assumes that there is no func _process() method that constantly checks for the player inside doorsstairs.gd

1 Like

Its a great question which in a year or so I will ask myself also. Doing this for about 8 weeks so not really gotten a handle of things within the programming world.

This has worked but has left me with another issue.

@onready var p_marker_2d = $"../../Player/Marker2D"

var player
var object_picked_up: bool = false

func _ready():
	GlobalGameManager.player_instanced.connect(func(): player = GlobalGameManager.player)

Marker2D cant be found, however, with some thinking im sure i will work it out.

Really appreciate all your help!

Edit: Aye this has just broke everything in the game which requires player checks. Think this will be a while to rework everything.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.