Lerp函数
Lerp函数使用的最常见的误区是把线性移动用成了弹性移动(关键是自己还不知道,还奇怪,咦,不是说好的线性移动吗?怎么有点弹性的赶脚……),如下图所示:
void Update()
{
transform.position = Vector3.Lerp(transform.position, target, 0.1f);
}
这个错误主要是没有理解好Lerp的第三个参数t的作用,这里我们为了便于理解,我们拿Mathf.Lerp函数来分析,思路是一样的。我们先来看一下Mathf.Lerp函数的具体实现:
/// <summary>
/// <para>Clamps value between 0 and 1 and returns value.</para>
/// </summary>
/// <param name="value"></param>
public static float Clamp01(float value)
{
float result;
if (value < 0f)
{
result = 0f;
}
else if (value > 1f)
{
result = 1f;
}
else
{
result = value;
}
return result;
}
/// <summary>
/// <para>Linearly interpolates between a and b by t.</para>
/// </summary>
/// <param name="a">The start value.</param>
/// <param name="b">The end value.</param>
/// <param name="t">The interpolation value between the two floats.</param>
/// <returns>
/// <para>The interpolated float result between the two float values.</para>
/// </returns>
public static float Lerp(float a, float b, float t)
{
return a + (b - a) * Mathf.Clamp01(t);
}
估计大家一看函数的实现就明白了关键点,想要线性移动,应该只控制t的变化,t的值在0-1,它就类似一个百分比的值,代表着a点到b点之间的位置,比如想要到a和b之间的3/10的位置,t就应该是0.3,想要一步到位t的值就应该为1。在上述错误的用法中,实际的效果就是第一次到达1/10位置,第二次到达1/10+9/10*1/10的位置……理论上讲永远到不了b点,每一次都是a与b点之间的1/10的位置,所以移动的幅度越来越小,有一种弹性感觉。正确的使用方法如下:
private Vector3 target = new Vector3(0, 0, 5);
private Vector3 startPos;
private float t1;
void Start()
{
startPos = transform.position;
}
void Update()
{
t1 += 1f * Time.deltaTime;
transform.position = Vector3.Lerp(startPos, target, t1);
}
SmoothDamp函数
SmoothDamp函数在使用过程中比较容易出现的一个问题就是容易在代码较复杂的情形下将currentVelocity这个参数需要的变量定义为局部变量,如下:
public float smoothTime = 0.3F;
void Update()
{
Vector3 velocity = Vector3.zero;
transform.position = Vector3.SmoothDamp(transform.position, target, ref velocity, smoothTime);
}
}
使用局部变量的效果如下图所示,越来越慢,定义的平滑时间明明是0.3f,怎么用了10几秒的时间都还在缓慢移动?为了便于理解,我们来看一下Mathf.SmoothDamp的具体实现:
public static float SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, [DefaultValue("Mathf.Infinity")] float maxSpeed, [DefaultValue("Time.deltaTime")] float deltaTime)
{
smoothTime = Mathf.Max(0.0001f, smoothTime);
float num = 2f / smoothTime;
float num2 = num * deltaTime;
float num3 = 1f / (1f + num2 + 0.48f * num2 * num2 + 0.235f * num2 * num2 * num2);
float num4 = current - target;
float num5 = target;
float num6 = maxSpeed * smoothTime;
num4 = Mathf.Clamp(num4, -num6, num6);
target = current - num4;
float num7 = (currentVelocity + num * num4) * deltaTime;
currentVelocity = (currentVelocity - num * num7) * num3;
float num8 = target + (num4 + num7) * num3;
if (num5 - current > 0f == num8 > num5)
{
num8 = num5;
currentVelocity = (num8 - num5) / deltaTime;
}
return num8;
}
通过具体的实现我们就可以很清楚的发现,currentVelocity这个变量是先使用,然后再赋值,通过ref传递就是为了获取到上一次计算得到的速度值,如果我们使用局部变量,每一次速度都是零,相当于刚开始进行平滑移动,平滑的距离(transform.position到target)不断在缩短,然而平滑时间却没有变化,为了保证大约在平滑的时间内完成平滑移动,这个起步速度肯定是越来越慢的,所以就导致了上图中的问题。
我们把currentVelocity改为全局变量,就可以看到正常效果了
private Vector3 target = new Vector3(0, 0, 5);
public float smoothTime = 0.3F;
private Vector3 velocity = Vector3.zero;
void Update()
{
transform.position = Vector3.SmoothDamp(transform.position, target, ref velocity, smoothTime);
}
效果如下: