如何使Vector3在行走时进行插值处理并带有旋转?

huangapple go评论72阅读模式
英文:

how i can make walking vector3 lerping with rotation?

问题

I'm trying to translate this code from luau in Roblox to GDScript in Godot.

这段代码从Luau语言翻译到Godot中的GDScript:

This WalkingOffset is multiplied by weapon CFrame in luau:

这个WalkingOffset在Luau中被乘以武器的CFrame

WalkingOffset = WalkingOffset.lerp(
	Basis(Vector3(
		0.045 * abs(sin(tick * my_speed * 0.6)),
		-0.02 * sin(tick * my_speed * 0.6),
		clamp(pos.x * 15, -0.08, 0.08)),
	10)
		* Vector3(
			0.035 * abs(sin(tick * my_speed * 0.55)),
			0.01 * sin(tick * my_speed * 0.6) - abs(pos.x / 1.75),
			0
		),
	0.3)

This WalkingOffset is multiplied by weapon global_position. But it looks bad and almost nothing moves.

这个WalkingOffset被乘以武器的global_position。但是它看起来效果不佳,几乎没有移动。

英文:

I'm trying to translate this code from luau in Roblox to GDScript in Godot.

This WalkingOffset is multiplied by weapon CFrame in luau:

local abs=math.abs
local sin=math.sin
local clamp=math.clamp

local tick=100000

--render every frame
tick+=deltaTime
if running then 
  local totalspeed=16


  WalkingOffset=WalkingOffset:Lerp(
	CFrame.Angles(
	        .045*abs(sin(tick*totalspeed*.6)),
		-.02*sin(tick*totalspeed*.6),
	        clamp(r(pos.X*15),-.08,.08)
	)
	   *CFrame.new(
		   035*abs(sin(tick*totalspeed*.55)),
		   .01*sin(tick*totalspeed*.6),
		   0)
	   ,.3)
else
   WalkingOffset=WalkingOffset:Lerp(CFrame.new(),.1)
end

in roblox this looks like this
https://i.stack.imgur.com/U9Fa5.jpg

I tried this code in Godot:

WalkingOffset=WalkingOffset.lerp(
	Basis(Vector3(
		.045*abs(sin(tick*my_speed*.6)),
		-.02*sin(tick*my_speed*.6),
		clamp(pos.x*15,-.08,.08)),
	10)
		*Vector3(
			.035*abs(sin(tick*my_speed*.55)),
			.01*sin(tick*my_speed*.6)-abs(pos.x/1.75),
			0
		)
	,.3)

This WalkingOffset is multiplied by weapon global_position. But it looks bad and almost nothing moves.

答案1

得分: 1

初始观察

首先,根据Roblox文档,CFrame是一个“坐标框架”,描述了一个3D位置和方向。在Godot 3中,这相当于Transform,在Godot 4中相当于Transform3D。请注意,Basis没有位置。

其次,我找不到r函数的作用。

第三,我认为WalkingOffset是一个CFrame,因为代码对其进行了CFrame的插值。因此,在Godot 3中应该是一个Transform,在Godot 4中应该是Transform3D

当然,请停止模仿


翻译

CFrame.Angles方法的文档说明创建了一个以弧度为单位的欧拉角的CFrame,旋转按Z、Y、X的顺序应用。

因此,这里的参数是以弧度为单位的欧拉角:

    .045*abs(sin(tick*totalspeed*.6)),
    -.02*sin(tick*totalspeed*.6),
    clamp(r(pos.X*15),-.08,.08)
)

现在,我不知道Roblox中轴向的定向和惯用性是什么,也不知道这是否重要。我也不知道你是否使用的是Godot 3还是Godot 4,所以我会给你一个灵活的解决方案:

Godot 3或Godot 4

    Vector3.FORWARD,
    .045*abs(sin(tick*totalspeed*.6))
).rotated(
    Vector3.UP,
    -.02*sin(tick*totalspeed*.6)
).rotated(
    Vector3.RIGHT,
    clamp(r(pos.X*15),-.08,.08)
)

注意:我不知道r是什么,我将其保留为原样。

在这里,你可以根据需要更改顺序或其他任何内容,使其符合你想要的。重点不是更改角度的顺序,而是应用它们的旋转顺序。

当然,你可以将其作为Godot 3中的Transform或Godot 4中的Transform3D的一部分。例如:

Godot 3

    Basis(...),
    Vector3.ZERO
)

