【Unity】DoTween の Tween や Sequence をほぼ途中から再生できる拡張メソッド

# 概要
DoTween を途中から再生する方法が見つからなかったので作ってみました。
具体的には、Tween や Sequence を n 秒スキップする拡張メソッドを作り、それを利用して途中から再生できる拡張メソッドを作りました。

![gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307985/bd935084-9faa-80c2-dd65-a095da2863ec.gif)
上の gif は、10秒で反時計回りに一周する sequence を用意して Cube に適応し、Button を押すと 2.5 秒スキップするようにしたものです。

実装方法は、次のフレームで目的の再生位置になるように timeScale を調整し、次のフレームで timeScale を元に戻すというものです。
Skip() にマイナスの値を入れた場合は、n 秒前の再生時間になるよう Restart() と Skip() を実行します 。

途中から再生する正式な方法や改良案などがありましたら、コメントをお願いします。

### 注意事項
スキップ前後の```Time.deltaTime```がほぼ同じという前提で実装しているので、FPSが不安定なほど目的の再生位置からズレます。
1フレーム進める必要があるので、停止中の Tween/Sequence でも1フレームだけ再生します。

# コード
お手持ちの DoTweenEx クラスに追加してご利用ください。

```DoTweenEx.cs
using DG.Tweening;
using UnityEngine;

namespace Extensions
{
    public static class DoTweenEx
    {
        /// <summary>
        /// [Caution] The more unstable the FPS, the more back and forth from the specified time.
        /// Since it is necessary to play 1 frame, let it play for 1 frame even if it is paused.
        /// </summary>
        public static Tween Skip(this Tween tween, float second, bool ignoreTimeScale = true)
        {
            if (second == 0f) return tween;
            var isPlayed = tween.IsPlaying();
            if (second < 0f)
            {
                var elapsedSecond = tween.Elapsed(true);
                second = elapsedSecond + second;
                tween.Restart();
                if (second <= 0f)
                {
                    if (!isPlayed) tween.Pause();
                    return tween;
                }
            }
            var timeScale = tween.timeScale;
            tween.timeScale = (second + (isPlayed ? Time.deltaTime : 0f)) / Time.deltaTime / (ignoreTimeScale ? 1f : timeScale);

            if (!isPlayed) tween.Play();

            DOVirtual.DelayedCall(float.Epsilon, () =>
            {
                if (tween == null) return;
                tween.timeScale = timeScale;
                if (!isPlayed) tween.Pause();
            });
            return tween;
        }
        /// <summary>
        /// [Caution] The more unstable the FPS, the more back and forth from the specified time.
        /// Since it is necessary to play 1 frame, let it play for 1 frame even if it is paused.
        /// </summary>
        public static Sequence Skip(this Sequence sequence, float second, bool ignoreTimeScale = true)
        {
            if (second == 0f) return sequence;
            var isPlayed = sequence.IsPlaying();
            if (second < 0f)
            {
                var elapsedSecond = sequence.Elapsed(true);
                second = elapsedSecond + second;
                sequence.Restart();
                if (second <= 0f)
                {
                    if (!isPlayed) sequence.Pause();
                    return sequence;
                }
            }

            var timeScale = sequence.timeScale;
            sequence.timeScale = (second + (isPlayed ? Time.deltaTime : 0f)) / Time.deltaTime / (ignoreTimeScale ? 1f : timeScale);
            
            if (!isPlayed) sequence.Play();

            DOVirtual.DelayedCall(float.Epsilon, () =>
            {
                if (sequence == null) return;
                sequence.timeScale = timeScale;
                if (!isPlayed) sequence.Pause();
            });
            return sequence;
        }
        public static Tween Restart(this Tween tween, float skipSecond, bool ignoreTimeScale = true)
        {
            tween.Restart();
            return tween.Skip(skipSecond, ignoreTimeScale);
        }
        public static Sequence Restart(this Sequence tween, float skipSecond, bool ignoreTimeScale = true)
        {
            tween.Restart();
            return tween.Skip(skipSecond, ignoreTimeScale);
        }
    }
}
```