Unity万能插值工具:ValueLerp 支持多种类型平滑过渡

引言

在 Unity 开发中,平滑过渡(Lerp)是一个非常常见的需求,无论是对象的移动、颜色的渐变,还是数值的变化,都需要通过插值来实现平滑的效果。然而,为每种类型单独编写插值逻辑不仅繁琐,还容易导致代码冗余。今天,我将为大家介绍一个万能插值工具——ValueLerp,它能够轻松处理 GameObject 的移动、RectTransform 的位置变化、Transform/RectTransform 的角度变化、Color 的渐变以及 float 的数值变化,让你的开发效率大幅提升!

简介:

ValueLerp 是一个高度通用的 Unity 插值工具方法,支持对 GameObject 的 Transform 位置、RectTransform 的 anchoredPosition、以及二者的localRotation、Color 颜色值以及 float 浮点数进行平滑过渡(Lerp)。通过这一个方法,你可以轻松实现对象的平滑移动、颜色渐变、数值变化等多种效果,无需再为每种类型单独编写插值函数。

该方法通过协程(Coroutine)实现插值运算,并支持使用 Time.deltaTime 或 Time.unscaledDeltaTime 来控制插值速度。无论是游戏开发还是 UI 动画,ValueLerp 都能胜任。

功能特点

ValueLerp 是一个高度通用的插值工具方法,具有以下特点:

  1. 支持多种类型:

    • GameObject 的 Transform 位置插值(Vector3)。

    • RectTransform 的 anchoredPosition 插值(Vector2)。

    • Color 颜色值的插值。

    • float 浮点数的插值。

    • Transform/RectTransform的localRotation插值。

  2. 灵活的速度控制:

    • 支持通过 lerpSpeed 参数控制插值速度。

    • 可选择使用 Time.deltaTime 或 Time.unscaledDeltaTime,适应不同的场景需求(例如在游戏暂停时使用 unscaledTime)。

  3. 回调函数支持:

    • 提供 onUpdate 回调函数,方便在插值过程中实时获取当前值。

  4. 协程管理:

    • 通过 CoroutineManager 管理协程,避免重复启动协程。

上代码:

using System.Collections;
using UnityEngine;

public class CoroutineManager
{
    private Coroutine currentCoroutine;
    private MonoBehaviour monoBehaviour;

    public CoroutineManager(MonoBehaviour monoBehaviour)
    {
        this.monoBehaviour = monoBehaviour;
    }

    public void StartNewCoroutine(IEnumerator coroutine)
    {
        if (currentCoroutine != null)
        {
            monoBehaviour.StopCoroutine(currentCoroutine);
        }
        currentCoroutine = monoBehaviour.StartCoroutine(coroutine);
    }
}

public class Lerps : MonoBehaviour
{
    public static void ValueLerp<T, U>(U obj, T targetValue, float lerpSpeed, bool useUnscaledTime, MonoBehaviour monoBehaviour,AnimationCurve curve = null, System.Action<T> onUpdate = null)
    {
        CoroutineManager coroutineManager = new CoroutineManager(monoBehaviour);

        if (obj is GameObject gameObject && targetValue is Vector3 vector3Target)
        {
            // 处理 GameObject 的 Transform 位置插值
            if(curve == null)
            {
                Transform transform = gameObject.transform;
                coroutineManager.StartNewCoroutine(PosLerpCoroutine(transform, vector3Target, lerpSpeed, useUnscaledTime));
            }
            else
            {
                Transform transform = gameObject.transform;
                coroutineManager.StartNewCoroutine(PosLerpCoroutine(transform, vector3Target, lerpSpeed, useUnscaledTime,curve));
            }
        }
        else if (obj is RectTransform rectTransform && targetValue is Vector2 vector2Target)
        {
            // 处理 RectTransform 的 anchoredPosition 插值
            if(curve == null)
            {
                coroutineManager.StartNewCoroutine(PosLerpCoroutine(rectTransform, vector2Target, lerpSpeed, useUnscaledTime));
            }
            else
            {
                coroutineManager.StartNewCoroutine(PosLerpCoroutine(rectTransform, vector2Target, lerpSpeed, useUnscaledTime,curve));
            }
        }
        else if(obj is Transform transF && targetValue is Quaternion quaternionTarget)
        {
            if(curve == null)
            {
                coroutineManager.StartNewCoroutine(RotationLerpCoroutine(transF, quaternionTarget, lerpSpeed, useUnscaledTime));
            }
            else
            {
                coroutineManager.StartNewCoroutine(RotationLerpCoroutine(transF, quaternionTarget, lerpSpeed, useUnscaledTime,curve));
            }
        }
        else if (obj is float floatValue && targetValue is float floatTarget)
        {
            // 处理 float 值的插值
            if(curve == null)
            {
                coroutineManager.StartNewCoroutine(ValueLerpCoroutine(floatValue, floatTarget, lerpSpeed, useUnscaledTime, onUpdate as System.Action<float>));
            }
            else
            {
                coroutineManager.StartNewCoroutine(ValueLerpCoroutine(floatValue, floatTarget, lerpSpeed, useUnscaledTime, onUpdate as System.Action<float>,curve));
            }
        }
        else if (obj is Color colorValue && targetValue is Color colorTarget)
        {
            // 处理 Color 值的插值
            if(curve == null)
            {
                coroutineManager.StartNewCoroutine(ColorLerpCoroutine(colorValue, colorTarget, lerpSpeed, useUnscaledTime, onUpdate as System.Action<Color>));
            }
            else
            {
                coroutineManager.StartNewCoroutine(ColorLerpCoroutine(colorValue, colorTarget, lerpSpeed, useUnscaledTime, onUpdate as System.Action<Color>,curve));
            }
        }
        else
        {
            Debug.LogError("Unsupported type for ValueLerp");
        }
    }

