Help With Movement Issue

Godot Version

3.5.3.stable

Movement Question!

So, I have been having this small issue with my movement. If I go from a fast movement state to a slower movement state it looks like the character jitters. So I’m trying to figure out something a little more smooth. Here is what I have at the moment:

if Input.is_action_pressed("Thrust"):
		velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)
else:
		velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

My thought is to remove velocity/limit_length() and working the FRICTION variable into the first equation to slow the character down naturally, but I’m not great with mathematics and can’t figure it out :sweat_smile:

Any help would be greatly appreciated!

Edit: Here is a video for reference. It’s a tiny detail, but it’s been bothering me like no other.

Is there any other part of the code that edits velocity or SLOW_MOVEMENT? Seems like this should slowly reduce the speed, you are only limiting the length while thrust is active so it shouldn’t suddenly slow down like that.

I have it set up so that I can go from MEDIUM/FAST/BOOST_MOVEMENT to SLOW_MOVEMENT. The transition is a bit jarring.

Could you share some of that code? Sounds like it may be the culprit!

Sure! Here it is:

export (int) var FRICTION = 100
export (int) var SLOW_MOVEMENT = 200
export (int) var MEDIUM_MOVEMENT = 300
export (int) var FAST_MOVEMENT = 400
export (int) var BOOST_MOVEMENT = 500
export (int) var FORWARD_SPEED = 200
export (int) var BACKWARD_SPEED = 110

var moveState = SLOWMOVE
var velocity = Vector2.ZERO

enum {
	SLOWMOVE,
	MEDIUMMOVE,
	FASTMOVE,
	BOOSTMOVE1,
	BOOSTMOVE2,
	BOOSTMOVE3
}

func slowMove(delta):
	if Input.is_action_pressed("Reverse"):
		velocity += Vector2(0, BACKWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)
		
	if Input.is_action_pressed("Thrust"):
		velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)
		animationState.travel("SlowMovement")

	else:
		animationState.travel("Idle")
		velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
		
	if Input.is_action_just_pressed("Speed_Up"):
		moveState = MEDIUMMOVE

	if Input.is_action_just_pressed("Boost") && cooldownTimer.is_stopped():
		moveState = BOOSTMOVE1

func mediumMove(delta):
	if Input.is_action_pressed("Reverse"):
		velocity += Vector2(0, BACKWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)

	if Input.is_action_pressed("Thrust"):
		velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(MEDIUM_MOVEMENT)
		animationState.travel("MediumMovement")

	else:
		animationState.travel("Idle")
		velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

	if Input.is_action_just_pressed("Speed_Up"):
		moveState = FASTMOVE

	if Input.is_action_just_pressed("Speed_Down"):
		moveState = SLOWMOVE

	if Input.is_action_just_pressed("Boost") && cooldownTimer.is_stopped():
		moveState = BOOSTMOVE2

func fastMove(delta):
	if Input.is_action_pressed("Reverse"):
		velocity += Vector2(0, BACKWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)

	if Input.is_action_pressed("Thrust"):
		velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(FAST_MOVEMENT)
		animationState.travel("FastMovement")

	else:
		animationState.travel("Idle")
		velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

	if Input.is_action_just_pressed("Speed_Down"):
		moveState = MEDIUMMOVE

	if Input.is_action_just_pressed("Boost") && cooldownTimer.is_stopped():
		moveState = BOOSTMOVE3

func boostMove1(delta):
	velocity += Vector2(0, -BOOST_MOVEMENT).rotated(rotation) * delta
	velocity = velocity.limit_length(BOOST_MOVEMENT)
	animationState.travel("Boost")
	if boostLengthTimer.is_stopped():
		moveState = SLOWMOVE

func boostMove2(delta):
	velocity += Vector2(0, -BOOST_MOVEMENT).rotated(rotation) * delta
	velocity = velocity.limit_length(BOOST_MOVEMENT)
	animationState.travel("Boost")
	if boostLengthTimer.is_stopped():
		moveState = MEDIUMMOVE

