Sprite orientation on random rotation in instanced scene

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

Hi everyone,

I have a Scene RotateThing with a Sprite rotating for 2 seconds to a random degree :

extends Sprite

func _ready():

	var tween = Tween.new()

	tween.interpolate_property(self,"rotation_degrees",
	rotation_degrees, rand_range(-180, 180), 2,
	Tween.TRANS_QUINT,Tween.EASE_IN)
	tween.start()

	yield(get_tree().create_timer(2),"timeout")
	queue_free()

The scene is being instanced every now and then from within another Scene:

extends Timer
…
func xyz():
	var that_sprite = preload("res://general-assets/animations-sprites/RotateThing.tscn").instance()
	add_child(that_sprite)

So the moment that_sprite is being instanced it runs the animation, and all this works fine: the sprite plops in orientated straightly as it is drawn and then starts rotating to some random degree.

However:
If instancing a scene with only the sprite but no inherent script at all, and then do the tweening within the instancing scene-script, the sprite’s orientation on animation-start is not straight but randomly rotated, finishing the animation in the straight position! So basically the sequence is being flipped…

extends Timer
…
func xyz():

	var that_sprite = preload("res://general-assets/animations-sprites/RotateThing.tscn").instance()

	var tween = Tween.new()

	add_child(that_sprite)
	tween.interpolate_property(that_sprite,"rotation_degrees",
	rand_range(-180, 180), 2,
	Tween.TRANS_QUINT,Tween.EASE_IN)
	tween.start()

	yield(get_tree().create_timer(2),"timeout")
	that_sprite.queue_free()

(Please note that here putting in another rotation_degrees throws the error „The identifier „rotation_degrees“ isn't declared in the current scope.“.)

So I wonder, how can I have my sprite start rotating from 0 degrees? Any ideas what I’m missing here?

:bust_in_silhouette: Reply From: timothybrentwood

In the second example the sprite is being added as a child to the node that the script is attached to. A child node inherits its parent’s position/rotation by default (as well as other properties). This is probably the cause of the behavior you’re experiencing.

Two ways to deal with this: attach the node to a node that doesn’t rotate, likely by changing add_child(that_sprite) to:

get_parent().add_child(that_sprite)

Or by accounting for the parent’s rotation whenever changing the child’s rotation (using the formula: desired_starting_rotation - parent_rotation):

tween.interpolate_property(that_sprite,"rotation_degrees",
    (0 - rotation_degrees), (0 - rotation_degrees) + rand_range(-180, 180), 2,
    Tween.TRANS_QUINT,Tween.EASE_IN)

The node the script is attached to is a Timer node in this case, which is a child of a Control node. A Timer node can’t be rotated, right? The Control node’s rect_rotation is 0.
But the behaviour won’t change using get_parent(). It starts on a random rotation and ends at 0 degrees, so exactly the opposite of what it should do.

Your second suggestion throws that error again (The identifier „rotation_degrees“ isn't declared in the current scope.)

I’m still at a loss here, sorry. Could this be a bug this time maybe…?

pferft | 2021-10-31 20:22

which is a child of a Control node

Ah okay that’s why. To my knowledge you’re not supposed to add Node2D/Spatials (or anything inheriting from them) that are going to display on screen as a child of a control node. That would explain the undefined behavior you’re experiencing. (It’s probably not good practice to add them as children to a Timer node either.) Try using:

get_tree().get_root().add_child(that_sprite)
tween.interpolate_property(that_sprite,"rotation_degrees",
0, rand_range(-180, 180), 2,
Tween.TRANS_QUINT,Tween.EASE_IN)

timothybrentwood | 2021-11-01 00:33

Well, another valuable hint, thank you. (Hitting me hard, because I guess I have quite some Control nodes set up like this…)

Unfortunately this doesn’t do the trick as well - interestingly enough there is no rotation going on at all now. But the debugger says _build_interpolation: Tween target object has no property named: rotation_degrees.

I believe the issue to be rootet in that “rotation” somehow, because interpolate_property(that_sprite,"position" works fine. Could that be a hint?

pferft | 2021-11-01 12:15

My apologies, the Sprite object inherits from CanvasItem (generic 2D things that can be drawn to screen) not Node2D so it doesn’t have the same properties as Node2D. The solution here is change your Sprite’s scene to mimic Node hierarchy:

-Node2D (or something that inherits from Node2D)
--Sprite

This will allow you to tween the rotation_degrees property of the parent Node2D and the child Sprite object will inherit this rotation as a result of being a child.

timothybrentwood | 2021-11-01 13:48

No no no, my mistake! A typo in “tween”. In the end that “0” indeed makes the rotation start at 0 degrees:
tween.interpolate_property(that_sprite,“rotation_degrees”, 0, rand_range(-180, 180), …

Just as you had put into your example in the first place. You’ve been right all along, sorry for the unnecessary fuss.

I still don’t grasp why the sprite is being randomly rotated at start if that 0 is missing, but for now I’m very happy it works. Thank you very much for your help!

pferft | 2021-11-01 14:29

Glad you got it figured out.

I still don’t grasp why the sprite is being randomly rotated at start if that 0 is missing

If you look at the arguments for the interpolate_propert() function it should become clear:

interpolate_property(Object object, NodePath property, Variant initial_val, Variant final_val, float duration, TransitionType trans_type=0, EaseType ease_type=2, float delay=0)

If you have the first two arguments correct, the remaining arguments are just numbers in the case of tweening rotation_degrees. As long as you’re giving it at least 5 arguments, the last three arguments will default in.

In your case, by leaving out the 0 argument you shifted the rand_range(-180, 180) argument one to the left to make IT the initial_val, the final_value was 2 - what you intended for the duration. Since Tween.TRANS_QUINT is an enumeration, it’s really just an integer under the hood so you didn’t get a debugger message for putting it as the duration. Then you used Tween.EASE_IN - another enumeration so no debugger issues when setting it as the trans_type. Then Godot just defaulted in 2 for the ease_type because you didn’t give it enough arguments to specify an ease_type in your function call.

When you follow the above logic you can see that it did exactly what you told it to. The reason it started at a random rotation was because you gave it an initial_value = rand_range(-180, 180). :slight_smile: To be clear, I believe what I said about adding a Node2D as a child of a Control node should produce undefined behavior - it just so happens that in this case, doing so wasn’t the cause of the unexpected behavior.

timothybrentwood | 2021-11-01 15:10

Thanks for that detailed explanation, very much appreciated (and copy-pasted into my archives ; )

And I’ll certainly keep in mind to be more “careful” with Control nodes in the future.
Thanks again for taking your time and helping me out!

pferft | 2021-11-01 16:38