Hydroper
Hydroper

Reputation: 433

Constant rotation tween in Godot 4

Godot's built-in Tween doesn't meet my requirements:

So I implemented a simple tweener for spinning a Node2D with a constant increment speed:

namespace Recoyx.CarKnockOnline.Util;

using Godot;

public class ConstantRotationTween
{
    public float IncrementDegrees;

    private Node2D _tweenNode = null;
    private bool _running = false;
    private float _incrementScale = 1;
    private float _targetDegrees = 0;

    public ConstantRotationTween(float incrementDegrees)
    {
        this.IncrementDegrees = incrementDegrees;
    }

    public bool IsRunning() => _running;

    public void Tween(Node2D tweenNode, float targetDegrees)
    {
        this._tweenNode = tweenNode;

        float a = 0;

        // keep target rotation between 0-360
        a = targetDegrees;
        a = a < 0 ? 360 - (-a % 360) : a;
        this._targetDegrees = Mathf.Round(a) % 360;

        // keep node rotation between 0-360
        this._lockTweenNodeRotation();

        a = this._targetDegrees;
        float b = this._tweenNode.RotationDegrees;
        float ab = a - b, ba = b - a;
        ab = ab < 0 ? ab + 360 : ab;
        ba = ba < 0 ? ba + 360 : ba;
        bool goClockwise = ab < ba;
        this._incrementScale = goClockwise ? 1 : -1;

        this._running = true;
    }

    public void Stop()
    {
        this._running = false;
        this._tweenNode = null;
    }

    public void Process(double delta)
    {
        if (!this._running)
        {
            return;
        }
        this._lockTweenNodeRotation();
        if (this._tweenNode.RotationDegrees == this._targetDegrees)
        {
            this._running = false;
            return;
        }
        this._tweenNode.RotationDegrees += this.IncrementDegrees * this._incrementScale * ((float) delta);
    }

    /// <summary>Keep node rotation between 0-360.</summary>
    private void _lockTweenNodeRotation()
    {
        float a = this._tweenNode.RotationDegrees;
        a = a < 0 ? 360 - (-a % 360) : a;
        this._tweenNode.RotationDegrees = Mathf.Round(a) % 360;
    }
}

That's how you use it:

var tween = new ConstantRotationTween(150); // rotation speed = 150
tween.Tween(someNode, finalRotationDegrees); // starts a tween
tween.Stop(); // cancels the tween
tween.IsRunning();

// must be called in _Process
tween.Process(delta);

It almost works, just 2 issues:

It's almost two questions, but they're really simple and interconnected. So what can I do to solve this frame rate issue and the unnecessary spins?

Or better, is there something in Godot for achieving the same with fewer lines of code?


I improved the above tweener as you can see in my answer, but the frame rate issue remains. Very little slight abrupt jumps during the object spin.

Upvotes: 2

Views: 1059

Answers (1)

Hydroper
Hydroper

Reputation: 433

A solution I found was to move the clockwise or counterclockwise route detection to the tweener's Process(). And if the route is too short (e.g. delta < 5), I assign the object's rotation to the final rotation and stop the tweener.

Unfortunately there are some very little slight jumps during the object spin. I'd appreciate help still.

Here's the working tweener:

namespace Recoyx.CarKnockOnline.Util;

using Godot;

public class ConstantRotationTween
{
    public float IncrementDegrees;

    private Node2D _tweenNode = null;
    private bool _running = false;
    private float _incrementScale = 1;
    private float _targetDegrees = 0;

    public ConstantRotationTween(float incrementDegrees)
    {
        this.IncrementDegrees = incrementDegrees;
    }

    public bool IsRunning() => _running;

    public void Tween(Node2D tweenNode, float targetDegrees)
    {
        this._tweenNode = tweenNode;

        // keep target rotation between 0-360
        float a = targetDegrees;
        a = a < 0 ? 360 - (-a % 360) : a;
        this._targetDegrees = Mathf.Round(a) % 360;

        this._running = true;
    }

    public void Stop()
    {
        this._running = false;
        this._tweenNode = null;
    }

    public void Process(double delta)
    {
        if (!this._running)
        {
            return;
        }
        this._lockTweenNodeRotation();
        if (this._tweenNode.RotationDegrees == this._targetDegrees)
        {
            this._running = false;
            return;
        }
        (bool goClockwise, float routeDelta) = this._updateRoute();
        this._tweenNode.RotationDegrees += this.IncrementDegrees * this._incrementScale * ((float) delta);
        if (routeDelta <= 1.8)
        {
            this._tweenNode.RotationDegrees = this._targetDegrees;
            this._running = false;
        }
    }

    private (bool goClockwise, float routeDelta) _updateRoute()
    {
        float a = this._targetDegrees;
        float b = this._tweenNode.RotationDegrees;
        float ab = a - b, ba = b - a;
        ab = ab < 0 ? ab + 360 : ab;
        ba = ba < 0 ? ba + 360 : ba;
        bool goClockwise = ab < ba;
        this._incrementScale = goClockwise ? 1 : -1;
        return (goClockwise, Mathf.Round(goClockwise ? ab : ba));
    }

    /// <summary>Keep node rotation between 0-360.</summary>
    private void _lockTweenNodeRotation()
    {
        float a = this._tweenNode.RotationDegrees;
        a = a < 0 ? 360 - (-a % 360) : a;
        this._tweenNode.RotationDegrees = a % 360;
    }
}

Upvotes: 1

Related Questions