1

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.

Theraot
  • 31,890
  • 5
  • 57
  • 86
display
  • 35
  • 3

2 Answers2

1

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.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Thank you so much for such a detailed explanation! I tried to make a script that you wrote but the formulas somehow fell off and looks strange ( https://imgur.com/spMdyHi ) And why so many action to transform the rotation? really there are no easier solutions? its so weird – display Apr 17 '23 at 16:37
  • @ortyrxz Yes, there is an easier solution, I'll post another answer. – Theraot Apr 17 '23 at 18:03
  • @ortyrxz I have added a new answer. It should give you control over the animation you want without all the math. - Looking at the video, perhaps you had to work with the local position instead of the global. - Anyway, I have come up with some helper methods to do this kind of stuff, but what they do isn't what you do in luau. I would not even use lerp for damping anymore. – Theraot Apr 17 '23 at 18:42
0

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.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Thanks again so much for the answer! And yes, animating through a node is the way that I had in luau, only I animated the entire model and then the principle turns out to be the same, I dont know why I didnt think of it right away. Ive also been developing in roblox studio for 4 years, so you dont have to write everything like that, I understand :) It is very similar – display Apr 21 '23 at 06:43