Lerp:理解线性插值_天天信息

2023-07-06 00:31:20来源:哔哩哔哩

翻译自:Derek Stobbe Lerp: Understanding Linear InterpolationOct 7, 2016


(资料图片)

 

如果你曾经想要在玩家受到伤害时将他们的生命值条从绿色变为红色,根据能量水平计算攻击的伤害,或者在关卡设计中从黑暗平滑地过渡到光明,那么你很可能使用了线性插值或“lerp”操作。在游戏编程中,掌握线性插值并对其应用充满信心是非常宝贵的,但我注意到也存在一些需要当心的陷阱,所以我们来讨论一下“lerp”的确切含义,以及我们如何在代码中应用它。

线性插值背后的思想相当简单:寻找位于两个已知值之间距离的某个百分比的未知值(从技术上讲,它可以是一组任意大的已知值,但在游戏开发中,我发现它通常是两个值),为了清晰性和一致性,我们称第一个已知值为“a”,第二个已知值为“b”,距离百分比为“t”。以函数的形式,我们可以这样写: lerp(a, b, t).

这是我构建的一个可视化工具。你可以拖动点来实验两点之间的线性插值(绿色的点表示插值的值);悬停或触及方程顶部的某个点或有颜色的部分,将使所有匹配的值加粗:

有几点需要注意。首先,参数要么是数字,要么是由数字组成的东西。这自然意味着,你可以在浮点数和整数之间做插值,而你也可以通过分别lerping每个组件的值,在颜色、数值向量(就像Unity中的Vector2和Vector3类型),和由数字组成的自定义数据类型之间进行插值(Unity通过静态方法为前两项提供了包装器 和)

其次,请注意插值的一个有趣属性:

当(蓝色)t值为0时,绿色的插值点等于第一个(红色)点。当t = 1时,插值点等于第二个(紫色)点。基于此,你可以推测当t = 时,插值点位于两者之间,t = 是a到b距离的四分之三,以此类推。

请注意,可视化工具允许你探索小于0或大于1的t值,但此时,你不再是插值一个值,而是外推一个值。lerp函数的许多实现(包括Unity的)将t值夹在0和1之间,这意味着你将无法重现超出0 > t > 1的结果:

当夹紧输入时,例如lerp(a, b, ),给出与lerp(a, b, )相同的结果。

考虑到这一点,让我们谈谈我经常看到开发人员犯的一个错误:使用lerp在值之间进行插值,其中t值是由时间驱动的。请看下面的伪代码:

function charge_attack (target_value) {

_power = lerp(0, target_value, elapsed_game_time);

}

function update_game () {

if (button_held) {

charge_attack(target_value);

}

}

在这个例子中,当玩家按住按钮时,我们希望代表玩家攻击力的某些值在一段时间内(比如一秒钟)

从初始值0变为目标值。

然而,这个实现中有一个bug

还记得lerp函数的t值是如何被限制在[0,1]范围内的吗?

假设我们的elapsed_game_time值(由Time表示,例如Unity里的时间)

是一个表示游戏已经运行的秒数的浮点数

并且在游戏开始时从0开始递增,这段代码只会在游戏的第一秒按预期工作

在这之后,“消耗时间”将是一个大于1的数字,并且调用charge_attack会

立即将attack_power设置为目标值,而不是随着时间的推移不断增加,让玩家进行完全充满能量的攻击而不受惩罚,

从而破坏我们完美调整的游戏平衡。

我们如何解决这个问题?

我们需要将t的值重新映射到0到1(包括0和1)之间,最简单的方法是缓存用户开始按下按钮的时间。

在我们的示例实现中,它可能看起来像这样:

function charge_attack (target_value) {

progress = elapsed_game_time - button_pressed_at;

_power = lerp(0, target_value, progress);

}

function update_game () {

if (button_pressed) {

button_pressed_at = elapsed_game_time;

}

if (button_held) {

charge_attack(target_value);

}

}

唯一不同的是缓存了按下按钮的时间,并从游戏循环中调用charge_attack更新attack_power时,当前消耗的游戏时间中减去这个时间。

我们可以看到,这适用于我们所期望的1秒的持续时间,例如,假设玩家在秒时按下按钮。

在半秒之后,这样在游戏时间为秒时,我们计算的t值是 - ,也就是…正是我们想要的值。

如果我们不想在一秒内将attack_power改变为目标值,而是想在更长的一段时间内改变它,比如秒呢?

我们的实现再次失败了,因为在按下按钮一秒后,elapsed_game_time - button_pressed_at超过,并且被夹住了。我们如何解决这个问题?

好吧,如果我们看看我们当前的解决方案,我们可以看到elapsed_game_time - button_pressed_at给了我们一个小数,

表示我们当前在“填充”attack_power的进展,使其等于target_value。

我们知道任何数字除以1等于这个数本身,

所以我们可以用公式(elapsed_game_time - button_pressed_at) / 表示我们的进度。

巧合的是(或者不是),恰好代表了充能效果持续时间的旧需求,所以如果我们把这个常量换成一个表示充能时间的变量,就又回到了正轨:

function charge_attack (target_value) {

progress = (elapsed_game_time - button_pressed_at) / charge_duration;

_power = lerp(0, target_value, progress);

}

function update_game () {

if (button_pressed) {

button_pressed_at = elapsed_game_time;

}

if (button_held) {

charge_attack(target_value);

}

}

一切都按照预期运行,我们还得到一个额外的好处,就是可以轻松地调整充能持续时间来控制玩家的攻击需要多长时间才能达到最大威力。

游戏平衡完好无损!

正如我希望展示的那样,在游戏中成功使用线性插值的问题域主要涉及到如何将 t 进度变量映射到0到1之间的浮点数。

标签:

今日热门
More
生意
返回顶部