如何在 Godot 中平滑旋转?

Fre*_*den 1 godot

下面是我的代码(忽略输入变量,它只是用于移动):

var input = Vector3()
    if Input.is_action_pressed("move_forward") and Input.is_action_pressed("move_left"):
        rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, -1), delta * 7)
        input[2] = 1
        input[0] = 1
    elif Input.is_action_pressed("move_forward") and Input.is_action_pressed("move_right"):
        rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, 1), delta * 7)
        input[2] = 1
        input[0] = -1
    elif Input.is_action_pressed("move_backward") and Input.is_action_pressed("move_left"):
        rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, -1), delta * 7)
        input[2] = -1
        input[0] = 1
    elif Input.is_action_pressed("move_backward") and Input.is_action_pressed("move_right"):
        rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, 1), delta * 7)
        input[2] = -1
        input[0] = -1
    else:
        if Input.is_action_pressed("move_forward"):
            rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(1, 0), delta * 7)
            input[2] = 1
        if Input.is_action_pressed("move_backward"):
            rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(-1, 0), delta * 7)
            input[2] = -1
        if Input.is_action_pressed("move_left"):
            rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(0, -1), delta * 7)
            input[0] = 1
        if Input.is_action_pressed("move_right"):
            rotation_degrees.y = lerp_angle(rotation_degrees.y, atan2(0, 1), delta * 7)
            input[0] = -1
Run Code Online (Sandbox Code Playgroud)

由于某种原因,玩家几乎一动不动。在我将旋转度数设置为方向之前,但这并没有进行平滑的移动。任何帮助表示赞赏!

The*_*aot 6

获取输入

我们有更好的方法从输入中获取向量,让我们从这里开始:

var input := Vector3(
    Input.get_action_strength("move_left") - Input.get_action_strength("move_right"),
    0,
    Input.get_action_strength("move_forward") - Input.get_action_strength("move_backward")
)
Run Code Online (Sandbox Code Playgroud)

附录:这是您的订单。然而,请注意,我们通常会x向右生长,而在戈多中,向前是负数z。所以这是倒退的,我不知道这是不是故意的。

这里发生的事情为get_action_strength我们提供了输入的“强度”,它是 0 到 1 之间的数字。对于数字输入,它是 0 或 1,但对于模拟输入,它可能是介于两者之间的值。

然后,为了获得向量的一个分量,我采用输入使其为正值并减去负值。这给你一个从 -1 到 1 范围内的值。这也意味着我们没有得到单位向量,这取决于你在做什么,你可能想要也可能不想对其进行标准化。


接下来您需要 xz 平面上的角度。好吧,如果你只使用 a Vector2,那就很简单了。因此,让我们使用以下代码来代替上面的代码Vector2

var input := Vector2(
    Input.get_action_strength("move_left") - Input.get_action_strength("move_right"),
    Input.get_action_strength("move_forward") - Input.get_action_strength("move_backward")
)
Run Code Online (Sandbox Code Playgroud)

顺便说一句,在 Godot 3.4+ 和 Godot 4.0+ 中,您将能够使用Input.get_vector,这进一步简化了这一过程。

Vector3如果您需要它做其他事情,从中获得 a 应该不难:Vector3(input.x, 0, input.y)

现在获取角度:

var input_angle := input.angle()
Run Code Online (Sandbox Code Playgroud)

附录:您可能需要对角度进行操作才能使其在 3D 中可用。原因是角度是从x轴开始测量的,但向下看则没有旋转z。当我写这个答案时,我忽略了这一点。虽然我在这里测试了所有代码。如果没有运动,我没有注意到方向与运动方向不匹配。


平滑旋转

现在,我们要顺利地更改rotation_degrees.yinput_angle. 有多种方法可以做到这一点,这里有一些。

在他们任何一个之前。您需要将目标角度声明为字段,因此在没有输入时旋转不会重置。

var _target_angle:float
Run Code Online (Sandbox Code Playgroud)

每当有输入时,我们都会存储它:

var input := Vector2(
    Input.get_action_strength("ui_left") - Input.get_action_strength("ui_right"),
    Input.get_action_strength("ui_up") - Input.get_action_strength("ui_down")
)
if input.length_squared() > 0:
    _target_angle = input.angle()
Run Code Online (Sandbox Code Playgroud)

注意:我正在检查length_squared而不是length避免平方根。


lerp_角度

是的,您应该能够使用lerp_angle. 即使想,我也不提倡这样做。

我将继续添加7您拥有的神奇常数:

const _rotation_amount:float = 7.0
Run Code Online (Sandbox Code Playgroud)

然后你会这样做(我承认这是方便且简短的代码):

rotation.y = lerp_angle(rotation.y, _target_angle, delta * _rotation_amount)
Run Code Online (Sandbox Code Playgroud)

