unity3d————Mathf.Lerp() 函数详解
Mathf.Lerp()
是Unity中的一个非常有用的数学函数。它的名字来自于“Linear Interpolation”的缩写,意思是“线性插值”。
想象一下,你有两个点,一个点叫A,另一个点叫B。现在,你想在A和B之间找到一个新的点,这个点不是随便找的,而是根据一定的比例来确定的。这个比例我们称之为t,t的范围是从0到1。
- 当t=0时,新点就是A点。
- 当t=1时,新点就是B点。
- 当t在0和1之间时,新点就会在A和B之间,离A和B的距离取决于t的值。
Mathf.Lerp()
函数就是帮你找到这个新点的位置的。
为什么要用Mathf.Lerp()?
-
平滑过渡:在游戏开发中,经常需要让物体平滑地从一个位置移动到另一个位置。使用
Mathf.Lerp()
可以实现这种平滑过渡的效果,让物体看起来移动得更加自然。 -
动画效果:在制作动画时,
Mathf.Lerp()
可以帮助你创建两个状态之间的平滑变化,比如颜色的渐变、物体大小的缩放等。 -
性能优化:在某些情况下,直接计算物体的新位置可能比较复杂或者耗时。使用
Mathf.Lerp()
可以简化计算,提高游戏的性能。 -
灵活性:
Mathf.Lerp()
不仅可以用于位置的插值,还可以用于其他任何可以用数字表示的属性的插值,比如颜色、旋转角度、缩放比例等。
计算公式:
result = start + (end - start) * t
下面来看这俩个例子实例代码:
public class CudeFollow : MonoBehaviour
{
// B位置信息
public Transform B;
//跟随速度
public float speed = 10;
private Vector3 pos;
private Vector3 startPos;
private Vector3 bNowPos;
float time;
// Update is called once per frame
void Update()
{
//插值运算用法一
//每帧改变start的值——变化速度先快后慢,位置无限接近,但是不会得到end位置
//start = Mathf.Lerp(start, 10, Time.deltaTime);
//result = start + (end - start)*t
//pos = this.transform.position;
//pos.x = Mathf.Lerp(pos.x, B.position.x, Time.deltaTime);
//pos.y = Mathf.Lerp(pos.y, B.position.y, Time.deltaTime);
//pos.z = Mathf.Lerp(pos.z, B.position.z, Time.deltaTime);
//第二种 就是 匀速运动
if (bNowPos != B.position)
{
time = 0;
bNowPos = B.position;
startPos = this.transform.position;
}
time += Time.deltaTime;
pos.x = Mathf.Lerp(startPos.x, bNowPos.x, time);
pos.y = Mathf.Lerp(startPos.y, bNowPos.y, time);
pos.z = Mathf.Lerp(startPos.z, bNowPos.z, time);
this.transform.position = pos;
}
}
第一种是先快后慢(物体A靠近物体B的速度是先快后慢),那这是为什么呢?
首先,让我们澄清一下 Mathf.Lerp()
函数的行为。Mathf.Lerp(a, b, t)
会根据 t
(一个通常在 0 到 1 之间的浮点数)返回 a
和 b
之间的一个插值。当 t
接近 0 时,返回值更接近 a
;当 t
接近 1 时,返回值更接近 b
。如果 t
是 Time.deltaTime
(即上一帧到当前帧的时间间隔),那么插值的速度将取决于这个时间间隔的大小以及 a
和 b
之间的距离。
-
插值参数:您使用
Time.deltaTime
作为插值参数t
。Time.deltaTime
是一个很小的数,表示上一帧到当前帧的时间间隔(通常以秒为单位)。因此,每次插值时,物体 A 的位置只会向物体 B 的位置移动一小步。 -
逐帧插值:在
Update()
方法中,您每帧都对物体 A 的位置进行插值运算。由于Time.deltaTime
很小,每帧的移动量也很小,导致物体 A 逐渐靠近物体 B,但速度越来越慢。这是因为随着 A 越来越接近 B,每帧需要移动的距离变得越来越小。 -
插值的目标:在您的代码中,插值的目标是物体 B 的当前位置。如果物体 B 是静止的,那么物体 A 最终会停在物体 B 的位置(但由于
Time.deltaTime
的使用,它可能永远不会完全到达,只是无限接近)。如果物体 B 在移动,那么物体 A 会尝试跟随物体 B 的移动。 -
速度感知:由于插值是基于时间的,所以物体 A 移动的速度会感觉上是逐渐减慢的。这是因为随着 A 接近 B,每帧移动的绝对距离在减小,尽管相对移动的比例可能保持不变。
或者我在分析分析按我自己理解,其实也是看公式,t 不变 然后start 跟 end 距离不断减小 意味着 每一帧移动的距离都是不断减小的,时间一样 ,每一帧的速度就不断减小,看起来就是不断变慢过程,
第二种是匀速,为什么呢?
- 初始化条件:
- 当
bNowPos
(上一次记录的B的位置)不等于B.position
(B当前的位置)时,说明B已经移动了。 - 此时,您重置
time
为0,并更新bNowPos
和startPos
为当前的位置。这相当于在每次B移动时重新开始一个新的插值过程。
- 当
- 插值过程:
- 在每一帧中,您使用
Mathf.Lerp(startPos, bNowPos, time)
来计算A的新位置。 - 这里的
time
是从B开始移动(或者从A开始跟随B的新的位置)时累积的时间。 Mathf.Lerp
函数会根据time
在startPos
和bNowPos
之间进行线性插值。因为time
是均匀增加的(每帧加上Time.deltaTime
),所以插值的结果也是均匀变化的,即A会以恒定的速度向B的当前位置移动。
- 在每一帧中,您使用
- 匀速的原因:
- 在这种方法中,A的移动速度是由
time
的增加速率决定的,而time
是每帧均匀增加的(通过Time.deltaTime
)。 - 不像第一种方法中使用
Time.deltaTime
作为插值参数(这会导致速度逐渐减慢),在这里Time.deltaTime
只是用来更新time
变量,而time
变量本身则用于控制插值的进度。 - 因此,无论A和B之间的距离如何,A都会以相同的速度(在插值空间内)向B移动,直到到达B的位置。这个速度是由插值函数的线性性质和
time
的均匀增加共同决定的。
- 在这种方法中,A的移动速度是由
- 到达目标:
- 在这种方法中,当
time
增加到足够大时(具体取决于startPos
和bNowPos
之间的距离以及插值的速度),A最终会到达B的位置。 - 与第一种方法不同,这里没有使用
Time.deltaTime
来直接控制每帧的移动量,而是用它来累积时间,从而通过插值函数间接控制移动量。这导致了匀速运动的效果。
- 在这种方法中,当
总之,第二种方法实现了匀速运动,因为它通过累积时间来控制插值的进度,而不是直接使用每帧的时间间隔来控制移动量。这种方法更加直观和可控,特别是当您想要实现恒定的跟随速度时。
实际上,这两种方法在理论上都不会使物体A完全到达物体B的位置,而是会无限接近。但在实际的游戏或模拟环境中,这种差异通常是可以忽略的。
第一种方法(使用Time.deltaTime
作为插值参数)
在这种方法中,Mathf.Lerp()
函数的t
参数是Time.deltaTime
。由于Time.deltaTime
代表上一帧到当前帧的时间间隔,它总是一个非零的浮点数(除非游戏暂停或帧率极高,但在实际情况下这不会发生)。因此,每帧的插值都会使物体A向物体B移动一个非常小的距离。即使物体B是静止的,物体A也可能永远不会完全到达B的位置,因为它总是在接近但永远不会完全等于B的位置。
第二种方法(累积时间作为插值参数)
在第二种方法中,您使用了一个累积的时间变量time
来控制插值的进度。这个变量是通过每帧加上Time.deltaTime
来累积的。虽然这种方法看起来更像是一个匀速运动的过程,但同样由于浮点数的精度限制和Time.deltaTime
的非零性,物体A也可能永远不会完全到达物体B的位置。
然而,在实际应用中,当物体A足够接近物体B时,由于视觉上的限制和浮点数的精度问题,玩家或观察者通常无法区分物体A是否已经完全到达了物体B的位置。因此,在大多数情况下,可以认为物体A已经“到达”了物体B的位置,即使从数学的角度来看它们之间可能仍然存在一个极小的差距。