Godot 4

    Basis(...),
    Vector3.ZERO
)

其中:

  • Basis(...) 是如上所示构造的基础...或者如下所示,因为我会给你一些备选方案。
  • Vector3.ZERO 是位置。我会解释到这一点。

这些是特定版本的备选方案:

Godot 3(按Y、X、Z顺序应用旋转)

    Basis.IDENTITY.rotated(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        )
    ),
    Vector3.ZERO
)

Godot 4(按Y、X、Z顺序应用旋转)

    Basis.from_euler(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        )
    ),
    Vector3.ZERO
)

此外,Godot 4允许你指定顺序:

Godot 4(按Z、Y、X顺序应用旋转)

    Basis.from_euler(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        ),
        EULER_ORDER_ZYX
    ),
    Vector3.ZERO
)

不,这与更改参数的顺序不同。更改参数的顺序会改变每个角度应用到哪个轴,而不是它们应用的顺序。


接下来,你将第一个变换与另一个变换相乘,看起来是这样的:

    035*abs(sin(tick*totalspeed*.55)),
    .01*sin(tick*totalspeed*.6),
    0
)

这是一个只有位置的Transform。变换的乘法将它们组合在一起。所以我们可以直接将位置写入Godot中的Vector3.ZERO

Godot 3

    Basis (...),
    Vector3(
        035*abs(sin(tick*totalspeed*.55)),
        .01*sin(tick*totalspeed*.6),
        0
    )
)

Godot 4

    Basis (...),
    Vector3(
        035*abs(sin(tick*totalspeed*.55)),
        .01*sin(tick*totalspeed*.6),
        0
    )
)

现在你想对一个变换进行插值。Godot没有直接进行这样操作的方法。它之所以常见,仅仅是因为人们不断地复制这种代码。我知道我正在让你复制,所以我希望你至少能从中获得一些理解。

相反,我们将将变换分解为旋转和位置(在这种情况下仅有这两种)。

  • 旋转:我们从欧拉角创建了一个Basis。但这样不容易插值,所以我们将首先从中获取一个四元数。
  • 位置:我们有一个位置向量。我们可以直接进行插值。

所以,我们要这样做:

Godot 3或Godot 4

var a_origin := WalkingOffset.origin
var b_basis := Basis(...)
var b_origin := Vector3(...)

