Is it possible to play Spine animations in reverse?

Hello!

I am using Cocos Creator version 3.8.3.

I am interested in two use cases for animation playback:

  1. Play animation in reverse.
  2. Play animation first forward and then backward (ping-pong).

I didn’t find anything similar in sp.Skeleton and sp.spine.TrackEntry.

So far I have implemented it like this, but I don’t know how correct it is:

protected PlayReverseAnimation(skeleton: sp.Skeleton, animName: string): void {
	skeleton.timeScale=0;
	let track = skeleton.setAnimation(0, animName);

	tween(track)
		.set({ trackTime: track.animation.duration })
		.to(2, { trackTime: 0 })
		.start();
}

And one more thing. I implemented the animation to play back and forth without delay like this:

protected PlayPingPongAnimation(skeleton: sp.Skeleton, name: string, callback?: () => void): Tween<sp.spine.TrackEntry> {
	skeleton.timeScale = 0;

	let trackEntry = skeleton.setAnimation(0, name);
	let animation = trackEntry.animation;

	return tween(trackEntry)
		.to(animation.duration, { trackTime: animation.duration })
		.to(animation.duration, { trackTime: 0 })
		.call(() => {
			if (callback) {
				callback();
			}
		})
		.start();
}

Again, I don’t know how correct this is, but it works. But I needed to squeeze in between these animations: first we play forward, wait for something, and then we play backwards. I wrote two functions for this:

protected PlayAnimation(skeleton: sp.Skeleton, name: string, callback?: () => void): Tween<sp.spine.TrackEntry> {
	skeleton.timeScale = 0;

	let trackEntry = skeleton.setAnimation(0, name);
	let animation = trackEntry.animation;

	return tween(trackEntry)
		.to(animation.duration, { trackTime: animation.duration })
		.set({ trackTime: animation.duration })
		.call(() => {
			if (callback) {
				callback();
			}
		})
		.start();
}

protected PlayReverseAnimation(skeleton: sp.Skeleton, name: string, callback?: () => void): Tween<sp.spine.TrackEntry> {
	skeleton.timeScale = 0;
	
	let trackEntry = skeleton.setAnimation(0, name);
	let animation = trackEntry.animation;

	return tween(trackEntry)
		.set({ trackTime: animation.duration })
		.to(animation.duration, { trackTime: 0 })
		.call(() => {
			if (callback) {
				callback();
			}
		})
		.start();
}

It turned out that this did not work properly. When the PlayAnimation() function is executed, the animation either returns to the zero time state, or the object returns to the non-animated state. My question then is, how can I make the animation freeze at the end of the animation?

For now I’ve entered a time correction variable:

private static readonly timeCorrection: number = 0.01;

And I subtract this time in the code of these functions:

protected PlayAnimation(skeleton: sp.Skeleton, name: string, callback?: () => void): Tween<sp.spine.TrackEntry> {
	skeleton.timeScale = 0;

	let trackEntry = skeleton.setAnimation(0, name);
	let animation = trackEntry.animation;

	return tween(trackEntry)
		.to(animation.duration, { trackTime: animation.duration - BuildingView.timeCorrection })
		.set({ trackTime: animation.duration - BuildingView.timeCorrection })
		.call(() => {
			if (callback) {
				callback();
			}
		})
		.start();
}

protected PlayReverseAnimation(skeleton: sp.Skeleton, name: string, callback?: () => void): Tween<sp.spine.TrackEntry> {
	skeleton.timeScale = 0;
	
	let trackEntry = skeleton.setAnimation(0, name);
	let animation = trackEntry.animation;

	return tween(trackEntry)
		.set({ trackTime: animation.duration - BuildingView.timeCorrection })
		.to(animation.duration - BuildingView.timeCorrection, { trackTime: 0 })
		.call(() => {
			if (callback) {
				callback();
			}
		})
		.start();
}

But this is some kind of wrong decision. It is interesting that when calling the PlayPingPongAnimation() function, the animation works normally.