#How do I properly calculate velocity for a 3D platformer?

213 messages · Page 1 of 1 (latest)

vivid hull
#

I'm trying to create a 3D prototype where players can build up momentum by performing certain moves to increase their velocity. I've got a basic structure down, but I think my logic is wrong somewhere because it isn't working quite right, as seen in the video.

I don't know the best practices for handling velocity in this kind of scenario and I was hoping someone could show me how it's typically done.

# Handles acceleration.
func accelerate(target_velocity: float, delta: float) -> void:
    acceleration_velocity = velocity.move_toward((movement_direction * target_velocity), (acceleration * delta))
    velocity.x = acceleration_velocity.x
    velocity.z = acceleration_velocity.z

# Handles deceleration.
func decelerate(delta: float) -> void:
    acceleration_velocity = velocity.move_toward(Vector3.ZERO, (friction * delta))
    velocity.x = acceleration_velocity.x
    velocity.z = acceleration_velocity.z

# Handles horizontal movement when applicable.
func _allow_movement(delta: float) -> void:
    var current_speed: float = velocity.length()
    movement_direction = (transform.basis * Vector3(input_direction.x, 0.0, input_direction.y).normalized())
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

    if movement_direction:
        if current_speed < input_velocity:
            accelerate(input_velocity, delta)
        else:
            velocity.x = (movement_direction.x * current_speed)
            velocity.z = (movement_direction.z * current_speed)
        
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
    else:
        decelerate(delta)
gray plaza
#

it could be related to checking if current_speed < input_velocity
is input_velocity velocity.dot(movement_direction) or something like that?

#

actually it looks like thats so you don't slow down the character if they end up going faster than max speed

vivid hull
#

The conditional I'm using does seem to do that, but unfortunately it causes other issues as seen in the video clip. I'm not sure what I'm doing wrong here

vivid hull
upper oyster
#

Can you explain in short or I should check the whole code?

upper oyster
vivid hull
#

Basically what's happening here is that it breaks the deceleration. I don't lose speed at all and when I'm in the air I just gain tons of speed, as can be seen in the video

#

I'm wondering if it might be because of the way I'm handling the "current_speed" variable

upper oyster
vivid hull
#

That's how the built-in template does it, and all of the tutorials I've seen do it that way as well

#
movement_direction = (transform.basis * Vector3(input_direction.x, 0.0, input_direction.y).normalized())

You're talking about this ,right?

upper oyster
#

Yes

#

Wait I have to go quick I'll be back in about 30min, then I'll pick it up.

upper oyster
vivid hull
#

Gotcha

upper oyster
# vivid hull Gotcha

Alright, I have checked your code, can you explain what exactly is going wrong?

upper oyster
#

You don't lose speed when you stop giving it any input?

vivid hull
#

I do

upper oyster
#

What?

vivid hull
#

If I stop pressing the movement keys, I start to lose speed. the problem is that if I hold down the movement keys, I continue to gain speed even when I'm doing nothing more than running and jumping. That's not supposed to happen

#

I want to maintain any extra velocity I've gained, while still being able to rotate in the air

upper oyster
vivid hull
#
    if movement_direction:
        if current_speed < input_velocity:
            accelerate(input_velocity, delta)
        else:
            velocity.x = (movement_direction.x * current_speed)
            velocity.z = (movement_direction.z * current_speed)
            decelerate(delta)
upper oyster
#

Vector depends on direction and it mess up how it works a 1D value like a float magnitude works best because it has no direction.

#

And sorry for the delay. Had to take s leak

vivid hull
#
var current_speed: float = velocity.length()

Isn't that what I'm doing here?

upper oyster
vivid hull
#

Oh, hmm...

upper oyster
upper oyster
vivid hull
#
func accelerate(target_velocity: float, delta: float) -> void:
    acceleration_velocity = velocity.move_toward((movement_direction * target_velocity), (acceleration * delta))
    velocity.x = acceleration_velocity.x
    velocity.z = acceleration_velocity.z
