最短路径
首先,lerp_angle 是一个 GDScript 内置函数。而且它会走得更短。
例如:
extends Sprite
var from := 0.0
var to := 0.0
var weight := 0.0
func _process(delta: float) -> void:
if Input.is_action_pressed("Click"):
var mouse_position = get_viewport().get_mouse_position()
from = rotation
to = Vector2.UP.angle_to(mouse_position - global_position)
weight = 0.0
weight = min(weight + delta * 10, 1.0)
rotation = lerp_angle(from, to, weight)
如果你定义你自己的,你需要给它起别的名字,否则,Godot 不会使用它。例如:
func my_lerp_angle(from, to, weight):
var difference = fmod(to - from, TAU)
var distance = fmod(2.0 * difference, TAU) - difference
return from + distance * weight
此实现基于 Godot 源。说服自己这等同于您的代码。请记住,TAU 是一个回合(助记词:它以 t 开头),即您的 max_angle (PI * 2)。
您可能还对fposmod 和wrapf 函数感兴趣。事实上,我们可以像这样更短的实现:
func my_lerp_angle(from, to, weight):
var distance = wrapf(to - from, -PI, PI)
return from + distance * weight
这里wrapf 会给我们一个介于-PI 和PI 之间的值。或者,如果您愿意:
func short_angle_dist(from, to):
return wrapf(to - from, -PI, PI)
func my_lerp_angle(from, to, weight):
return from + short_angle_dist(from, to) * weight
最长的路
最长的方式就是采取最短的方式并在相反方向上添加一个旋转。也就是加上一个反号的TAU,即:
func my_lerp_angle(from, to, weight):
var distance = wrapf(to - from, -PI, PI)
distance -= TAU * sign(distance)
return from + distance * weight
或者,如果您愿意:
func complement_angle(angle):
return angle - TAU * sign(angle)
func short_angle_dist(from, to):
return wrapf(to - from, -PI, PI)
func long_angle_dist(from, to):
return complement_angle(short_angle_dist(from, to))
func my_lerp_angle(from, to, weight):
return from + long_angle_dist(from, to) * weight
自然方式
你认为你想要最长的路。你没有。你想要的是避免角色的脖子在动画中被勒死。
回到基本的 lerp:
func my_lerp_angle(from, to, weight):
var distance = to - from
return from + distance * weight
这将为您提供一个永远不会超过半圈的旋转。
好吧,除非你的角度没有标准化,但我们可以解决这个问题:
func my_lerp_angle(from, to, weight):
var distance = wrapf(to, -PI, PI) - wrapf(from, -PI, PI)
return from + distance * weight
如果你想移动它从不相交的角度,那就是抵消这些角度。结果是这样的:
func my_lerp_angle(from, to, zero, weight):
to = wrapf(to - zero, -PI, PI)
from = wrapf(from - zero, -PI, PI)
var distance = to - from
return from + distance * weight + zero
其中zero 是中间位置(偏离角度半转以避免)。
或者像这样:
func my_lerp_angle(from, to, avoid, weight):
to = wrapf(to - avoid + PI, -PI, PI)
from = wrapf(from - avoid + PI, -PI, PI)
var distance = to - from
return from + distance * weight + avoid - PI
其中avoid 是旋转不应交叉的角度。
此外,头部的范围有限。如果目标角度 (to) 不在它的范围内,请不要放纵。像这样的:
extends Sprite
var from := 0.0
var to := 0.0
var weight := 0.0
var zero := deg2rad(90)
func _process(delta: float) -> void:
if Input.is_action_pressed("Click"):
var mouse_position = get_viewport().get_mouse_position()
from = rotation
to = Vector2.UP.angle_to(mouse_position - global_position)
weight = 0.0
if abs(wrapf(to - zero, -PI, PI)) > deg2rad(170):
return
weight = min(weight + delta * 10, 1.0)
rotation = my_lerp_angle(from, to, zero, weight)
func my_lerp_angle(from, to, zero, weight):
to = wrapf(to - zero, -PI, PI)
from = wrapf(from - zero, -PI, PI)
var distance = to - from
return from + distance * weight + zero
总是顺时针,总是逆时针
为了完整起见,这里是一个简单的总是顺时针的 lerp:
func my_lerp_angle(from, to, weight):
var distance = fposmod(to - from, TAU)
return from + distance * weight
逆时针:
func my_lerp_angle(from, to, weight):
var distance = fposmod(to - from, -TAU)
return from + distance * weight
附录:角度范围
虽然上面我建议在角度超出其范围时不要放纵。如果那不是您想要的……我想您仍然想 lerp,但将其限制在一个范围内。
要到达那里,我们需要从夹紧角度开始。但是,我们需要确保对其进行归一化(例如wrapf(angle, -PI, PI)),然后进行钳制:
func clamp_angle(angle, min_angle, max_angle):
return clamp(wrapf(angle, -PI, PI), min_angle, max_angle)
或者只是从零开始的deviation 角度:
func clamp_angle(angle, deviation):
return clamp(wrapf(angle, -PI, PI), -deviation, deviation)
说到zero,我们需要考虑如何处理它。一种选择是钳制输出,一旦与 lerp 代码合并,如下所示:
func my_lerp_angle(from, to, zero, deviation, weight):
to = wrapf(to - zero, PI, -PI)
from = wrapf(from - zero, PI, -PI)
var distance = to - from
var angle = wrapf(from + distance * weight, PI, -PI)
return clamp(angle, -deviation, deviation) + zero
但是,请记住,这会截断 lerp。因此,动画将比停止在那里更快地到达极限。
为避免这种情况,不要钳制输出,而是钳制目标角度 (to):
func my_lerp_angle(from, to, zero, deviation, weight):
to = clamp(wrapf(to - zero, PI, -PI), -deviation, deviation)
from = wrapf(from - zero, PI, -PI)
var distance = to - from
return from + distance * weight + zero