Tween speed problem when vaulting

Godot Version

4.4.1

Situation

Wanted functionality in my game for vaulting over stuff, but there weren’t any good tutorials on how to do it so I coded it myself.

It’s working okay so far, but the problem is that the vaulting speed doesn’t match up with the player’s speed at high velocities since I’m using tweening to move the player. If the player is moving slowly the tween speed matches up, but if we’re moving fast it feels a bit off since it effectively slows the player down for the duration of the vault.

I’m using the GoldGdt character controller as my base.

Question

Is there a better way to handle this?

Code
class_name Vault extends Node3D

@export_category("Components")
@export var raycastabove : RayCast3D # eye level raycast
@export var raycastbelow : RayCast3D # mid level raycast
@export var raycastshort : RayCast3D # feet level raycast
@export var raycastsurface : RayCast3D # raycast pointing directly down to get the floor we're vaulting onto
@export var body : GoldGdt_Body # character body
@export var camera_anim : AnimationPlayer
@export var thinobjecttarget : Node3D
@export var vaultsound : AudioStreamPlayer
var vel
var targetpoint : Vector3
var velocitystored = false
var unvaultable : bool
var vaulting : bool

func _physics_process(_delta: float) -> void:
	
	# force update the raycasts so we don't get "double vaulting" - even after the position tween below,
	# the raycasts were saying that we were still in front of the object, forcing another vault
	# even though we were already on top of or past the object
	for ray in [raycastabove, raycastbelow, raycastshort]:
		ray.force_raycast_update()
	
	if !body.is_on_floor() and (raycastbelow.is_colliding() or raycastshort.is_colliding()) and !raycastabove.is_colliding():
		# if either of the lower raycasts hit a node that is in the group "unvaultable", stop
		# (this is a fix for surf ramps and other sloped surfaces)
		for ray in [raycastbelow, raycastshort]:
			if ray.is_colliding() and ray.get_collider().is_in_group("unvaultable"):
				unvaultable = true
		
		if !unvaultable:
			
			vaulting = true
			vaultsound.play()
			var tween = create_tween()
			
			# we need to store the player velocity so we can give it back to them after the vault
			if !velocitystored: # only store the velocity once per vault
				vel = body.velocity
				velocitystored = true
				
			# if we do find floor under the point we're testing, vault up to the floor...
			if raycastsurface.is_colliding():
				targetpoint = raycastsurface.get_collision_point()
			# ...otherwise vault through to the other side of the short object we're jumping towards
			else:
				targetpoint = thinobjecttarget.global_position
			
			# the actual vaulting code itself
			body.velocity = Vector3.ZERO # stop the player on a dime
			body.collision_hull.disabled = true # disable the collision hull so we noclip through the thing
			# the raycast just gets the point on the floor and the character body would move to that point,
			# so we add a little offset to the Y position of the target point so the body doesn't
			# move halfway into the thing we're vaulting on
			tween.tween_property(body, "global_position", targetpoint + Vector3(0,1,0), 0.2)
			# wait for the tween to finish, then call on_tween_finished
			tween.connect("finished", on_tween_finished)
			# can't forget about the camera movement (it just tilts the camera a little.
			# could've probably used a tween here instead)
			camera_anim.play("vault")
	
	# clear the unvaultable flag when we hit the floor, so we can vault stuff again
	# if we previously hit a node that was unvaultable
	if body.is_on_floor():
		unvaultable = false

# give the player their velocity back after the vault but clear vertical velocity.
# this runs after we've vaulted
func on_tween_finished():
	body.velocity = vel
	body.velocity.y = 0
	body.falldamageflag = false
	body.collision_hull.disabled = false
	velocitystored = false
	vaulting = false
Video

Okay, I feel a bit dumb, but… Your tween has fixed duration, so it feels like it would always take the same time, no? If you want your tween to take less time or more time, you need to change the duration.

Yes, but the problem is how would one make that duration proportional to the velocity in a way that makes sense

What does “in a way that makes sense” mean? I would just make the tween take the time it would take at the player’s current velocity.

time = distance / speed

That makes sense to me and is the simplest answer. If you need something else, I feel like you have to come up with what makes sense.

Sorry yeah could’ve worded that better. By “in a way that makes sense” I mean it works well enough and isn’t “if player velocity higher than x, use tween time of y”.

I don’t really understand what you mean by time = distance / speed, as the distance and speed in this case would both be Vector3 while we need a float.

Would there be a way to calculate something like, say, “the higher the velocity, the closer to 0 the time”?

If you define the velocity needed for the smallest time an animation runs you can calculate the difference between velocities to see how much the time value should increase.

If you also define a lower bound - a velocity which gets the the longest time the animation runs - you could define a curve between the two points and define the time based on where the player velocity is on the curve.

Had some outside help and they suggested getting the percentage of the player’s speed from max velocity. Just using the percentage itself means we’d get a slower tween time if we’re going faster, so subtracting the percentage from 100% gives us what we need. However, it’s not strong enough, so I multiplied it by 3, then clamped the tweentime it to make sure it doesn’t go below 0.05 (as anything faster is, funnily enough, too fast)

# these seem pretty reasonable
var mintweentime = 0.5
var maxtweentime = 0.05

# get the percentage of our current velocity compared to max velocity
var ratio = round(Vector2(body.velocity.x, body.velocity.z).length()) / 100.0

# subtracting the percentage from 100% (so 0.22 -> 0.78) kind of works
# (as in we're now getting a higher tween time when moving slower)
# but it's not "strong" enough.
# e.g. 0.8 (minimum tween time) * 0.78 = new tween time of 0.624
# so we multiply it by 3 which makes it stronger
# e.g. 0.8 * (1 - 0.22 * 3) = new tween time of 0.272
var tweentime = mintweentime * (1 - (ratio * 3))
	
# the raycast just gets the point on the floor and the character body would move to that point,
# so we add a little offset to the Y position of the target point so the body doesn't
# move halfway into the thing we're vaulting on
tween.tween_property(body, "global_position", targetpoint + Vector3(0,1,0), clamp(tweentime, maxtweentime, mintweentime))

Works fine now. Thank you both for replying!