func decelerate(delta: float) -> void:
    acceleration_velocity = velocity.move_toward(Vector3.ZERO, (friction * delta))
    velocity.x = acceleration_velocity.x
    velocity.z = acceleration_velocity.z
#

So what exactly do I clamp? I'm not sure I fully understand

upper oyster
#

Yes, use clampf instead of move_toward and make these float

#

And use that as magnitude in your movement_direction to multiply with

vivid hull
#

Wait, what? If I don't use move_toward then where does the acceleration come from?

upper oyster
#

new_mag = clampf(current_mag + acceleration*delta,0, target_mag)

upper oyster
#

Although keep in mind as you said you want to keep the same speed even if you change direction. I don't think it would look good if you make a 180 U-turn. Well but its upto you what you want

vivid hull
#

Well, I could make it so that it matches the rotation of the model, but I don't know how

upper oyster
upper oyster
vivid hull
#

Yes

upper oyster
#

What will you make follow your model's rotation?

vivid hull
#

Well I thought movement_direction, but I tried that and it didn't work

upper oyster
#

So you didn't understand what I recommended?

#

Let me re-write your code

vivid hull
#
func accelerate(target_velocity: float, delta: float) -> void:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    velocity.x = new_velocity

I've got this so far. Am I on the right track?

upper oyster
#
# Handles acceleration.
func accelerate(target_mag: float, delta: float) -> float:
    return clamp(velocity.length()+(acceleration * delta),0, target_mag)

# Handles deceleration.
func decelerate(delta: float) -> float:
    return clamp(velocity.length()-(friction * delta),0, velocity.length())

# Handles horizontal movement when applicable.
func _allow_movement(delta: float) -> void:
    var current_speed: float = velocity.length()
    movement_direction = (transform.basis * Vector3(input_direction.x, 0.0, input_direction.y).normalized())
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

    if movement_direction:
        if current_speed < input_velocity:
            velocity = movement_direction* accelerate(input_velocity, delta)
     
        
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
    else:
        velocity = movement_direction*decelerate(delta)
vivid hull
#

Ohhh

#

Alright let me try this out

upper oyster
#

Check it and see if there is any problem

#

Also this only accounts for same speed when changing direction not for any condition for changing speed. It will accelerate when is moving and de-accelerate when there is no input.

vivid hull
#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction = (transform.basis * Vector3(input_direction.x, 0.0, input_direction.y).normalized())
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

    if movement_direction:
          if current_velocity < input_velocity:
            velocity = (movement_direction * accelerate(input_velocity, delta))
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
    else:
        velocity = (movement_direction * decelerate(delta))
#

I noticed that you seem to only modify the entire velocity vector, and not its individual axes. Could that be part of the issue?

upper oyster
#

Aah, sorry. Some guy on the internet was bad mouthing Godot left him a good long comment.

vivid hull
#

This code doesn't set the velocity for x and z individually, it sets it for the entire velocity vector, including y

upper oyster
#

Whats your value for acceleration, friction and input_velocity(your target velocity)

upper oyster
#

Even in the video y component is always 0

vivid hull
#

input_velocity is 5.0, ground_friction is 40.0, air_friction is 20.0

upper oyster
#

Thats like 5m/s

#

With accleration of 40 that takes like 1/8th of a second to reach

#

Ahh acceleration whats that

vivid hull
#

I wanted the acceleration to be barely noticeable. It's less about speed and more about feel

#

Instantly going from zero to full speed feels bad, but I still want movement to be pretty snappy

upper oyster
vivid hull
#

Okay but the issue here is that while moving I can't change direction. It just keeps me moving in the direction I started in

upper oyster
#

Wait a min

#

The part where you use rotated on movement_direction, that part is making sure that your movement is always in forward with respect to the camera. Comment that part out

#

No wait.

#

That won't work

#

1 min

#

I think something is wrong here.

#

The part in your code

#
    movement_direction = (transform.basis * Vector3(input_direction.x, 0.0, input_direction.y).normalized())
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

Did these both lines came with the template

#

Hello?

vivid hull
#

No, only the first one

upper oyster
#
    movement_direction = (to_global( Vector3(input_direction.x, 0.0, input_direction.y))- global_position).normalised()

Comment the both lines and replace it with this

vivid hull
#

So that makes me immediately start moving backwards, and I can no longer control Mega Man

vivid hull
#

Alright

#

Still doesn't let me change direction while moving. Only while standing still

upper oyster
#

Wait

#

Show your new code

vivid hull
#
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction = (to_global(Vector3(input_direction.x, 0.0, input_direction.y)) - global_position).normalized()

  if movement_direction:
    if current_velocity < input_velocity:
            velocity = (movement_direction * accelerate(input_velocity, delta))
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
    else:
        velocity = (movement_direction * decelerate(delta))
upper oyster
#

I am an idiot, it needs local coords

#
    movement_direction =  Vector3(input_direction.x, 0.0, input_direction.y).normalised()

Comment the both lines and replace it with this

#

Use this

vivid hull
#

Same issue, also now my controls are reversed

upper oyster
#

You're node might be directly oriented

#

In local scene in which axis is it facing?

vivid hull
#

-Z

upper oyster
#

Yeah, use the original line but only the first one, not the rotated one

vivid hull
#

Same issue, can't change direction while moving

upper oyster
#

Yeah it would be hard if I don't open my setup wait

#

1 more time

#
    movement_direction =  Vector3(input_direction.y, 0.0, -input_direction.x).normalised()
#

Try this

#

Is the right, left still inverted?

vivid hull
#

Every single direction is wrong. The left key goes up for instance

#

Are you trying to decouple the movement direction from the camera orientation?

upper oyster
#

I am trying to base it over the orientation of character's facing direction.

#

Wait let me think. I'll try to rewrite your code again

vivid hull
#

Alright

#
func _process(_delta: float) -> void:
    input_direction = (Vector2(Input.get_axis("move_left_digital","move_right_digital"), Input.get_axis("move_forward_digital", "move_backwards_digital")))

    if input_direction.is_zero_approx():
        input_direction = Input.get_vector("move_left_analog", "move_right_analog", "move_forward_analog", "move_backwards_analog", stick_deadzone)
upper oyster
#

I won't need this

#

I am trying to figure out the rotation part

#

Gimme 5 min

#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction =  Vector3(input_direction.x, 0.0, input_direction.y).normalized()
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

    if movement_direction:
          if current_velocity < input_velocity:
            velocity = (Vector3.FORWARD * accelerate(input_velocity, delta))
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
    else:
        velocity = (Vector3.FORWARD * decelerate(delta))
#

Try

#

I wish I had my setup with me. I could have tested all this on my end before sharing.

vivid hull
#

That's alright, I appreciate it

upper oyster
#

Aah wait

#

Instead of rotating the model

#

Rotate the characterbody

#

Also I am assuming your characterbody is not a child of your camera?

vivid hull
#

No, the CharacterBody is the root of the scene

#

The camera is a child of the CharacterBody

upper oyster
#

I was thinking everything wrongly

#

Let me rethink

#

How is your camera moving? Is your camera itself moving or is it moving the characterbody

vivid hull
#

The new code has the same issue

#

I move the camera with the mouse. It only moves with the mouse

upper oyster
upper oyster
vivid hull
#

Wait, sorry I think I misread your question

#

The camera is moving around the character

upper oyster
#

Lemme rethink everything

#

Your structure is completely different than I had in mind

vivid hull
#

Oh is this not how the character scene is typically handled?

#

It's crazy how many different ways there are to handle things

upper oyster
#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction =  Vector3(input_direction.x, 0.0, input_direction.y).normalized()
    movement_direction = movement_direction.rotated(Vector3.UP, camera.rotation.y)

    if movement_direction:
          if current_velocity < input_velocity:
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z), (rotation_speed * delta))
         velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* accelerate(input_velocity, delta))
    else:
        velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* decelerate(delta))
#

Try this

#

Try it, and I'll be back in 10min

vivid hull
#

As you can see, I'm afraid it's even more broken than before

upper oyster
#

😭

#

Let me focus more

#

I'll see what I can do

#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction =  Vector3(input_direction.x, 0.0, -input_direction.y).normalized()
    if movement_direction:
          if current_velocity < input_velocity:
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.x, movement_direction.z)+camera.rotation.y, (rotation_speed * delta))
         velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* accelerate(input_velocity, delta))
    else:
        velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* decelerate(delta))
#

I seriously forgot to change the main part

#

Ohh yeah the axis might be inverted

#

Where is the part where you get your key inputs

vivid hull
#

I posted it up above, it's in another script

upper oyster
upper oyster
upper oyster
#

The left right might be inverted, but check if everything else is fine.

vivid hull
#

So the velocity is never 0, it's always moving even when I'm not making any inputs

#

The directions are still incorrect as well

upper oyster
#

Which ones?

#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction =  Vector3(input_direction.x, 0.0, -input_direction.y).normalized()
    if movement_direction:
          if current_velocity < input_velocity:
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.z, movement_direction.x)+camera.rotation.y, (rotation_speed * delta)
         velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* accelerate(input_velocity, delta))
    else:
        velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* decelerate(delta)
vivid hull
#

Left goes up, right goes down, up goes left, down goes right

upper oyster
vivid hull
#

Now all of the directions are reversed

upper oyster
#

Whats the order

vivid hull
#

Left, Right, Forward, Backward

upper oyster
#

And what it should be?

vivid hull
#

Ah, sorry that is what it should be

upper oyster
#

And what it is?

vivid hull
#

Down is Up, Up is Down, Left is Right, and Right is Left

upper oyster
#
func _process(_delta: float) -> void:
    input_direction = (Vector2(Input.get_axis("move_right_digital","move_left_digital"), Input.get_axis("move_backwards_digital", "move_forward_digital")))

#

Use this

#

This should be the right order

#

Then remove the - in the input_movement line

#

X positive is right, x negative is left, z negative forward and z positive is backward

#

And axis takes +,-

vivid hull
#

I did it based off of the order in the method hint

#

I'll try your order

upper oyster
#

What the

#

Let me check

#

Aah yes

#

I was wrong

#

Damn

#

I really need my setup

#

Change the order

#

Left, right, forward and back word

#
func accelerate(target_velocity: float, delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity + (acceleration * delta), 0.0, target_velocity)
    return new_velocity
func decelerate(delta: float) -> float:
    var current_velocity: float = velocity.length()
    var new_velocity: float = clampf(current_velocity - (friction * delta), 0.0, current_velocity)
    return new_velocity
func _allow_movement(delta: float) -> void:
    var current_velocity: float = velocity.length()
    movement_direction =  Vector3(input_direction.x, 0.0, input_direction.y).normalized()
    if movement_direction:
          if current_velocity < input_velocity:
        model.rotation.y = lerp_angle(model.rotation.y, atan2(movement_direction.z, movement_direction.x)+camera.rotation.y, (rotation_speed * delta)
         velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* accelerate(input_velocity, delta))
    else:
        velocity = Vector3(cos(model.rotation.y), 0, sin(model.rotation.y))* decelerate(delta)
vivid hull
#

Huh it's moving me diagonally now

upper oyster
#

What the....

#

How?

#

I just changed 1 sign

vivid hull
#

Hold on, I think I screwed up somewhere

#

Okay the diagonal movement is fixed but the original issue remains

upper oyster
#

Which is

vivid hull
#

The directions are not in the correct order, and I still can't change directions while moving

upper oyster
#

However there is no part which restricts that

vivid hull
#

Well I think I'm going to take a break from this for now. I've just about run out of time

#

I do appreciate all of your effort though

upper oyster
#

Is the model's facing direction and the movement same or not?

vivid hull
#

Negative, they are not

upper oyster
#

I see. I think you should take some rest. Next time if I get the time to help you I'll try to be on my setup.

vivid hull
#

Yeah that would be great

#

Thanks again for your help

upper oyster
#

I am sure however they'll assist you will be much better.