How to update Tweener with parameters in Unity3D?

3.9k views Asked by At

I'd like to move up and down a certain part of a 2D-enemy-spaceship via DoTween's DOMoveY-shortcut. So far so good - it almost works... but when I try to change the value of the transitionTarget during gameplay the animation doesn't change accordingly.

So the problem seems to be that the code doesn't update the Tweener. Now I'd like to know what do I have to change of my code so that the Tweener gets updated when I change the value (via inspector) of the transitionTarget during gameplay?

This is the code:

public float transitionTarget = 0f;

private Tweener transitionTweener;
private bool toggleTransition = true;

void Start()
{
    TransitionTween(transitionTarget);

    transitionTweener.OnRewind(() => {
        Debug.Log("<<- Transition played backward and completed!");
        toggleTransition = true;
    });

    transitionTweener.OnComplete(() => {
        Debug.Log("->> Transition played and completed!");
        if (toggleTransition) toggleTransition = false;
        else toggleTransition = true;
    });
}

void TransitionTween(float targetY) {
    transitionTweener = this.transform.DOMoveY(targetY, 3f, false)
    .SetEase(Ease.Linear)
    .SetAutoKill(false)
    .SetId("tran")
    .Pause();
}

void Update() {
    if (toggleTransition) {
        transitionTweener.PlayForward();
    }
    else {
        transitionTweener.PlayBackwards();
    }
}
2

There are 2 answers

0
Brad_95 On

A simpler solution than IARI's (but perhaps less efficient) is to kill the current tween when you need to transition and create a new one with the new destination.

2
IARI On

Solution 1: Assuming that it suffices for you to have the updated transitionTarget take effect at the moment when the animation-cycle starts, there is a simple solution to your problem utilizing the ChangeEndValue method.

Moreover I suggest a simplification: Instead of rewinding the tween manually, I suggest that you use

SetLoops(-1, LoopType.Yoyo)

The same amount of control can be achieved by utilizing the OnStepCompleted callback.

public class EnemySpaceship : MonoBehaviour {
    private Tweener transitionTweener;
    public float transitionTarget = 0f;

    private Vector3 Endvalue => new Vector3(0.0f, transitionTarget, 0.0f);
    
    void Start()
    {
        transitionTweener = transform.DOMove(Endvalue, 3f, false)
            .SetOptions(AxisConstraint.Y)
            .SetEase(Ease.Linear)
            .SetLoops(-1, LoopType.Yoyo)
            .SetAutoKill(false);

        transitionTweener.OnStepComplete(OnStepCompleted);
    }

    private void OnStepCompleted() {
        if (transitionTweener.CompletedLoops() % 2 == 0) {
            Debug.Log("->> Transition played and completed!");
            transitionTweener.ChangeEndValue(Endvalue);            
        } else {
            Debug.Log("<<- Transition played backward and completed!");
        }
    }

}

Solution 2: If you do need the animation to use the updated target value instantly while the animation is still playing, then we need more clarifications. Consider the following cases for the time of the change of the transitionTarget value:

  1. during transition from the start location to the transitionTarget.
    1. the new transitionTarget value is yet to be reached
    2. the new transitionTarget value has already been surpassed
  2. during transition back from the transitionTarget to the start location.

I will ignore case 2. entirely, because the spaceship will have to return to the start location anyways, and that location which never changes - so in this solution like in the first, the change will only take effect, as soon as the loop is complete when the start location has been reached.

regarding 1., I suppose we can generally assume, that the movement of the spaceship should be continuous, so we will avoid any sort of "teleportation".

Unfortunately the ChangeEndValue method rewinds the tween. So if we want to use this method for case 1.1, we will have to manually "scrub" the tween to the correct position, which can be achieved with the Goto method. However, it appears that the Goto method does not work properly with infinite Loops (which should be reported as issue). The solution will just execute a single loop, still using LoopType.Yoyo (the number of loops has to be 2 to go forth and back once), and restart the tween afterwards (which is a bit closer to the sample code from the question).

In case of 1.2 we just scrub forward to the position in time, where we move back to the start-position, while we are at the same position, which is 2 * duration - transitionTweener.position

so without further ado, the solution:

public class EnemySpaceship : MonoBehaviour {
    private Tweener transitionTweener;
    public float transitionTarget = 0f;
    private float lastValue;
    private Vector3 startValue;

    private Vector3 Endvalue => new Vector3(0.0f, transitionTarget, 0.0f);
    private const float duration = 3f;
    
    void Start() {
        lastValue = transitionTarget;
        startValue = transform.position;
        transitionTweener = transform.DOMove(Endvalue, duration)
            .SetOptions(AxisConstraint.Y)
            .SetEase(Ease.Linear)
            .SetLoops(2, LoopType.Yoyo)
            .SetAutoKill(false);

        transitionTweener.OnComplete(RestartTween);
    }

    private void RestartTween() {
        transitionTweener.ChangeEndValue(Endvalue);
        transitionTweener.Restart();
    }


    private void Update() {
        if (lastValue != transitionTarget && transitionTweener.CompletedLoops() == 0) {
            var t = duration * (transform.position.y - startValue.y) / (transitionTarget - startValue.y);
            if (t > duration) {
                transitionTweener.Goto(2 * duration - transitionTweener.position, true);
            } else {
                transitionTweener.ChangeEndValue(Endvalue);
                transitionTweener.Goto(t, true);
            }
            lastValue = transitionTarget;
        }
    }
}

Note: To comply with your spec, that the tween should update when the transitionTarget is changed from within the inspector, the solution compares lastValue with transitionTarget every frame in the update method. To avoid this in the real application, you could probably do the following:

  • rename the update method to something like UpdateTarget, so you trigger it manually.
  • use a property to encapsulate transitionTarget and use the properties setter to invoke UpdateTarget
  • additionally invoke UpdateTarget when the tween is completed: transitionTweener.OnComplete(RestartTween);

An alternative option not considered in this Answer would be to recreate the tweens in the event of a change of transitionTarget.