请注意我更改rotation_degreesrotation,这是因为您得到的角度以弧度为单位。使用起来也会有问题atan2如果您愿意,可以使用deg2radrad2deg进行转换。

使用这种方法,您对插值的控制非常有限。您没有指定它的旋转速度,也没有指定旋转需要多长时间。

请注意,weight线性插值的时间不是时间。您并不是在告诉 Godot“在这段时间内从这个值转到这个值”(您可以使用 来做到这一点Tween,见下文)。

另请注意,它将提高每帧值差异的或多或少稳定的比例。因此,它在开始时移动较多,而在结束时移动较少。也就是说,这根本不是线性的。它有减速作用。


角速度

如果我们要使用角速度,那么我们需要一个字段来保存角速度:

const _angular_speed:float = TAU
Run Code Online (Sandbox Code Playgroud)

这里TAU(即PI * 2)表示每秒旋转一次(每秒四次旋转为4 * TAU,每秒四分之一旋转为0.25 * TAU,您可以TAU将一个T瓮作为助记符关联起来)。

现在,我们要计算出角度差(最短路径),为此我们可以这样做:

var angle_diff := wrapf(_target_angle - rotation.y, -PI, PI)
Run Code Online (Sandbox Code Playgroud)

wrapf函数将“包装”范围内的值(在本例中-PI为 to PI),因此,如果值超出该范围,就好像它是从相反的边缘进入的(如果你说wrapf(14, 0, 10)你得到4,并且如果你说wrapf(-4, 0, 10)你得到6)。我希望这是有道理的。

理论上,要应用角速度,我们只需要符号:

rotation.y += delta * _angular_speed * sign(angle_diff)
Run Code Online (Sandbox Code Playgroud)

然而,我们不想过度。我们可以用以下方法解决这个问题clamp

rotation.y += clamp(delta * _angular_speed, 0, abs(angle_diff)) * sign(angle_diff)
Run Code Online (Sandbox Code Playgroud)

请注意,我已将 的符号与 的大小分开angle_diff。我将根据角速度变化的角度限制为 的大小angle_diff(因此它永远不会超过angle_diff,即它不会超调)。夹紧后,我贴上标志,使其转向正确的方向。

当然,如果您真的愿意,您也可以使用角加速度和减速度。


吐温

使用Tween节点是最强大的选项,而不是使用AnimationPlayer. 而且它也非常容易使用。

当然,我们需要定义一个Tween对象并将其添加为子对象:

var _tween:Tween

func _ready() -> void:
    _tween = Tween.new()
    add_child(_tween)
Run Code Online (Sandbox Code Playgroud)

这是因为Tween对象每帧都会继续逐渐改变旋转(所以我们不必担心)。为此,它需要持续存在并获取帧通知,因此它需要位于场景树中的某个位置。

在 Godot 4.0+ 中,您将能够get_tree().create_tween()返回 root Tween,因此您不必担心保留引用或将其添加到场景树中。

然后你可以这样做:

var input := Vector2(
    Input.get_action_strength("ui_left") - Input.get_action_strength("ui_right"),
    Input.get_action_strength("ui_up") - Input.get_action_strength("ui_down")
)
if input.length_squared() > 0:
    var rotation_time := 0.2
    _tween.interpolate_property(self, "rotation:y", rotation.y, input.angle(), rotation_time)
    _tween.start()
Run Code Online (Sandbox Code Playgroud)

别忘了打电话start嗯,在 Godot 4.0+ 中,所有补间都会默认自动启动。

正如你所看到的,代码告诉的值从(其当前值)的值Tween插值到 的值,并且我们指定一个旋转时间(我创建了一个变量,以便我可以给它一个名称) 。就是这样。yrotationrotation.yinput.angle()

在 Godot 4.0+ 中,interpolate_property新 API 已不再适用tween_property,您无需指定起始值。


哦,等等,我们可以指定您希望如何缓和这些值:

_tween.interpolate_property(
    self,
    "rotation:y",
    rotation.y,
    input.angle(),
    rotation_time,
    Tween.TRANS_QUAD,
    Tween.EASE_OUT
)
Run Code Online (Sandbox Code Playgroud)

请参阅wandomPewlin 的这份备忘单来选择您的缓动:

Tween 速查表,作者:wandomPewlin

我相信Tween.TRANS_QUADTween.EASE_OUT给你一些接近你所得到的东西lerp_angle,除了稳定。看,你还有更多选择!

我还要提到,您可以询问Tween它当前正在执行什么插值,也可以删除它们,这在某些情况下很有用。然而,对于像这样的简单情况,您不需要深入了解这一点。而且,AnimationPlayer如果您需要更复杂的东西,也可以。顺便说一句,是的,AnimationPlayer可以对子属性进行操作(比如,我在别处rotation.y有解释)