DoTween是一款非常好用的补间动画插件,但是其不支持在非运行状态下预览动画

所以我尝试对DoTween进行扩展,让其能够支持非运行时预览

首先编写一个BaseTween基类

public enum EaseType
{
    Ease,
    AnimationCurve,
}

public abstract class BaseTween : MonoBehaviour
{
    [Header("BaseTween")] [LabelText("动画时长")]
    public float tweenTime = 1f;

    [LabelText("循环")] public int loopTimes = -1;
    [LabelText("动画曲线类型")]public EaseType easeType = EaseType.Ease;

    [LabelText("动画曲线")] [ShowIf("@easeType==EaseType.Ease")]
    public Ease ease = Ease.Linear;

    [LabelText("动画曲线")] [ShowIf("@easeType==EaseType.AnimationCurve")]
    public AnimationCurve
        animationCurve = AnimationCurve.Linear(0, 0, 1, 1);

    [LabelText("动画速度缩放")] public float tweenScale = 1f;
    [LabelText("倒放")] public bool isReserve = false;

    public Tweener tweener;

    [ShowInInspector] [ReadOnly][LabelText("当前百分比")]  protected float currentPercent;

    public float CurrentPercent
    {
        get => currentPercent;
        set => currentPercent = value;
    }

    [Range(0, 1)][LabelText("预览百分比")] public float previewPercent;

    protected virtual void Awake()
    {
        currentPercent = 0;
    }

    [Button("播放动画")]
    public virtual Tweener PlayTween()
    {
        if (tweener != null)
        {
            tweener.Goto(currentPercent * tweenTime, true);
        }
        else
        {
            ResetTween();
        }

        return tweener;
    }

    [Button("暂停动画")]
    public virtual Tweener PauseTween()
    {
        if (tweener != null)
        {
            tweener.Pause();
        }

        return tweener;
    }

    [Button("停止动画")]
    public virtual Tweener StopTween()
    {
        if (tweener != null)
        {
            tweener.Pause();
            tweener.Kill();
        }

        return tweener;
    }

    [Button("重置动画")]
    public virtual Tweener ResetTween()
    {
        if (tweener != null)
        {
            tweener.Kill();
        }

        tweener = DOTween.To(() => isReserve ? 1f : 0f,
                             value => OnTweenUpdate(value),
                             isReserve ? 0f : 1f, tweenTime)
            .SetLoops(loopTimes);
        //percent已经在此倒置
        switch (easeType)
        {
            case EaseType.Ease:
                tweener.SetEase(ease);
                break;
            case EaseType.AnimationCurve:
                tweener.SetEase(animationCurve);
                break;
        }

        return tweener;
    }


    protected virtual Tweener OnTweenUpdate(float percent)
    {
        currentPercent = percent;
        if (tweener != null)
        {
            tweener.timeScale = tweenScale;
        }

        return tweener;
    }

    protected virtual void OnValidate()
    {
    }

    protected virtual void Reset()
    {
    }
}

其中直接使用了DOTween.To从0到1进行补间,这个值表示动画百分比,然后在OnTweenUpdate中编写对应百分比时执行的操作即可。

编辑器下预览脚本如下,原理就是绑定EditorApplication.update回调,再调用OnTweenUpdate即可

public abstract class BaseTween : MonoBehaviour
{
    //
    #region 编辑器下预览

#if UNITY_EDITOR
        protected float editorTimer;        //计时器
        protected float editorStartTime;    //起始运行时间
        protected int editorTimes;          //运行次数

        [Button("编辑器中预览")][HorizontalGroup("Editor")]
        public virtual void PreviewInEditor()
        {
            editorTimer = 0;
            editorTimes = 0;
            editorStartTime = Time.realtimeSinceStartup;
            EditorApplication.update -= OnEditorUpdated;
            EditorApplication.update += OnEditorUpdated;
        }

        [Button("停止预览")][HorizontalGroup("Editor")]
        public virtual void StopPreviewInEditor()
        {
            EditorApplication.update -= OnEditorUpdated;
        }
        
        protected virtual void OnEditorUpdated()
        {
            editorTimer = Time.realtimeSinceStartup - editorStartTime;

            var percentTimer =
                MathTool.MapClamped(editorTimer, 0, tweenTime, 0, 1);
            //计算动画曲线实际百分比
            if (easeType == EaseType.Ease)
            {
                OnTweenUpdate(EaseManager.Evaluate(ease, null, editorTimer,
                    tweenTime, DOTween.defaultEaseOvershootOrAmplitude,
                    DOTween.defaultEasePeriod));
            }

            if (easeType == EaseType.AnimationCurve)
            {
                OnTweenUpdate(animationCurve.Evaluate(percentTimer));
            }

            if (editorTimer > tweenTime)
            {
                editorTimes++;
                editorStartTime = Time.realtimeSinceStartup;
                if (loopTimes >= 0)
                {
                    if (editorTimes >= loopTimes)
                    {
                        Debug.Log(
                            $"{name}动画预览完成,动画时长{tweenTime}s,循环{editorTimes}次,实际耗时{editorTimer * loopTimes}s");
                        EditorApplication.update -= OnEditorUpdated;
                        editorTimes = 0;
                    }
                }
                editorTimer = 0;
            }
        }
#endif

        #endregion
}

下面以一个简单的位置补间为例

public class TweenTransform : BaseTween
{
    [Header("TweenTransform")]
    [LabelText("目标物体")]public Transform target;
    [LabelText("起点")]public Transform from;
    [LabelText("终点")]public Transform to;
    [TabGroup("位置")]
    [LabelText("使用位置")]public bool tweenPosition = true;
    [TabGroup("位置")]
    [LabelText("使用X坐标")]public bool useX = true;
    [TabGroup("位置")]
    [LabelText("使用Y坐标")]public bool useY = true;
    [TabGroup("位置")]
    [LabelText("使用Z坐标")]public bool useZ = true;
    [TabGroup("旋转")][LabelText("使用旋转")]public bool tweenRotation = true;
    protected override Tweener OnTweenUpdate(float percent)
    {
        if (target != null&&@from!=null&&to!=null)
        {
            if (tweenPosition)
            {
                target.position = new Vector3(
                    !useX
                        ? target.position.x
                        : Mathf.Lerp(@from.position.x, to.position.x, percent),
                    !useY
                        ? target.position.y
                        : Mathf.Lerp(@from.position.y, to.position.y, percent),
                    !useZ
                        ? target.position.z
                        : Mathf.Lerp(@from.position.z, to.position.z, percent));
            }

            if (tweenRotation)
            {
                target.rotation =
                    Quaternion.Lerp(@from.rotation, to.rotation, percent);
            }
        }

        return base.OnTweenUpdate(percent);
    }

    protected override void OnValidate()
    {
        base.OnValidate();
        OnTweenUpdate(previewPercent);
    }
}

面板如图所示

在非运行状态下预览的效果如下

shadow1232

甚至可以继续扩展为曲线路径,然后在非运行状态下预览

Last modification:September 1st, 2022 at 04:21 pm