Issue with transition for a CharacterBody3D between acceleration and deceleration (3D)

Godot Version

4.3

Question

I’m currently trying to implement a way to make the player accelerate and decelerate slower when on an icy surface. Currently, the code looks like this:

var input_dir = Input.get_vector("A", "D", "W", "S")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if(direction and running):
		if(iced): 
			velocity.x = move_toward(velocity.x, direction.x * RUN_SPEED, icyFriction)
			velocity.z = move_toward(velocity.z, direction.z * RUN_SPEED, icyFriction)
		elif(!is_on_floor()):
			velocity.x = move_toward(velocity.x, direction.x * RUN_SPEED, AIR_FRIC)
			velocity.z = move_toward(velocity.z, direction.z * RUN_SPEED, AIR_FRIC)
		else:
			velocity.x = direction.x * RUN_SPEED
			velocity.z = direction.z * RUN_SPEED
	elif(direction and !running):
		if(iced): 
			velocity.x = move_toward(velocity.x, direction.x, icyFriction)
			velocity.z = move_toward(velocity.z, direction.z, icyFriction)
		elif(!is_on_floor()):
			velocity.x = move_toward(velocity.x, direction.x * SPEED, AIR_FRIC)
			velocity.z = move_toward(velocity.z, direction.z * SPEED, AIR_FRIC)
		else:
			velocity.x = direction.x * SPEED
			velocity.z = direction.z * SPEED
	elif(iced):
		velocity.x = move_toward(velocity.x, 0, icyFriction)
		velocity.z = move_toward(velocity.z, 0, icyFriction)
	elif(!is_on_floor()):
		velocity.x = move_toward(velocity.x, 0, AIR_FRIC)
		velocity.z = move_toward(velocity.z, 0, AIR_FRIC)
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
		velocity.z = move_toward(velocity.z, 0, SPEED)
		running = false

However, when I release a button while facing between any 90 or 45 degree angle, it causes me to go sideways at a fairly consistent angle, being more noticeable at higher velocities.
It is not caused by the deceleration itself, but instead it is something to do with when the acceleration stops. Is there a better method of implementing acceleration?

Use global_transform.basis

Changing transform.basis to global_transform.basis doesn’t seem to solve it.
Here’s a video of the issue for some clarification, also showcasing how it becomes more extreme the closer the player is to a 22.5 degree rotation along Y in any direction. https://streamable.com/i3v7bj

Do you know what

I bet it’s related to these.

velocity.x = move_toward(velocity.x, 0, icyFriction)
velocity.z = move_toward(velocity.z, 0, icyFriction)

You would actually want to do a vector based reduction. What I think happens is one axis hits zero first. Then you get the drift. I think the implementation of the generic float move towards is not great for multiple axes movement.

You should do this for each movement handling.

velocity.move_toward(Vector3( 0, velocity.y, 0 ), <delta>)

This will be proportional to the length of the vector. (I threw y velocity in there to try and preserve gravity/jumping)

I tried switching out the lines you mentioned for velocity.move_toward(Vector3( 0, velocity.y, 0 ), <delta>), however that did not solve the issue and made the player slide indefinitely.
Just to be sure I implemented this correctly, I changed:

velocity.x = move_toward(velocity.x, 0, icyFriction)
velocity.z = move_toward(velocity.z, 0, icyFriction)

to:

velocity.move_toward(Vector3(0, velocity.y, 0), icyFriction)

Yes that looks right, and okay, I assume the icy value is a positive value. The delta parameter is the change in velocity. So if icy is small it may take a while for it to get to zero.

Even if I set the delta to something high like 60, the player still slides indefinitely.

it works fine for me

	if(direction and running):
		if(iced): 
			velocity = velocity.move_toward(Vector3(direction.x * RUN_SPEED, velocity.y, direction.z * RUN_SPEED), icyFriction)
		elif(!is_on_floor()):
			velocity = velocity.move_toward(Vector3(direction.x * RUN_SPEED, velocity.y, direction.z * RUN_SPEED), AIR_FRIC)
		else:
			velocity.x = direction.x * RUN_SPEED
			velocity.z = direction.z * RUN_SPEED
	elif(direction and !running):
		if(iced):
			velocity = velocity.move_toward(Vector3(direction.x , velocity.y, direction.z), icyFriction)
		elif(!is_on_floor()):
			velocity = velocity.move_toward(Vector3(direction.x * SPEED, velocity.y, direction.z * SPEED), AIR_FRIC)
		else:
			velocity.x = direction.x * SPEED
			velocity.z = direction.z * SPEED
	elif(iced):
		velocity = velocity.move_toward(Vector3(0, velocity.y, 0), icyFriction)
	elif(!is_on_floor()):
		velocity = velocity.move_toward(Vector3(0, velocity.y, 0), AIR_FRIC)
	else:
		velocity = velocity.move_toward(Vector3(0, velocity.y, 0), SPEED)
		running = false

Oh, it was an issue on my part. I forgot to put velocity = before the rest. Thanks for all your help!