Singleton script has different behavior depending on how the current scene was loaded

Godot Version

4.0.3

Question

In my game, starting and playing any individual level directly from the editor works flawlessly. As soon as I start from the main menu though, selecting a level to play from there results in something being broken or missing from the level, but only sometimes.

One thing that’s broken every time though is an instantiation function I have in my Global script to instantiate common instances like blood splatters. It’s being used to pickup and drop weapons, and this system works well when loading straight into a level, but utterly broken when loading in from the main menu first.

I found the cause: a variable isn’t being set in this function when you’ve loaded a scene from the main menu. Here’s the relevant code of the function:

func instantiate_node(packed_scene, pos = null, parent = null):
	var clone = packed_scene.instantiate()

It’s crashing when I go to set the global_transform of clone but there’s nothing there to set.

Here’s what the breakpoint looks like when the function is working:
Screenshot 2024-02-15 160553
When it’s not working, clone is null.

How should I go about solving discrepancies in the behavior of my code that’s dependent on how a scene was loaded? Would it be easier to just do the instantiate code outside of a Global scope? Cause it feels like the order of the code being executed is off or something.

I think you should look at the call stack because why is packed_scene null?

If the packed scene used load it may have returned null. The documentation suggests using preload for all static path resources.

Btw when I instantiate a scene I use a static function inside the class. You don’t have to do this but it is an alternative approach.

Example:

extends Node2D
class_name Enemy

static func new_enemy(params) -> Enemy:
  const res_path = "res://path/to/enemy.tscn"
  var enemy = preload(res_path).instantiate()
  enemy.my_init(params)
  return enemy 

Use example:

var new_enemy = Enemy.new_enemy(params)
add_child(new_enemy)
...
2 Likes

I could never figure out why packed_scene returns null because it does that when the function actually works, so I left it alone. I’m also already using preload for all my resources that use this function, and they’re all working every time.

I did find that in my code, I’m calling a function to drop the weapon before I call the function to instance the new weapon, so that’s the area that might need attention.

So I’m not sure, I would only ask now is why are you on 4.0.3 and not at 4.2.1? In the least you should go to 4.1.0 because early 4.0 builds had many issues.

I took a peak around, there seems to be many who also have had similar instantiate returns null issue most with no real resolution. The only thing in GitHub issues that resembled this was issues with scripts to packed scenes that defined an _init function with parameters with no defaults. Do your scenes use the_init function with parameters?

From what you described there is probably some handling of the scene through your Singleton that is sus.

1 Like

In my stubbornness, I usually wait until something like this happens to upgrade my Godot build so it sounds like I’ll try that. No scene that I put through this function uses the _init function.

I’ll report back when I’ve moved to 4.2.x

Updated to 4.2.1 and it looks like the actual culprit is the main menu itself. When you load into the main menu first before other scenes, a plugin (Qodot) resource reference breaks when entering levels that use that plugin and the game hangs while loading.

This bug previously only happened in earlier test exports. I guess newer versions of Godot more closely match the final product after export when running in-editor. Everything works now except for this so at least now I know how to go about fixing things. Thanks.

I placed an instance of the player in the main menu out of sight, then I removed the player immediately in _ready() in the menu’s script. And suddenly everything is fixed and the game runs flawlessly. Incredible.