I am trying to instantiate a packed scene. When I do, none of the child nodes of the packed scene get instantiated. When I try to access them with the script, of the instantiated packed scene’s _ready function, I get cannot call method foo and null.
Specifics
The plan is to have a dialog menu that can pop up and call on other events. The overall functionality is working fine. The issue I am running into is that when I save the scene and the add it as a packed scene to be loaded through another event (usually a signal) I get get an error when the packed scene’s script is trying to access the children of the packed scene.
This is the code to instance the packed scene.
class_name DialogChoicePopupSpawner
extends EventResource
@export var dialog_name:String
@export var dialog_scene:PackedScene
func trigger(_ship_body: Node2D, _poi_position:Vector2) -> void:
assert(not dialog_name.is_empty())
assert(dialog_scene != null)
var d := load(dialog_scene.resource_path)
var dialog:DialogChoicePopup = d.instantiate()
dialog_name = dialog_name
print(dialog_name)
Global.level.add_child(dialog)
dialog.popup()
This is a resource that can be added in the level that handles loading these events.
The line var d:= load(…) was something I was trying to fix this issue but it didn’t fix the issue and the scene can be instantiated without calling load.
This is a dynamic dialog popup menu. The code that is throwing the error is called in the ready function of the dialog_choice_popup.
func _ready() -> void:
visible = false
if !buttons.is_empty():
for button in buttons:
call_deferred('add_button', %ButtonContainer, button)
func add_button(n:Node, button_info:ButtonInfo) -> void:
var b = Button.new()
b.text = button_info.Words
b.name = button_info.Id
b.pressed.connect(action)
var callback = func():
for finished_resource in button_info.finished_resources:
finished_resource.trigger(operator, position)
return
b.pressed.connect(callback)
n.call_deferred('add_child', b)
What I am getting is is that %ButtonContainer is null.
This is likely your problem. You’re using call_deferred() to add this button to the tree, and you call that function with call_deferred(). Which means you have to wait at least two frames for the button to be added to the tree. So you have a race condition.
Everything outside of your reference to %ButtonContainer looks like it should work if that does.
load → instantiate → add_child → ready() → dialogue.popup() should work without issue (minus some of the stuff you tried for testing.)
You could try referencing the %ButtonContainer by path in the scene tree (like $ButtonContainer or $Some/Nodes/ButtonContainer) instead of accessing it as unique name to see if it’s the unique name that’s messed up somehow.
The scene I am packing is a custom type. I know Godot can be odd with differences in the ui between add child node and instantiate child scene. Maybe this has to do with one of those issues. I know that if I instantiate a custom scene I cannot set that scene as the root node. Maybe that has something to do with it, or maybe I am going down a rabbit hole with that.
If your script is a @tool script that you’re running in the editor, that could certainly do it. I’ve run into similar issues trying to automate some menu creation stuff.
If it’s at runtime in the game, I don’t expect it to matter.
I think I have found the reason for my issues. You cannot load an instantiated scene as the root of a scene. The DialogChoicePopup is a node in a packed scene. That means it doesn’t have any of the children that it would have if it was an instantiated scene.
I can get around this by having the packed scene be a node with an instantiated DialogChoicePopup scene. I dunno, this is pretty lame. I expect I am missing something in the design here to make this choice make sense.
If you create a scene tree and go to the root of the tree, then right click and tell it to save the “branch as scene” then Godot will not let you. It will tell you to copy the scene files manually or go to the “Scene” menu and click “New Inherited Scene” after you save this as a new scene then you can load this as a packed scene.
I still don’t quite see what the technical limitations are, but the root of a scene cannot be an instantiated scene because you’re expected to create a new inherited scene. I have not seen much documentation around inherited scenes. This seems to be different than inheriting in a OOP class sense but more like you’re inheriting the scene tree from another scene.
It has to do with the difference between adding a node and instantiating a scene.