引言
在 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
是一个高度通用的插值工具方法,具有以下特点:
-
支持多种类型:
-
GameObject
的Transform
位置插值(Vector3
)。 -
RectTransform
的anchoredPosition
插值(Vector2
)。 -
Color
颜色值的插值。 -
float
浮点数的插值。 -
Transform/RectTransform的localRotation插值。
-
-
灵活的速度控制:
-
支持通过
lerpSpeed
参数控制插值速度。 -
可选择使用
Time.deltaTime
或Time.unscaledDeltaTime
,适应不同的场景需求(例如在游戏暂停时使用unscaledTime
)。
-
-
回调函数支持:
-
提供
onUpdate
回调函数,方便在插值过程中实时获取当前值。
-
-
协程管理:
-
通过
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实现角度插值。
通过回调函数 onUpdate
,ValueLerp
可以将插值结果实时传递回外部,确保外部变量能够正确更新。
注意事项
-
类型匹配:
-
确保传入的
obj
和targetValue
类型匹配,否则会触发Unsupported type for ValueLerp
错误。
-
-
协程管理:
-
如果需要停止插值,可以通过
CoroutineManager
停止当前协程。
-
-
性能优化:
-
如果频繁调用插值函数,建议优化协程的使用,避免产生大量协程。
-
总结
ValueLerp
是一个功能强大且高度通用的插值工具,适用于 Unity 中的多种场景。通过一个方法,你可以轻松实现平滑过渡效果,提升游戏的视觉表现和用户体验。
作者抱怨环节:
bro再也不会戴着耳机写东西了,给我脑袋热的..... 今天更新角度插值的时候流了四次鼻血.