using duplicate in a scripted node messing with time sensitive code

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By neutralend

I’m learning how to use godot, i have a character in my game that have a sprite in it without texture, i’m randomly setting the texture in code, duplicating the sprite and adding it as a node in the a subnode of the character parent node. But the character is initiate by his parent at the start of the game so i’m assuming his sprite is initiate as well. but i have a script attached to the sprite with time sensitive code, so because it is initiate with the character all the duplicates share the same state.
I done something similar before, using packedscenes they returned his node after setting his sprites and properties. in this case since the packedscene isn’t initiate by the main tree, i guess it is why i don’t have any problems with my code in this nodes. The case is it is a simple sprite i don’t want to make a hole scene for it, there is a way to duplicate his node without they sharing the same time states? i tried using different duplicateflags, like, duplicate use instancing, but it didn’t work. Every time I duplicate a new sprite it starts in the same state the last one I duplicate is.

:bust_in_silhouette: Reply From: Gabrii

One solution might be to put whatever you do in _ready into a new function init, which sets the initial state. _ready could call init, but you could also call init after duplicating.

Note that you’ll need to set the initial value of all the class attributes in the init function, not on their declaration.

There’s almost nothing on the _ready() just setting a random rotation_speed i use to rotate the sprite after. Almost all the code is in the _process function. Basically it spins the sprite and scale it while turning it visibility down:

func _process(delta):
	rotation += rotarion_speed * delta
	scale += scale * delta
	var color = get_modulate()
	color.a -= delta
	set_modulate(color)

and it has a timer, called LifeTimer that kills it after 3s:

func _on_LifeTime_timeout():
	queue_free()

As I set the sprite every time in my character _process function after duplicate the empty sprite, every sprite has different texture but all share the same state. it is something like this:

var sprite= get_node('Sprite').duplicate()
	sprite.set_texture(sprite_texture[randi() % sprite_texture.size()])
	sprite.set_position(get_node('SpriteSpawn').get_global_position())
	sprite.get_node('LifeTime').start()
	get_parent().add_child(sprite)

Even when I do set_process(false) in the _ready of the sprite script and set_process(true) after the duplicate() they all start with the same alpha the previous one has at the moment, the same rotation and scale, and when the duplicate is queued free. My code broke because the original node sprite is queued free too. And godot debug says i’m trying to duplicate a null reference, or something like that.
I’m asking if there is a way to create a fresh instance of the node, because how similar the nodes are to objects. But I have a feeling an initiated node is pretty much like an instantiated object in python and all I can do is referencing it, and non initiated referenced packedscenes are the way to have a class like method to initiate a new node ‘object’. It’s that a right assumption?

neutralend | 2018-04-17 17:02

Let me clarify this part:

Note that you’ll need to set the initial value of all the class attributes in the init function, not on their declaration.

Even if you didn’t have the null reference problem, the other behavior would still be the same. When you instantiate the first object, it’s scale is (1,1), but then it gets modified and copied into the duplicated node. What I ment was to put scale = (1,1) in the init function. The same would apply to the color, and rotation (not that rotation matters probably). You should intend the init function to set the initial state, not hope on value declarations or defaults.

You can expect even more “undefined” behavior with signals, maybe the connection to signal from the first timer and first node, gets duplicated into the duplicated node. Who knows!? I don’t.

In the end, you shouldn’t be using duplicate, as reduz says in #3393 as of 2017:

duplicate() for nodes is hackish, and not generally recommended. It’s there because some people insists it can be of use, because otherwise i would have removed it long ago. Using preload().instance() is always the better choice.
The reason is that some nodes create subnodes (ie, a list creates it’s scrollbars), so a simple duplicate() function has to be able to tell to a certain degree what to duplicate and what not to duplicate. This is not always obvious.
Maybe the best it would be to document what the function can actually does,
or just add more flexibility to it, not really sure what you guys prefer.

Go the easy way and preload it as a scene and use .instance(), or venture into the undefined.

Gabrii | 2018-04-17 18:54

I’m really new to Godot, so I dind’t know duplicate() was not recommended. I will continue to making new scenes and instantiate them with instance() . One last question, I sometimes used to return a node trough a function and to do that I needed to use duplicate() too, there is another way to do it or it isn’t recommended too? I do it only to avoid having millions of scenes and keeping similar things together in only one scene. But if it isn’t a recommended way to do things in the engine I will off course stop to do things this way and simple using more folders to organize the scenes.
Sorry for the noobish question, it is really my first time using the engine, or any engine at all, I used to program some little games in C with SDL and in python with pymunk, pygame or pyglet, But it is really my first attempting with Godot, and for somethings the documentation is kinda lacking. The role node/scene thing is a little hard to understand how to use properly in code.

Thanks.

neutralend | 2018-04-17 19:53

You don’t need to use duplicate() to return a node in a function, there is nothing preventing you to use instance(), which is the correct way. An object reference can be returned by a function, it doesn’t matter where that reference comes from.

If you feel that you need too many scenes, maybe it comes from a design problem. For example, if you need 100 similar nodes where only a few parameters or behaviors change, you might consider creating a node which holds the parameters for those 100 nodes, and additional parameters to toggle or tweak given behaviors.

That “factory” node should have a function to instantiate and return a generalized node with the desired parameters applied to it.

It’s also much easier to maintain and develop further. I fear you might have a million scenes and a million scripts.

Gabrii | 2018-04-18 19:25

Thank you very much, I was doing something like that, my main node was holding the parameters to initiate other nodes. I also learned that we can inherit a scene from another scene this can make things easier to deal with too, when making different versions of the same game object.

Thank you.

neutralend | 2018-04-19 00:57