    public static IEnumerator PosLerpCoroutine(Transform transform, Vector3 targetValue, float lerpSpeed, bool useUnscaledTime,AnimationCurve curve = null)
    {
        float elapsedTime = 0;
        Vector3 startValue = transform.position;

        while (elapsedTime < 1)
        {
            float t = curve.Evaluate(elapsedTime);
            transform.position = Vector3.Lerp(startValue, targetValue, t);

            if (useUnscaledTime)
            {
                elapsedTime += Time.unscaledDeltaTime * lerpSpeed;
            }
            else
            {
                elapsedTime += Time.deltaTime * lerpSpeed;
            }

            yield return null;
        }

        transform.position = targetValue;
    }
    public static IEnumerator RotationLerpCoroutine(Transform transform, Quaternion targetValue, float lerpSpeed, bool useUnscaledTime,AnimationCurve curve = null)
    {
        float elapsedTime = 0;
        Quaternion startValue = transform.localRotation;

        while (elapsedTime < 1)
        {
            float t = curve.Evaluate(elapsedTime);
            transform.localRotation = Quaternion.Lerp(startValue, targetValue, t);

            elapsedTime += (useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime) * lerpSpeed;
            yield return null;
        }

        transform.localRotation = targetValue;
    }
    public static IEnumerator PosLerpCoroutine(RectTransform rectTransform, Vector2 targetValue, float lerpSpeed, bool useUnscaledTime,AnimationCurve curve = null)
    {
        float elapsedTime = 0;
        Vector2 startValue = rectTransform.anchoredPosition;

        while (elapsedTime < 1)
        {
            float t = curve.Evaluate(elapsedTime);
            rectTransform.anchoredPosition = Vector2.Lerp(startValue, targetValue, t);

            if (useUnscaledTime)
            {
                elapsedTime += Time.unscaledDeltaTime * lerpSpeed;
            }
            else
            {
                elapsedTime += Time.deltaTime * lerpSpeed;
            }

            yield return null;
        }

        rectTransform.anchoredPosition = targetValue;
    }

    public static IEnumerator ColorLerpCoroutine(Color startColor, Color targetColor, float lerpSpeed, bool useUnscaledTime, System.Action<Color> onUpdate,AnimationCurve curve = null)
    {
        float elapsedTime = 0;
        while (elapsedTime < 1)
        {
            float t = curve.Evaluate(elapsedTime);
            Color currentColor = Color.Lerp(startColor, targetColor, t);
            onUpdate?.Invoke(currentColor); // 通过回调传递当前颜色

            if (useUnscaledTime)
            {
                elapsedTime += Time.unscaledDeltaTime * lerpSpeed;
            }
            else
            {
                elapsedTime += Time.deltaTime * lerpSpeed;
            }

            yield return null;
        }
        onUpdate?.Invoke(targetColor); // 确保最终颜色准确
    }

