How to keep extra impulse applied to characterbody3d?

Godot Version

4.2.2.stable

Question

Hello! So I added a “rocket jump” mechanic of sorts to my game, which adds a vector to an extra velocity variable, and then the velocity adds the extra velocity to itself. (so basically, extraVelocity += Vector3(blah,blah,blah), then velocity += extraVelocity)

What I want to happen is, when rocket jumping, you keep the impulse from the rocket jump but are also able to change directions and whatnot, but what actually happens is if you rocket jump, the vertical impulse gets applied, but all horizontal velocity still depends on whether you are holding a directional key or not, and has 0 horizontal velocity applied from the explosion.

My current code to handle the velocity and extra velocity looks like this (in _physics_process)

if direction:
	velocity.x = direction.x * speed
	velocity.z = direction.z * speed
else:
	velocity.x = move_toward(velocity.x, 0, speed)
	velocity.z = move_toward(velocity.z, 0, speed)

velocity += extraVelocity

extraVelocity.x = move_toward(extraVelocity.x, 0, speed)
extraVelocity.y = move_toward(extraVelocity.y, 0, speed)
extraVelocity.z = move_toward(extraVelocity.z, 0, speed)

and the portion of the explosion code that applies impulse like this

func _physics_process(delta):
	for o in get_overlapping_bodies():
		if o is Player:
			var force = (o.global_position - global_position).normalized()
			force *= 3.25
			o.add_extra_velocity(force)

Any help is appreciated. Thanks!

Based on the code I’m looking at, it should work.

Are you sure that you’re actually adding any horizontal extraVelocity?
Where is the explosion object located relative to the player?
Also, it would be nice to see the entire code for your movement script. Context is important.

I’ll check if it’s adding any horizontal extraVelocity real quick!

The explosion object is located wherever the rocket projectile fired from the rocket launcher hits.

Player script:

@export var DEFAULT_SPEED = 5.0
@export var RUNNING_SPEED = 7.5
@export var JUMP_VELOCITY = 4.5

var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

var speedMultiplier := 1.0
var jumpMultiplier := 1.0
var gravityMultiplier := 1.0
var extraVelocity := Vector3.ZERO

var running := false

@onready var head: Node3D = $Head
@onready var camera: Camera3D = $Head/Camera3D
@onready var hud: CanvasLayer = $HUD

func _input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(-event.relative.x * Options.sensitivity)
		camera.rotate_x(-event.relative.y * Options.sensitivity)
		camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-89), deg_to_rad(89))

func _physics_process(delta):
	gravity *= gravityMultiplier
	speed *= speedMultiplier
	
	if not is_on_floor():
		velocity.y -= gravity * delta

	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY * jumpMultiplier

	var input_dir := Input.get_vector("left", "right", "forward", "backward")
	var direction := (head.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	running = Input.is_action_pressed("run")
	if !running: speed = DEFAULT_SPEED 
	else: speed = RUNNING_SPEED
	
	if direction:
		velocity.x = direction.x * speed
		velocity.z = direction.z * speed
	else:
		velocity.x = move_toward(velocity.x, 0, speed)
		velocity.z = move_toward(velocity.z, 0, speed)

	velocity += extraVelocity

	extraVelocity.x = move_toward(extraVelocity.x, 0, speed)
	extraVelocity.y = move_toward(extraVelocity.y, 0, speed)
	extraVelocity.z = move_toward(extraVelocity.z, 0, speed)

	move_and_slide()

func add_extra_velocity(vel: Vector3):
	extraVelocity += vel

After checking, the explosion DOES add horizontal extraVelocity, but still gets stopped if you’re not holding any directional keys

1 Like

Hmm… I suspect it has something to do with these lines:

	extraVelocity.x = move_toward(extraVelocity.x, 0, speed)
	extraVelocity.y = move_toward(extraVelocity.y, 0, speed)
	extraVelocity.z = move_toward(extraVelocity.z, 0, speed)

This code moves extraVelocity towards (0, 0, 0).
Perhaps you would want to only do that if you’re grounded (i.e. is_on_floor())?

Another approach

Your velocity system is actually a little tricky.
velocity is a variable inherited from CharacterBody3D and its value is modified not only in your code but also in the call to move_and_slide().

I think what you should do is modify add_extra_velocity() to be this instead:

func add_extra_velocity(vel: Vector3):
    velocity += vel

…and then remove the extraVelocity altogether.
I’ve made a similar system before where I had two “extraVelocity” variables; one for each movement sub-system. The more I used that system, the more I realized how much of a headache it creates - having to select and administer multiple velocities whenever I wanted to modify the player’s velocity.

I propose that you use a simpler system.

Sidenote

gravity *= gravityMultiplier
speed *= speedMultiplier

I reckon you shouldn’t be doing this. If you were to modify gravityMultiplier, your gravity would very quickly spiral to infinity. The same would happen to speed if not for the fact that you override the change through:

    running = Input.is_action_pressed("run")
    if !running: speed = DEFAULT_SPEED 
    else: speed = RUNNING_SPEED

I’m still analyzing your code to try and come up with an exact solution. Bear with me.

1 Like

Here are two ways I think you can fix it.

Simple fix

Remove the following extraVelocity lines

velocity += extraVelocity  # Permanently adds extraVel every frame (not good)
                           # The x- and z-components are reset but the y is never.
                           # The y-velocity will keep increasing as long
                           # as extraVel is more than zero

extraVelocity.x = move_toward(extraVelocity.x, 0, speed)
extraVelocity.y = move_toward(extraVelocity.y, 0, speed)
extraVelocity.z = move_toward(extraVelocity.z, 0, speed)

…and replace them with

velocity += Vector3(extraVelocity.x, 0f, extraVelocity.y)

# Reset the extra velocity once the ground is hit
if is_on_floor() and extraVelocity.length() > 0:
    extraVelocity = Vector3.ZERO

…and add this to the add_extra_velocity-function:

    # Since the y-component of extraVelocity is no longer added
    # in _physics_process(), it must be added here.
    # (in add_extra_velocity)
    velocity.y += vel.y

System Redesign

Here’s an alternate solution. This is what I usually do.

Code

I’m gonna use the variable names from your pre-existing script.

if direction:
    # Movement (w. acceleration)
    velocity.x = direction.x * speed * delta
    velocity.z = direction.z * speed * delta

    # The y-velocity and xz-velocity
    var yVel = velocity.y
    var xzVel = velocity.slide(Vector3.UP)

    # The maximum velocity in the XZ-plane
    # Here, the limit can be higher than "speed" if the player is not on the floor
    var speedLimit = speed if is_on_floor() else xzVel.length()

    # Clamp the XZ-velocity to the speed limit
    velocity = xzVel.limit_length(speedLimit)
    # Restore the y-velocity
    velocity.y = yVel
else:
    # Deceleration (for grounded and airborne)
    if is_on_floor():
        velocity.x = move_toward(velocity.x, 0, speed * delta)
        velocity.z = move_toward(velocity.z, 0, speed * delta)
    else
        # Insert deceleration code for the air here.
        # I suspect you want the player to keep flying even if they let go?
        # In that case, just delete the else-statement

Let me know if either works for you.
I always feel a little iffy with writing code like this without testing it. Might not work.

1 Like

It worked!!! Thank you so much!!!

1 Like