How to get exported properties from SceneState

Godot Version

v4.5.1.stable.official [f62fdbde1]

Question

How do I get exported properties from a PackedScene? The docs say it should be possible using SceneState as written in get-node-property-count. Unfortunately, neither does the method count my exported property nor does it show with get_node_property_name.

Example

I want to spawn an Item dynamically via code. The Item is a scene containing a Node with the following script attached and a Sprite2D that changes based on the stat (omitted for simplicity):

@tool
extends Area2D

enum StatTypes {HEALTH, DAMAGE, DEFENSE}
signal onPickup

@export var stat: StatTypes = StatTypes.HEALTH
@onready var _sprite: Sprite2D = $Sprite2D

This item should be spawned by ItemSpawner, another scene that contains one node with the following script:

@tool

## Which item to spawn
@export var spawnItem: PackedScene:
    set(value):
        spawnItem = value
        getItemProperties()


var oldSpawnItem: PackedScene

func play() -> void:
    var spawnedItem = spawnItem.instantiate()
    (spawnedItem as Node2D).position = targetPosition
    (spawnedItem as Node2D).rotation = targetRotation
    if (targetNode):
        targetNode.add_child(spawnedItem)
    else:
        get_tree().current_scene.add_child(spawnedItem)
    onFinished.emit()


func getItemProperties() -> void:
    if (spawnItem != oldSpawnItem && spawnItem != null):
        var sceneState = spawnItem.get_state()
        for idx in range(sceneState.get_node_property_count(0)):
            print_rich("[b]Node Property ", idx , " Name[/b] ", sceneState.get_node_property_name(0, idx))
            print_rich("[b]Node Property ", idx , " Count[/b] ", sceneState.get_node_property_value(0, idx))
            if (sceneState.get_node_property_value(0, idx) is GDScript):
                var script = sceneState.get_node_property_value(0, idx) as GDScript
                print_rich("[b]Script Property List[/b]", script.get_script_property_list())
    oldSpawnItem = spawnItem

Finally, the ItemSpawner is added in another Scene and I set its property spawnItem to the Item mentioned above. Since PackedScenes don’t allow to adjust their exported properties directly, the goal was to read it myself via SceneStates property methods, i.e. get_node_property_name. Unfortunately, it was impossible to get the exported properties. The only way I could actually get exported properties is by reading the script directly and getting the script property list which returns all properties and not just exported ones. The only meaningful way to then get the exported ones is by enforcing a naming convention for exported properties and then filtering properties by those conventions (i.e. use _ as prefix for all non-exported members, etc.).

I feel like this is opaque when the docs say exported properties should be part of the SceneStates property methods. Could someone clarify this or show a simpler way of getting exported properties of a PackedScene?

As explained in the SceneState documentation, only exported and overridden properties will be part of the get_node_property_*() methods.

Maintains a list of resources, nodes, exported and overridden properties, and built-in scripts associated with a scene.

Emphasis mine.

So it will only show if you change the value in that exported variable.

You can distinguish between built-in properties and exported properties by checking if the @GlobalScope.PROPERTY_USAGE_STORAGE flag is set in the usage field of the property like:

for prop in script.get_script_property_list():
	if prop.usage & PROPERTY_USAGE_STORAGE > 0:
		print(prop)
1 Like

That’s the quote I am confused about. Going by your answer, it sounds like a property needs to be both, exported and overridden, to be shown but an Area2D also shows other overwritten properties that are not exported by the user like changes to the collision layer. So shouldn’t it just say “overridden properties” instead of “exported and overwritten”? Or I’m still not getting what you mean in which case I’m sorry.

Regardless, your code does work like a charm. Thank you very much!

Any property that appears in the Inspector dock is an exported property (aka any property with the flag @GlobalScope.PROPERTY_USAGE_EDITOR set) It does not matter if it was exported by the user or not.

Any property that can be serialized into a file (a scene or resource) can be overridden (aka any property with the flag @GlobalScope.PROPERTY_USAGE_STORAGE set)

Technically, yes, it should be “overridden properties” because you can have editor only properties that won’t be serialized into the file and you can have storage only properties that will be serialized if their default value changes like:

@tool
extends Node


@export_storage var saved_to_file: String
@export_custom(PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR) var exported: String:
	get: return saved_to_file
	set(value): saved_to_file = value

where only the property saved_to_file will appear when using the SceneState.get_node_property_*() methods (if its value changed)