How can I avoid a slight stutter when using a tween to animate a property?

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

Hello!

I’m trying to move a player around a grid, using a tween to animate the translation property. It’s working well, except for the case when the player still has the input key held down, and here I’d like the movement to smoothly continue - instead, there’s a slight stutter when restarting the tween, and I was wondering if there was anything I could do to avoid it?

I think the underlying issue is that e.g. asking a tween to animate a value between 1 and 2 will produce frames where the value is 1 at the start, and 2 at the end - when I chain that with a tween between 2 and 3, I get a ‘2’ value used for two subsequent frames, where the tweens join.

For example:

extends Spatial

var tween = Tween.new() 
var val = 0.0

func _on_Tween_all_complete():
	tween.interpolate_property(self, "val", null, val+1, 0.1)
	tween.start()

func _ready():
	add_child(tween)
	tween.connect("tween_all_completed", self, "_on_Tween_all_complete")
	tween.interpolate_property(self, "val", null, val+1, 0.1)
	tween.start()

func _process(delta):
	print(val)

The list of values printed show duplicate values on the integer boundaries - I’d like to set it up somehow to avoid that, so there’s only one integer value there instead. What’s the best way to do that?

I guess I could set the initial value to be val + (a bit)… but how would I determine how much to add? Would I need to keep track of the _process delta values?

This may be a long shot, but have you considered using an AnimationTree node with a state machine on the AnimationTree? When moving the character, have it transition (i.e. move) to the desired animation.

Ertain | 2022-07-31 04:50

:bust_in_silhouette: Reply From: Inces

So You are interpolating animation frames only ?
Well, You can just store your hypothetic floating frame in another high scope variable, and use it as a base for tween calculations and translate it into real frame in process or in setget.

func_process():
       $AnimatedSprite.frame = int(floatingframe)

To make it simpler, You could also design your animation to always make finited loop inbetween two tiles. This way You would always know exact frame, it would always be 0 in the middle of a new tile.

If I misunderstood, please show your actual code :slight_smile:

Thank you, but at the moment I’m just moving the player across a grid, I’m not yet animating the player (sorry if my original question was confusing, I’m new to Godot and likely using incorrect/confusing terms!)

So in my actual code, I start the player moving in an _input call like this:

tween.interpolate_property($Player, "translation", null, $Player.translation + Vector3(1,0,0), 1)

and when that tween finishes, I check as soon as I can whether the player wants to continue moving, like this:

func _on_Tween_all_complete():
	if Input.is_key_pressed(KEY_D):
		tween.interpolate_property($Player, "translation", null, $Player.translation + Vector3(1.0,0,0), 1)
		tween.start()

But there’s a slight stutter visible in the movement, because doing it this way the player will have the same position in two frames - the frame when one tween ends, and the the next frame when the next tween begins.

waveydave | 2022-07-31 09:20

I don’t think this is a source of problem.
One thing is :
Interpolation means a value is being changed within a unit of time according to specific mathematic function. Tween uses non-linear math by default, so it slows down value change near the final step of interpolation. So when your animation consists of multiple smaller interpolations, the total movement increment will not look linear, it will look a little jumpy.

Another thing is :
Choosing new value at the moment tween finished interpolating former value is simply too late. Your player just has to stop completely for a split of second.

You need to adress both of these issues. Change all of the tweens hidden arguments into linears. Check for input before tween finishes interpolation, and add another to queue. You can use dynamic time yield for this ( for example wait 80% of tweens interpolation speed and check if button is still pressed ). It is important You add tracks to tween, so it will do them in a queue, and You will never have to start tween again, until input is no longer pressed.

Inces | 2022-07-31 12:08

Thank you for this - by queueing up the next tween early, it does eliminate the stutter I was seeing, I’ll just need to refactor the game logic a bit to take account of this. Thank you!

waveydave | 2022-08-01 21:49