func boostMove3(delta):
	velocity += Vector2(0, -BOOST_MOVEMENT).rotated(rotation) * delta
	velocity = velocity.limit_length(BOOST_MOVEMENT)
	animationState.travel("Boost")
	if boostLengthTimer.is_stopped():
		moveState = FASTMOVE

func move():
	velocity = move_and_slide(velocity)

func _physics_process(delta):
	match moveState:
		
		SLOWMOVE:
			slowMove(delta)
			
		MEDIUMMOVE:
			mediumMove(delta)
			
		FASTMOVE:
			fastMove(delta)
			
		BOOSTMOVE1:
			boostMove1(delta)
			
		BOOSTMOVE2:
			boostMove2(delta)
			
		BOOSTMOVE3:
			boostMove3(delta)
	
	move()

Seems like pressing the “Speed_Down” action will immediately change your state, thus immediately change how the velocity is limited. maybe you can use the same move_toward with friction if the velocity is greater than the limit, instead of setting it directly? Assuming your FORWARD_SPEED is less than FRICTION.

velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
if velocity.length() > SLOW_MOVEMENT:
	velocity = velocity.move_toward(velocity.limit_length(SLOW_MOVEMENT), FRICTION * delta)

There’s a lot of repeated code here, especially with boostMove1, 2, and 3, seems like a seperate “max speed” variable would perform better.

onready var max_speed: int = SLOW_MOVEMENT
var animation_prefix = "Slow"

enum {
	MOVE,
	BOOSTMOVE,
}


# Increasing or decreasing max_speed by set intervals, Could use
# `move_toward` on max_speed to smoothly transition to any target speed
func increaseSpeed() -> void:
	if max_speed == SLOW_MOVEMENT:
		max_speed = MEDIUM_MOVEMENT
		animation_prefix = "Medium"
	elif max_speed == MEDIUM_MOVEMENT:
		max_speed = FAST_MOVEMENT
		animation_prefix = "Fast"


func decreaseSpeed() -> void:
	if max_speed == MEDIUM_MOVEMENT:
		max_speed = SLOW_MOVEMENT
		animation_prefix = "Slow"
	elif max_speed == FAST_MOVEMENT:
		max_speed = MEDIUM_MOVEMENT
		animation_prefix = "Medium"


func normalMove(delta: float):
	if Input.is_action_pressed("Reverse"):
		velocity += Vector2(0, BACKWARD_SPEED).rotated(rotation) * delta
		velocity = velocity.limit_length(SLOW_MOVEMENT)
		
	if Input.is_action_pressed("Thrust"):
		velocity += Vector2(0, -FORWARD_SPEED).rotated(rotation) * delta
		# Using `max_speed` to dynamicly limit length, this does not
		# fix sudden stops when using "Speed_Down" but reduces states
		velocity = velocity.limit_length(max_speed)
		animationState.travel(animation_prefix + "Movement")

	else:
		animationState.travel("Idle")
		velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

	if Input.is_action_just_pressed("Speed_Up"):
		increaseSpeed()
	elif Input.is_action_just_pressed("Speed_Down"):
		decreaseSpeed()

	if Input.is_action_just_pressed("Boost") && cooldownTimer.is_stopped():
		moveState = BOOSTMOVE


func boostMove(delta):
	velocity += Vector2(0, -BOOST_MOVEMENT).rotated(rotation) * delta
	velocity = velocity.limit_length(BOOST_MOVEMENT)
	animationState.travel("Boost")
	if boostLengthTimer.is_stopped():
		moveState = MOVE


func _physics_process(delta):
	match moveState:
		
		MOVE:
			normalMove(delta)
			
		BOOSTMOVE:
			boostMove(delta)
	
	move()
1 Like

This is exactly what I was looking for! With this is can remove velocity = velocity.limit_length(max_speed) and have just have FRICTION do all the work.


Thank you for optimizing the code! I will have to study this to refine my abilities :sweat_smile: