Hi everyone,

I'm trying to expose some of the parameters of a child node in a scene and I encounter a situation with inherited scenes.

Let's take a very simple example : The scene I want to inherit (SceneParente) has two Node nodes The child node (Dummy) has a _dummy_var parameter that I would like to see exposed when I instantiate the scene



The script attached to the Dummy node defines a Setter and a Getter for the dummy_var variable and prints in the console the passage in the init and _ready function.

tool
extends Node


var _dummy_var : String = "rien" setget set_dummy_var, get_dummy_var


func _init() -> void:
	print("_init Dummy (" + str(get_instance_id()) + ")")


func _ready() -> void:
	print("_ready Dummy (" + str(get_instance_id()) + ")")


func set_dummy_var(value : String) -> void:
	_dummy_var = value
func get_dummy_var() -> String:
	return _dummy_var

The script attached to the SceneParente root node exposes the variable dummy_var of the Dummy node using the get_property_list() method and, as for the script of the Dummy node, prints the passage in different methods in the console.

tool
extends Node

var _dummy_child : Node


func _get_property_list() -> Array:
	
	print("_get_property_list SceneParente")
	var properties := Array()
	
	properties.append({
		name = "dummy_var",
		type = TYPE_STRING,
		usage = PROPERTY_USAGE_DEFAULT
	})
	
	return properties


func _set(property: String, value) -> bool:
	
	match property:
		"dummy_var" :
			print("_set SceneParente dummy_var")
			_dummy_child.set_dummy_var(value)
			return true
		_ :
			return false


func _get(property: String):
	
	match property:
		"dummy_var" :
			print("_get SceneParente dummy_var")
			return _dummy_child.get_dummy_var()
		_ :
			return null


func _init() -> void:
	print("_init SceneParente (" + str(get_instance_id()) + ")")


func _enter_tree() -> void:
	print("_enter_tree SceneParente (" + str(get_instance_id()) + ")")
	_dummy_child = $Dummy


func _ready() -> void:
	print("_ready SceneParente (" + str(get_instance_id()) + ")")

Everything works perfectly, _dummy_var is correctly exposed in the inspector and the order of execution of the methods gives this in the output :

_init SceneParente (31429)
_init Dummy (31430)
_enter_tree SceneParente (31429)
_ready Dummy (31430)
_ready SceneParente (31429)
_get_property_list SceneParente
_get SceneParente dummy_var

get_property_list() is called after enter_tree, dummy_child is correctly defined. But if I create a new scene inherited from SceneParente, let's call it the SceneFille, and I attach a script inherited from SceneParente's script (containing only the output of the passages in the different methods), the order of passage changes. enter_tree is called after get_property_list(), dummy_child is not defined and Godot raises an error :

_init SceneParent (60503)
_init Dummy (60504)
_get_property_list SceneParent
_get SceneParent dummy_var
 res://SceneParente.gd:43 - Invalid call. Nonexistent function 'get_dummy_var' in base 'Nil'.
_init SceneParent (60503)
_init SceneFille (60503)
_enter_tree SceneParent (60503)
_enter_tree SceneFille (60503)
_ready Dummy (60504)
_ready SceneParent (60503)
_ready SceneFille (60503)
_get_property_list SceneParent
_get SceneParent dummy_var

Did I miss something? Thanks for your help!

The question is very interesting, I will try to answer it for you. First I did your similar operation and see the xx.tscn. '''

[gd_scene load_steps=3 format=2]

[ext_resource path="res://SceneParente.tscn" type="PackedScene" id=1]
[ext_resource path="res://SceneFile.gd" type="Script" id=2]

[node name="SceneParente" instance=ExtResource( 1 )]
script = ExtResource( 2 )

'''

i find the tscn is PackedScene and change script

The second step, I went to see the loading code in godot core. 'scene/resources/packed_scene.cpp' the code is in 186-210 '''

		int nprop_count = n.properties.size();
		if (nprop_count) {
			const NodeData::Property *nprops = &n.properties[0];

			for (int j = 0; j < nprop_count; j++) {
				bool valid;
				ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr);
				ERR_FAIL_INDEX_V(nprops[j].value, prop_count, nullptr);

				if (snames[nprops[j].name] == CoreStringNames::get_singleton()->_script) {
					//work around to avoid old script variables from disappearing, should be the proper fix to:
					//https://github.com/godotengine/godot/issues/2958

					//store old state
					List<Pair<StringName, Variant>> old_state;
					if (node->get_script_instance()) {
						node->get_script_instance()->get_property_state(old_state);
					}

					node->set(snames[nprops[j].name], props[nprops[j].value], &valid);

					//restore old state for new script, if exists
					for (List<Pair<StringName, Variant>>::Element *E = old_state.front(); E; E = E->next()) {
						node->set(E->get().first, E->get().second);
					}

'''

The key point is node->get_script_instance()->get_property_state(old_state), store old state and give it to new script if you change script in instance tscn. This operation is in init function. the enter_tree function is not doing. so the dummy_child is None .

I think this is the cause of the problem

And I think it should pop up at this step,

res://SceneParente.gd:43 - Invalid call. Nonexistent function 'get_dummy_var' in base 'Nil'. why will it continue to execute

By the way, the problem of calling twice is because the base class method must be called in gdscript.It has been fixed in 4.0https://github.com/godotengine/godot-proposals/issues/594

