For continuous level map loading in 3D game, instead of instantiating whole PackedScene of newly reached level map zone, I read SceneState of the zone’s PackedScene and instantiate its child nodes one by one over few frames to avoid frame drop.
It worked pretty well until I added a child scene with editable children to my level map zone tree.
Example
The loaded PackedScene has this tree:
Root
…
(geometry and objects)
…
LoadZone (child scene instance, Editable Children = true)
CollisionShape3D (the editable children with overridden unique Shape)
My use case (or architecture) is to place instances of the LoadZone to different map zones, make its children editable and adjust the collision shape for each instance. Then load it and instantiate from the SceneState with other nodes.
Implementation
Some example minimal code:
func example_instantiate_scene_from_state(packed_scene: PackedScene):
var state = packed_scene.get_state()
var node_count = state.get_node_count()
for idx in range(node_count):
var child_name = state.get_node_name(idx)
var child_instance = state.get_node_instance(idx)
var child_type = state.get_node_type(idx)
var node : Node
if child_instance:
node = child_instance.instantiate()
elif child_type:
node = ClassDB.instantiate(child_type)
if not node:
push_error("Child node %s was not instantiated!" % child_name)
continue
# Continue with instance setting, restore params, parents etc.
What happens when I run this code on the PackedScene from the example above:
LoadZone is instantiated as it has child_instance
Its editable children CollisionShape3D also appears in the nodes list, but is skipped since it has no child_instance nor child_type
The result in the scene tree (as I see in Remote tab) :
LoadZone instantiated
the editable children as well, but has default properties from load_zone.tscn, i.e. missing “the edits” I made
Actual questions
Is there way how to instantiate a child scene from the main SceneState with correct editable children overrides?
Does (or should) SceneState provide info that a child node’s instance has editable children with overrides? If so, I could run another iteration over this inner scene to setup its params, if it makes sense.
I was afraid it wasn’t possible as well as v4.6 SceneState does not provide child scene node groups which have to be fetched reading root node’s groups in the child scene SceneState (already as an open issue, but workaround exists!)
Thinking of a feature request, but I will wait a bit longer.
And thank you for the background loading documentation! In fact the input PackedScene for the code snippet has been already async loaded by ResourceLoader, but add_child() operation in main thread is also quite expansive for a room scene with some geometry and 50 items around, that’s why I decomposed the PackedScene and spawn it node by node.
Thanks! This is actually a workaround I started using for my use case:
@export var load_zone_area: Area3D
Instead of the editable children, LoadZone gets a well positioned and shaped unique level specific Area3D in the level tree, having only a script connecting signals and doing the business logic. My custom PackedScene loader should be aware of any editable scenes anyway.
When loading PackedScene instances in real time i usually do:
var packed: PackedScene = load(path_to_tscn)
var instance = packed.instantiate()
# instance is now a node with children, but is not yet on the tree.
# all of its children have run _ready().
# Poke, probe and gut open to edit its children.
var some_raycaster_node = instance.get_node(^"Obstacle detector")
some_raycaster_node.set_of_nodes_to_scan = get_tree().get_nodes_in_group("players")
var some_enemy = instance.get_node(^"mob")
some_enemy.hp = 300
some_enemy.status = BaseEnemy.status.PISSED
# now slap configured instance into the tree.
# only after you do this will the root of that scene run _ready().
mob_zone_one.add_child(instance)
So what might be happening in your case is that you tried to edit children before instantiating the scene, or you are trying to edit the same resource accross many instances.
Make sure whatever properties you try to edit on children of instanced scenes are raw Variants (no dicts, arrays or custom resources) of if they are resources, they must be unique (one instance of a resource per each instance of the node), otherwise you’ll run into the classic case many new users reported of HP being shared accross two enemies, items being shared between two chests, so on and so forth.
Thanks for the warning, this was not the case I guess. What happened to me was that 1) in the level design stage, I added my LoadZone instance to the level map, made its children editable and scaled the (child) Area3D to match my needs for this part of level (e.g. to cover space in front of a door to trigger seamless loading of another level behind the door). Then 2) in runtime while instantiating the scene from its SceneState node by node over few frames, the editable child Area3D had neither an instance, neither a type in the SceneState, so I couldn’t set its params after loading even if I wanted. Anyway, I found a workaround for my use case, my time-sliced assets streaming works well, even though it requires some rules to be followed in the level design stage.