    public static IEnumerator ValueLerpCoroutine(float startValue, float endValue, float lerpSpeed, bool useUnscaledTime, System.Action<float> onUpdate,AnimationCurve curve = null)
    {
        float elapsedTime = 0;
        while (elapsedTime < 1)
        {
            float t = curve.Evaluate(elapsedTime);
            float currentValue = Mathf.Lerp(startValue, endValue, t);
            onUpdate?.Invoke(currentValue); // 通过回调传递当前值

            if (useUnscaledTime)
            {
                elapsedTime += Time.unscaledDeltaTime * lerpSpeed;
            }
            else
            {
                elapsedTime += Time.deltaTime * lerpSpeed;
            }

            yield return null;
        }
        onUpdate?.Invoke(endValue); // 确保最终值准确
    }
}

使用方法

1. 引入脚本

将 Lerps 脚本挂载到任意 GameObject 上,或直接调用静态方法。

2. 调用 ValueLerp 方法

根据需要调用 ValueLerp 方法,传入目标对象、目标值、插值速度等参数。

// 移动 GameObject
Lerps.ValueLerp(gameObject, new Vector3(10, 0, 0), 1.0f, false, this);

// 插值浮点数
float startValue = 0;
float endValue = 100;
Lerps.ValueLerp(startValue, endValue, 1.0f, false, this, null,(value) =>
{
    startValue = value; //更新
    Debug.Log("当前值:" + value); // 实时获取插值结果
});

// 插值颜色
Color startColor = Color.red;
Color endColor = Color.blue;
Lerps.ValueLerp(startColor, endColor, 1.0f, false, this, null,(color) =>
{
    startColor = color; //更新
    Debug.Log("当前颜色:" + color); // 实时获取插值结果
});
//插值角度
UI:RectTransform
GameObject:Transform
Lerps.ValueLerp(object.transform/rectTransform,Euler(Rx,Ry,Rz),lerpSpeed,true,this);

代码示例

以下是一个完整的使用示例:

using UnityEngine;

public class Example : MonoBehaviour
{
    private void Start()
    {
        // 移动 GameObject
        Lerps.ValueLerp(gameObject, new Vector3(10, 0, 0), 1.0f, false, this);

        // 插值浮点数
        float startValue = 0;
        float endValue = 100;
        Lerps.ValueLerp(startValue, endValue, 1.0f, false, this, null,(value) =>
        {
            startValue = value; //更新
            Debug.Log("当前值:" + value); // 实时获取插值结果
        });

        // 插值颜色
        Color startColor = Color.red;
        Color endColor = Color.blue;
        Lerps.ValueLerp(startColor, endColor, 1.0f, false, this, null,(color) =>
        {
            startColor = color; //更新
            Debug.Log("当前颜色:" + color); // 实时获取插值结果
        });
        //插值角度     
    Lerps.ValueLerp(transform(rectTransform),Quaternion.Euler(0,0,90),lerpSpeed,true,this);
    }
}

适用场景

  • UI动画: 实现 RectTransform 的平滑移动或缩放。

  • 对象移动: 实现 GameObject 的平滑移动。

  • 颜色过渡: 实现材质颜色、UI 颜色的平滑过渡。

  • 数值变化: 实现血量、进度条等数值的平滑变化。

  • 角度变化:实现UI或场景内物体的角度平滑过渡

实现原理

ValueLerp 的核心原理是通过协程(Coroutine)实现插值运算。根据传入的参数类型,ValueLerp 会自动选择对应的插值逻辑:

  • 如果是 GameObject,则插值其 Transform 的位置。

  • 如果是 RectTransform,则插值其 anchoredPosition

  • 如果是 float,则通过 Mathf.Lerp 实现数值插值。

  • 如果是 Color,则通过 Color.Lerp 实现颜色插值。

  • 如果是Transform/RectTransform,则插值其localRotation实现角度插值。

通过回调函数 onUpdateValueLerp 可以将插值结果实时传递回外部,确保外部变量能够正确更新。

 注意事项

  1. 类型匹配:

    • 确保传入的 obj 和 targetValue 类型匹配,否则会触发 Unsupported type for ValueLerp 错误。

  2. 协程管理:

    • 如果需要停止插值,可以通过 CoroutineManager 停止当前协程。

  3. 性能优化:

    • 如果频繁调用插值函数,建议优化协程的使用,避免产生大量协程。

总结

ValueLerp 是一个功能强大且高度通用的插值工具,适用于 Unity 中的多种场景。通过一个方法,你可以轻松实现平滑过渡效果,提升游戏的视觉表现和用户体验。

作者抱怨环节:

bro再也不会戴着耳机写东西了,给我脑袋热的..... 今天更新角度插值的时候流了四次鼻血.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值