'''

	_init SceneParent (60503)							# create SceneParent
	_init Dummy (60504)									# create Dummy
	_get_property_list SceneParent						# change script in SceneParent,need store old_state 
	_get SceneParent dummy_var							# get dummy_var
	 res://SceneParente.gd:43 - Invalid call. Nonexistent function 'get_dummy_var' in base 'Nil'. # the _dummy_child is None,so it will error
	_init SceneParent (60503)							# changed script, the init function is called, first is Base class _init function
	_init SceneFille (60503)							# second is class _init function
	_enter_tree SceneParent (60503)						# first is Base class _enter_tree function
	_enter_tree SceneFille (60503)						# second is class _enter_tree function
	_ready Dummy (60504)								# it is the child node _ready function
	_ready SceneParent (60503)							# first is Base class _ready function
	_ready SceneFille (60503)							# second is class _ready function
	_get_property_list SceneParent
	_get SceneParent dummy_var

'''

Hi @vmjcv , thanks for this rich answer.

By the way, the problem of calling twice is because the base class method must be called in gdscript.It has been fixed in 4.0https://github.com/godotengine/godot-proposals/issues/594

Yes I've ported this code snippet to V4 and I don't have a double pass in the SceneParente constructor anymore. The order in which the methods are executed remains unchanged :

_init SceneParente (892766021081)
_init Dummy (892782798299)
_get_property_list SceneParente
_get SceneParente dummy_var
 res://SceneParente.gd:43 - Invalid call. Nonexistent function 'get_dummy_var' in base 'Nil'.
_init SceneFille (892766021081)
_enter_tree SceneFille (892766021081)
_ready Dummy (892782798299)
_ready SceneFille (892766021081)

Also, at the moment, in Godot V4 the variable dummy_var is not exposed in the editor, even for the SceneParente scene.

So, how can I get out of it? How do I expose a parameter of a child node when I instantiate a scene, and keep the benefits of scene inheritance?

in godot V3, i can see the dummy_var in editor. you can try this attach file.

I'm not sure about the status in godot4, it is really troublesome to merge master to 3.2 Can you try to run it in 4.0 and give me a reply?

I downloaded the 4.0 version, and the result is wrong as you showed. Then I looked at the code of godot master and it seemed to be a problem. I have to say that the current master version is unstable. Part of the code that needs to be merged is merged into 3.2 to continue development

Hello @vmjcv ,

I downloaded the 4.0 version, and the result is wrong as you showed. Then I looked at the code of godot master and it seemed to be a problem. I have to say that the current master version is unstable. Part of the code that needs to be merged is merged into 3.2 to continue development.

Yes, that's what I thought.

I've tested the code you provided, of course it works but it's not what I expected :p . Rather how to avoid these double calls to methods, or how to get out of them before doing any treatment. Exposing a parameter is not the only difficulty due to this behavior: for example, if I have to instantiate an object when building another object, as soon as I use the scene inheritance I end up with duplicate objects ?

Hi, @The_Moye

I think we have two problems.

  1. avoid these double calls to methods. This problem should be solved by merging the code of https://github.com/godotengine/godot-proposals/issues/594

  2. scene inheritance is achieved through change_script Maybe you need to modify the godot core to achieve the effect you want. Another alternative is to use composition to achieve the purpose of inheritance, which should avoid some of the problems.

Maybe you can try to ask other people @Megalomaniak @TwistedTwigleg

Out of curiosity, is there any reason to use get_property_list over using the export keyword? You can use setter and getter functions with exported variables, and using export should handle displaying the variable in the Godot editor for you automatically. I'm not sure it would solve the issue, but it may be something to look into, as it may help narrow down whether get_property_list is causing the issue or not.

hi, @TwistedTwigleg I test what you said, but the final phenomenon of using export and get_property_list is the same

_init SceneParente (1182)
set_dummy_var
_init Dummy (1183)
get_dummy_var
_init SceneParente (1182)
set_dummy_var
_enter_tree SceneParente (1182)
_ready Dummy (1183)
_ready SceneParente (1182)

In godot core, export value and get_property_list both store in PlaceHolderScriptInstance.properties . when node->get_script_instance()->get_property_state(old_state); we will visit properties and store old properties.

Hi @vmjcv and @TwistedTwigleg ,

Out of curiosity, is there any reason to use get_property_list over using the export keyword?

Yes, get_property_list allows to group some parameters. I think it will be much more readable. And as vmjcv points out, the issue remains with export.

Maybe you need to modify the godot core to achieve the effect you want. Another alternative is to use composition to achieve the purpose of inheritance, which should avoid some of the problems.

Um, I think it's starting to get a little too complicated for me

I have a bad way that don’t give SceneParente.tscn script, just give SceneFile.tscn script. in other words, don’t give the node script which is in parent scene and need change script in inherit scene. just give the node script in inherit scene.

But in this case, the order of init function will be a bit confusing. First, it will call the init method from parent node to child node, and then call the node with modified script from parent node to child node. You may need to care about the calling order to adjust

The code is ugly, but I still put it here. After all, it is a feasible solution

Another way is to use composition to achieve inheritance

Hmm, if the issue also occurs when using export, it may be a Godot engine bug... I am not sure though.

2 years later