@exported references to child nodes are shared between instances
Asked Answered
I

17

0

Hi! I've read the @export related section in docs, but it says nothing about this question, and it could be a bit misleading IMO.
The fact is, if you have i.e. an "item" scene that represents an inventory item with a BG color and an icon, and it has some @export var icon : TextureRect inside, when you instantiate that scene inside a parent "inventory", every time you want to change the icon for one random item, it has somehow the reference to the icon (TextureRect) of the first instantiated item, not its actual icon.

I've tested that this can be avoided using a reference like $Icon instead, but docs don't explain why is this happening, or if it's a by-design decission.

The advantage of using @export is that you can rename childs of your scenes without refactoring all occurrences inside your scripts, so it is not a huge problem, but a little annoying.

Can anyone clarify this? Am I doing something wrong?

EDIT: See other answers below to clarify the problem (only happens on duplicated nodes)

Intercalary answered 6/10, 2023 at 9:21 Comment(0)
K
0

Intercalary Not exactly sure what the question is. Can you clarify a bit or give some code example?

Kianakiang answered 6/10, 2023 at 9:38 Comment(0)
C
0

I think what they're describing is by-design. That is, if I have a collider saved as a resource and then assign to two enemies, it's the same collider and shared. It's the same for sprites, etc. And by-design because it's performant; for example, if a million meshes are have their own instance of a mesh data then, well, that's a waste. Better they have one instances of mesh data shared between those meshes and only position, scale (etc) changes.

What I think they expect is that two enemies instanced from the same scene that references the collider (or whatever @export), and then changing the collider is somehow unique to each enemy.

And for that, godot provides a couple of things to help. If you expand the resource, you can make it 'Local To Scene' or, right-click the resource and select 'Make Unique' depending on your goals. You can also duplicate a resource in code and use that instead.

Compander answered 6/10, 2023 at 11:20 Comment(0)
K
1

Compander Yes, that would have been my guess as well in regards of what the question was about. Shared resources, so if you change one you change them all.

Kianakiang answered 6/10, 2023 at 11:24 Comment(0)
U
0

I am also not sure what the doubt is, but I have the felling OP thinks the shared resource behavior have something to do with the @export tag, it should not have any relation, @export should be just a tag for deal in the editor. But I have a case where I had to export a variable to use in a complex scene once, I dont remember why. Can you clarify why do you think it has a relation with the @export clause ?

Undaunted answered 6/10, 2023 at 12:6 Comment(0)
I
0

I understand, it is a lightweight pattern. But in both cases, you are talking about resources, and I'm not. I'm talking about references to child nodes. I've made it more visual so you can understand what's happening (I hope):

This means you construct your scene thinking you have encapsulated references, but in reality all instances of "item" are pointing to the first instanced "icon", so they lose track of their own icons.

Intercalary answered 6/10, 2023 at 12:25 Comment(0)
I
0

Undaunted May be it's my fault. I've been working with Godot since 3.4, and the inclusion of @export made me think of it like it was something like Unity's reference drag&drop functionality, so I can avoid using "path to child nodes", specially when the name/path of your child nodes may change over time (so forcing you to refactor everything).

Intercalary answered 6/10, 2023 at 12:41 Comment(0)
I
0

Intercalary Post the exact code and node structure. Or better yet post a minimal reproduction project.

Infrequency answered 6/10, 2023 at 14:12 Comment(0)
C
0

Intercalary doesn't this still describe the resources, i.e. the icon (TextureRect) is the same resource in Instance 1, 2 and 3.

Compander answered 6/10, 2023 at 15:15 Comment(0)
I
0

Oh, I found the cause of the error. I attached a test project so you can see the results, and hopefully you understand me now.
The problem only appears when you clone (control+D) any node/scene that contains a @export referencing a child node.

When you drag a item.tscn inside your inventory.tscn, it works, but not when cloned, the @export reference points to the original's child.

varexportchildtest.zip
2MB
Intercalary answered 6/10, 2023 at 18:40 Comment(0)
I
1

Intercalary Since @export is in the top node of the instanced scene, when the instance is duplicated the engine will interpret the assigned path as if it's relative to the main scene. Hm... bug or feature? πŸ™‚

This is probably worthy of issue report as it behaves counter-intuitively. Maybe devs have some valid explanation. If not, they should fix it.

A resonable workaround is to use a node path instead of direct node reference:

@export var icon : NodePath
func _ready():
	get_node(icon).modulate = Color(randf(), randf(), randf())

Or assign the reference via script at startup (this then defeats the purpose of using @export though)

@export var icon : TextureRect
func _ready():
	icon = $Icon
	icon.modulate = Color(randf(), randf(), randf())
Infrequency answered 6/10, 2023 at 19:31 Comment(0)
I
0

Infrequency Exactly, it sounds more like a potential bug. Glad to know I'm not crazy (...yet) xD

So, if it is really a design choice from devs, it would be nice to know πŸ˜›

Thank you guys for your time!

Intercalary answered 6/10, 2023 at 19:43 Comment(0)
I
0

Intercalary It may be a design choice since you'd typically want to assign something outside of the instantiated scene to an export in the top node.

Infrequency answered 6/10, 2023 at 19:45 Comment(0)
I
0

Infrequency yes, but that does not relate to the problem I mentioned. You can't expect that duplicating a scene node breaks the inner references of that cloned scene, but drag&droped scenes doesn't. What's the point of it? When I clone something, I want it to behave like the original, parent referencing child, not to reference other's child. That's too weird.

Intercalary answered 6/10, 2023 at 20:49 Comment(0)
I
0

Intercalary It's not that weird if you know how Godot's scene tree operates. It doesn't have a concept of instances as entities. Instantiation is just an operation of placing some predefined nodes as a branch into the main scene. Once they are placed, the engine doesn't keep any back reference to the "original". At runtime, Godot doesn't know or care which collections of nodes came into existence as an instance.

The editor tries its best to give you an illusion that instances are entities by maintaining some of that back reference in the gui. Apparently it doesn't do the best job with it in this particular case πŸ™‚

Infrequency answered 6/10, 2023 at 21:12 Comment(0)
U
0

Intercalary When you add something new to the scene ( drag in drop ), for resources that can be shared, it will create an new internal copy of the resource, this is why you see its getting the original instance. When you clone it, for resources that can be shared, it will share the resource from the one you cloned, because its already in the scene so godot tries to use the same pointer in memory for it. You can allways click in the resource and set is as unique for example.

Undaunted answered 12/10, 2023 at 22:34 Comment(0)
I
0

Undaunted The problem is not referencing a resource, but referencing a child node in the original scene. The clone seems to reference the original's child, not the copy one.

Intercalary answered 16/10, 2023 at 20:14 Comment(0)
I
0

Ok, so it seems to be a bug that's been there for a while, and it continues today even in Godot 4.2.1:

https://github.com/godotengine/godot/pull/83343

Let's hope it gets fixed for 4.3 πŸ˜‰

Intercalary answered 21/2, 2024 at 10:34 Comment(0)

© 2022 - 2025 β€” McMap. All rights reserved.