其中Basis(...) 是前面创建的`Basis

英文:

Initial observations

First of all, according to Roblox documentation CFrame is a "coordinate frame" which "describes a 3D position and orientation". That is a Transform in Godot 3 or a Transform3D in Godot 4. Note that Basis does not have position.

Second, I can't find what the function r does.

Third, I believe WalkingOffset was a CFrame because the code interpolates it with CFrames. And thus it should be a Transform in Godot 3 or Transform3D in Godot 4.

And of course, please stop the cargo cult.


Translating

The method CFrame.Angles is documented to create a CFrame from Euler angles in radians, with the rotations applied in Z, Y, X order.

Thus, the arguments here are Euler angles in radians:

CFrame.Angles(
    .045*abs(sin(tick*totalspeed*.6)),
    -.02*sin(tick*totalspeed*.6),
    clamp(r(pos.X*15),-.08,.08)
)

Now, I don't know what is the axis orientation and handiness in Roblox, and I don't know if it matters. I also don't know if you are using Godot 3 or Godot 4, so I'll start with a flexible solution for this:

Godot 3 or Godot 4

Basis.IDENTITY.rotated(
    Vector3.FORWARD,
    .045*abs(sin(tick*totalspeed*.6))
).rotated(
    Vector3.UP,
    -.02*sin(tick*totalspeed*.6)
).rotated(
    Vector3.RIGHT,
    clamp(r(pos.X*15),-.08,.08)
)

Note: I don't know what r is, I'm leaving it as is.

There you can change the order or whatever you need to make it match what you want. The point is not changing the order of the angles, but the order in which their rotations are applied.

You, of course, would make it part of a Transform in Godot 3 or a Transform3D in Godot 4. For example:

Godot 3

Transform(
    Basis(...),
    Vector3.ZERO
)

Godot 4

Transform3D(
    Basis(...),
    Vector3.ZERO
)

Where:

  • Basis(...) is the basis constructed as shown above... Or as shown below, because I'll give you some alternatives.
  • Vector3.ZERO is the position. I'll get to that.

These are version specific alternatives:

Godot 3 (with rotations applied in Y, X, Z order)

Transform(
    Basis.IDENTITY.rotated(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        )
    ),
    Vector3.ZERO
)

Godot 4 (with rotations applied in Y, X, Z order)

Transform3D(
    Basis.from_euler(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        )
    ),
    Vector3.ZERO
)

Furthermore, Godot 4 lets you specify the order:

Godot 4 (with rotations applied in Z, Y, X order)

Transform3D(
    Basis.from_euler(
        Vector3(
            .045*abs(sin(tick*totalspeed*.6)),
            -.02*sin(tick*totalspeed*.6),
            clamp(r(pos.X*15),-.08,.08)
        ),
        EULER_ORDER_ZYX
    ),
    Vector3.ZERO
)

No, it is not the same as changing the order of the arguments. Changing the order of the arguments would change to which axis each angle is applied, not in which order they are applied.


Next, you multiply the first transformation by another one that looks like this:

*CFrame.new(
    035*abs(sin(tick*totalspeed*.55)),
    .01*sin(tick*totalspeed*.6),
    0
)

That is a Transform with only a position. The multiplication of transformations composes them. So we could just write the position directly in that Vector3.ZERO we had in Godot:

Godot 3

Transform(
    Basis (...),
    Vector3(
        035*abs(sin(tick*totalspeed*.55)),
        .01*sin(tick*totalspeed*.6),
        0
    )
)

Godot 4

Transform3D(
    Basis (...),
    Vector3(
        035*abs(sin(tick*totalspeed*.55)),
        .01*sin(tick*totalspeed*.6),
        0
    )
)

And now you want to lerp a transformation. Godot does not have a method to do that directly. The only reason it is common is because people keep copying this kind of code. I know I'm enabling you to copy, so I hope at least you get some understanding from this.

Instead, we will decompose the transform it into rotation and position (which is all we have in this case).

  • Rotation: We created a Basis from Euler angles. But that does not interpolate well, instead we are going to start by getting a quaternion from it.
  • Position: We have a position vector. We can interpolate directly.

So, we are going to do this:

Godot 3 or Godot 4

var a_basis := WalkingOffset.basis
var a_origin := WalkingOffset.origin
var b_basis := Basis(...)
var b_origin := Vector3(...)

Where Basis(...) is the Basis created as before, and Vector3(...) is the position vector as before.

Then we can get quaternions from the Basis:

Godot 3

var a_quat := a_basis.get_rotation_quat().normalized()
var b_quat := b_basis.get_rotation_quat().normalized()

Godot 4

var a_quat := a_basis.get_rotation_quaternion().normalized()
var b_quat := b_basis.get_rotation_quaternion().normalized()

Then we can interpolate:

Godot 3 or 4

var new_quat := a_quat.slerp(b_quat, 0.3)
var new_basis := Basis(new_quat)
var new_origin := a_origin.lerp(b_origin, 0.3)

And with that we build the interpolated transform:

Godot 3

var new_transform := Transform(new_basis, new_origin)

Godot 4

WalkingOffset = Transform3D(new_basis, new_origin)

On the other branch of the code we find CFrame.new() which should be an identity transform, which is interpolated. So you can directly use Quat.IDENTITY (Godot 3) or Quaternion.IDENTITY (Godot 4) and Vector3.ZERO.


By the way, are you sure you want to apply the transformation to the position? If you only change the position of the weapon it can only possibly move the weapon. Not rotate it. If that is what you want, I believe we could have made something much simpler, avoiding all the rotation bushiness.


Other notes

I want to point out that you could create a quaternion directly instead of dealing with all the business of Euler angles to Basis to quaternion. I do not know what is the intended rotation, but I assure it can be expressed as axis-angle:

var b_axis := Vector3(0.0, 0.0, 1.0)
var b_angle := 0.0

From which you can build a quaternion:

Godot 3

var b_quat := Quat(b_axis, b_angle)

Godot 4

var b_quat := Quaternion(b_axis, b_angle)

I don't know if the axis should change with time (tick), but the angle should. Something like this: wrapf(tick*my_speed*some_other_factor, -PI, PI).

You could experiment with that and try to find something that gives you a desirable result.


I also want to point out that you could compute the quaternion between the ones you have:

Godot 3 or Godot 4

var diff_quat := a_quat.inverse() * b_quat

convert that to axis angle:

Godot 3 or Godot 4

# make sure the rotation angle is the short way around
if diff_quat.w < 0:
    diff_quat = -diff_quat

diff_quat = diff_quat.normalized()
var diff_axis := Vector3(diff_quat.x, diff_quat.y, diff_quat.z).normalized()
var diff_angle := 2 * acos(diff_quat.w)

And then scale the angle. Which enables another way to interpolate quaternions:

Godot 3

var new_quat := a_quat * Quat(diff_axis, diff_angle * 0.3)

Godot 4

var new_quat := a_quat * Quatternion(diff_axis, diff_angle * 0.3)

Here 0.3 is the interpolation weight.


Just in case, know that this is what lerp does:

func lerp(a, b, t:float):
  return (1 - t) * a + t * b;

When t is zero, it should return a, when t is one it should return b. But your t is always 0.3. So as far as the math is concerned, it will never get to b (in practice it will, because eventually the difference is too small to be represented in floating point numbers).

Also, since it is 0.3 every frame, and the time between frame can vary, this is frame rate dependent.

答案2

得分: 0

所以,放弃数学,放弃露夫。让我们按照Godot的方式来做。请注意,这不是你要求的。

你将添加一个AnimationPlayer。*我会假设你将它添加为你正在编写脚本的节点的子节点。*我们想要添加一些动画。我们将在代码中调用它们,但首先我需要带你制作这些动画。


在Godot编辑器的底部,通过动画面板添加动画。对于AnimationPlayer,你点击“动画”按钮,然后选择“新建”,并为动画命名。

我们要添加的动画如下:

  • 一个动画是武器挥动。我会称之为“挥动”。
  • 另一个是武器不挥动。我会称之为“静止”。

我们希望它们的持续时间相同。要更改动画的持续时间,你在动画播放器的右上角键入它。我会使用一秒钟(这是默认值,所以你不需要更改它)。

还要将动画设置为循环。在你输入持续时间的右侧有一个带有两个箭头的图标,形成循环。点击它使动画成为循环播放。


要向动画添加关键帧,在打开动画面板的情况下,选择要添加关键帧的时间。然后选择你要动画化的对象,并将其移动到你想要的位置(或更改你想要动画化的任何属性),然后点击出现在检查器旁边的关键帧图标。

第一次点击对象的给定属性的图标时,Godot会问你是否要为其创建轨道。你要动画化的每个对象的每个属性都会在动画中有一条轨道。

对于位置,Godot还会询问是否要使用贝塞尔曲线轨道。这将为x、y、z创建单独的轨道。贝塞尔曲线将使运动更加流畅,但有点复杂,所以我建议你首先尝试不使用它。如果你不使用贝塞尔曲线,可以在轨道右侧选择插值的类型,你需要选择“连续”和“立方体”

现在,对于“挥动”动画,你将设置一些关键帧,类似于这样:

  • 在0.0秒时的左上位置。
  • 在0.25秒时的中下位置。
  • 在0.5秒时的右上位置。
  • 在0.75秒时的中下位置。
  • 在1.0秒时的左上位置。

对于“静止”动画,我们只想要武器居中。所以只有两个关键帧,一个在开始时,一个在结束时。

你可以将动画面板底部的时间捕捉设置为0.25,以使此操作更容易(默认值为0.1,因此它不会在0.25和0.75上)。注意,开始和结束是相同的,因此它循环。还可以选择一个关键帧,然后选择一个时间,在轨道的上下文菜单中选择“复制关键帧”。


现在,我们希望Godot在这些动画之间进行插值。为此,我们再次点击“动画”按钮。这次选择“编辑过渡”。Godot将打开“交叉混合动画时间”窗口,在这里我们可以指定动画之间的混合时间。你可以将其设置为半秒钟左右。你可能希望回来调整它。

还有一件事,将“静止”动画设置为自动开始。这是看起来像一个A在一个粗箭头内的图标(类似于|A>)的动画面板顶部的图标。


最后,当玩家角色不移动时,你可以这样播放“静止”动画:

$AnimationPlayer.play("stay")

否则,你要播放“挥动”动画:

$AnimationPlayer.play("swing")

如果告诉AnimationPlayer播放已经播放的相同动画,它不会产生变化。所以不要担心检查播放了哪个动画,而且你可以通过告诉它在每一帧播放来逃脱。

Godot会根据你在“交叉混合动画时间”窗口中设置的时间处理动画之间的过渡,因此它不应突然开始或停止挥动。


注意:我想你想要为其他事情使用其他动画,例如射击武器。你可能会将其完全分开,例如通过对中间节点进行动画化。也就是说,将你的场景树制作成这样:

PlayerCharacter
└ Pivot
  └ Weapon

并对Pivot进行动画化。如果你需要动画同时播放,你可能会为不同部分有不同的AnimationPlayer(每个AnimationPlayer只播放一个动画)。但要注意,它们不会共同工作以移动相同的属性。

对于更复杂的情况,你可以查看AnimationTree,它可以播放多个动画 - 在相同的AnimationPlayer上的相同对象上同时播放它们,并让它们混合在一起。但由于这对于你在这里做的事情并非绝对必要,我将其排除在此答案之外。

英文:

So, screw math, screw luau. Let's do this the Godot way. Please notice that this is not what you asked.

You are going to add an AnimationPlayer. I'll assume you add it as a child of the node where you are writing the script. And we want to add a couple animations. We will get to calling them from code, but first I need to walk you through making the animations.


You add the animations on the Animation panel at the bottom of Godot editor. With the AnimationPlayer you click the "Animation" button and then select "new" and write a name for the animation.

The animations we are going to add are these:

  • One animation is for the weapon swinging. I'll call it "swing".
  • And one is for the weapon not swinging. I'll call it "stay".

We want them to be of the same length. To change the duration of an animation you type it on the top right of the animation player. I'll work with one second (which is the default, so you don't need to change it).

Also set the animations to loop. On the right of the where you type the duration there is an icon with two arrows making a loop. Click it to make the animation a looping one.


To add key frames to the animation, with the Animation panel open, select the time you want to add the keyframe. Then select the object you want to animate and move it to where you want (or change whatever properties you want to animate), and click the key icon that appears in the inspector next to the property.

The first time you click the icon for a given property of an object Godot will ask you if you want to create a track for it. Each property of each object you animate will have a track in the animation.

For positions Godot will also ask if you want to use a bezier curves track. This will create separate tracks for x, y, z. The bezier curves will allow us to make the motion smoother, but it is somewhat more complicated, so I suggest you try without it first. If you are not using bezier curves you can select the kind of interpolation on the right of the track, you want "continuous" and "cubic".

Now, for the "swing" animation you are going to set some key frames, something like this:

  • Top left position at 0.0 seconds.
  • Bottom middle position at 0.25 seconds.
  • Top right position at 0.5 seconds.
  • Bottom middle position at 0.75 seconds.
  • Top left position at 1.0 seconds.

And for the "stay" animation we just want the weapon centered. So only two key frames, one at the start and one at the end.

You can set the time snap at the bottom of the Animation panel to 0.25 to make this easier (the default is 0.1, so it won't land on 0.25 and 0.75).

Notice that the starts and ends on the same one so it loops. It also possible to select a key frame, then select a time, and on the context menu of the track select "Duplicate Key(s)".


Now, we want Godot to interpolate between these animations. To do that we click on the Animation button again. This time select "Edit transition". Godot will open the "Cross-Blend Animation Times" window, where we can specify the blend times between animations. You can se it to something like half a second. You might want to come back to this and tweak it.

One more thing, set the "stay" animation to auto start. It is the icon that looks like an A inside of a thick arrow (something like |A>) on the top of the Animation panel.


And finally, when the player character is not moving you play the "stay" animation like this:

$AnimationPlayer.play("stay")

Otherwise you want to play the "swing" animation:

$AnimationPlayer.play("swing")

If you tell the AnimationPlayer to play the same animation it is already playing, it changes nothing. So do not worry about checking which animation it was player, plus you can get away by telling it to play it every frame.

Godot will handle the transition between the animations according to the time you set in the "Cross-Blend Animation Times" window, so it should not start and stop swinging suddenly.


Note: I suppose you want to use other animations for other things, for example for shooting the weapon. You might have that entirely separated for example by animating an intermediary pivot node. That is, making your scene tree something like this:

PlayerCharacter
└ Pivot
  └ Weapon

And animating the Pivot. You might also have different AnimationPlayers for different parts if you need the animations to play at the same time (each AnimationPlayer only plays one animation). But be aware that they will not work together to move the same property.

For more complex scenarios you would look into AnimationTree which is capable of playing multiple animations - of the same AnimationPlayer over the same objects at the same time and have them blend together. But since that is not explicitly necessary for what you are doing here, I'll leave it out of this answer.

huangapple
  • 本文由 发表于 2023年4月17日 12:34:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76031741.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定