Steer spiral movement when velocity low

Godot Version

4.6

Question

New to Godot and game making in general.

I’m trying to make a spaceship character that can move forward and steer left and right.

I watched a couple of videos to understand how this works, and i put together something that I’m pretty happy about.

The only problem is that if velocity is low or the character is stationary and steer left or right is attempted, the character rotates v quickly and starts slowly making an expanding spiral until in reaches max_speed.

If forward movement is done before or during steering, steering works great.

I understand why this happens but i have no idea how to fix.

Could i get some help on fixing this and also some thoughts on how the movement code is written and if i should change something.

extends CharacterBody2D

# Connected sprites
@onready var character_sprite = $sprite_main_ship
@onready var debug_node = $CharacterDebugNode


# delta time
var global_delta = null

# acceleration parameter
@export var acceleration_normal: float = 300.0
@export var acceleration_boosted: float = 500.0

# max speed reachble by acceleration in normal and boosted navigation
@export var max_speed_normal: float = 500.0
@export var max_speed_boosted: float = 1000.0

# value for direction calculation of steer based on current position
@export var steer_angle: float = 85

var adjust_roation_angle: float = + PI / 2
var adjust_facing_angle: float = -90.0

# current navigation mode max speed 
# changes between max_speed_normal and max_speed_boosted
var current_acceleration: float = acceleration_normal
var current_max_speed: float = max_speed_normal

# helps transition from boosted to normal and normal to boosted
# otherwise looks to sudden
var transition_rate: float = 0.1

func set_movement():
	_adjust_movement_parameters()
	
	var directional_data: PackedVector2Array = _get_directional_data()
	var direction = directional_data.get(0)
	var front = directional_data.get(1)
	
	if direction != Vector2.ZERO:
		velocity += (direction) * _adjusted_delta_acceleration() 
	elif direction == Vector2.ZERO:
		# reduce lenght of our velocity vector by a linear amount each frame
		# have to to this because a scalar value
		# can not be subtracted from vector
		var new_velocity_length = max(0,velocity.length() - _adjusted_delta_acceleration())
		
		# reduces speed after letting go of direction
		velocity = ( velocity.normalized() * new_velocity_length )
	
	_adjuct_velocity(direction)
	velocity = velocity.limit_length(current_max_speed)
	
	if velocity != Vector2.ZERO:
		character_sprite.rotation = velocity.angle() + (adjust_roation_angle)

	# velocity_line, direction and forward facing point
	debug_node.debug_process(velocity, front, direction, false, true, true)

# adjust steer angle for steeper or tighter turns
# try not to above 90deg as it starts rotating inwards
func _get_directional_data() -> PackedVector2Array:
	
	# front declaration strictly for debugging
	# direction for movement and velocity
	var direction: Vector2 = Vector2.ZERO
	var front: Vector2 = Vector2.ZERO

	# fetch direction based on angle of current position
	if Input.get_action_strength("forward"):
		direction += Vector2.from_angle(character_sprite.rotation + deg_to_rad(adjust_facing_angle))
	if Input.get_action_strength("steer-left"):
		direction += Vector2.from_angle(character_sprite.rotation + deg_to_rad(adjust_facing_angle - steer_angle))
	if Input.get_action_strength("steer-right"):
		direction += Vector2.from_angle(character_sprite.rotation + deg_to_rad(adjust_facing_angle + steer_angle))

	front = Vector2.from_angle(character_sprite.rotation + deg_to_rad(adjust_facing_angle))
	
	return [direction, front]

# change acceleration and max_speed based on mode of navigation
func _adjust_movement_parameters():
	if Input.get_action_strength("boost") != 1:
		current_acceleration = lerp(current_acceleration, acceleration_normal, transition_rate)
		current_max_speed = lerp(current_max_speed, max_speed_normal, transition_rate)
	if Input.get_action_strength("boost"):
		current_acceleration = lerp(current_acceleration, acceleration_boosted, transition_rate)
		current_max_speed = lerp(current_max_speed, max_speed_boosted, transition_rate)

# used to add delta to acceleratation
# for easier usage so that this multiplication 
# doesnt have to be written over and over
func _adjusted_delta_acceleration() -> float:
	return current_acceleration * global_delta
	
# ! check speed for results, not velocity
# speed = sqrt(x^2, y^2)
func _adjuct_velocity(direction: Vector2) -> void:
	# if we accelerate, add 0.1 to the velocity so that we hit max speed
	# instead of always being just below it
	if direction != Vector2.ZERO:
		velocity += velocity.normalized() * .1
	# if we decelerate, subtract 0.1 from current velocity to get us to Vector2.ZERO
	elif velocity != Vector2.ZERO:
		velocity = velocity.normalized() * max(0, velocity.length() - .1)
		
	# ! interesting idea adding + 5.0 instead of - 0.1 causes a friction like effect 
	# where speed is semi maintained and velocity will be decreased by steering

func _physics_process(delta: float) -> void:
	# for easier use, careful monitorization of consequences needed
	global_delta = delta 

	# checks for right acceleration and speed configuration
	if current_max_speed < current_acceleration:
		print("current_max_speed must be higher or equal to current_acceleration!")
	
	# initialize our movement function the the process
	set_movement()
	move_and_slide()

Whatever videos you watched were very overcomplicated. This code will basically do the same thing.

class_name Player extends CharacterBody2D

@export_group("Ship Stats")
@export var max_speed_normal: float = 500.0
@export var max_speed_boosted: float = 1000.0
@export var thrust_power_normal: float = 300.0
@export var thrust_power_boosted: float = 500.0
@export var drag: float = 0.95
@export var rotation_sensitivity: float = 2.5

var max_speed: float = 500.0
var thrust_power: float = 500.0


func _physics_process(delta: float) -> void:
	if Input.is_action_pressed("boost"):
		max_speed = max_speed_boosted
		var action_strength := Input.get_action_strength("boost")
		var thrust_delta := thrust_power_boosted - thrust_power_normal
		thrust_power = thrust_power_normal + (action_strength * thrust_delta)
	else:
		max_speed = max_speed_normal
		thrust_power = thrust_power_normal

	var rotate_direction: float = Input.get_axis("steer-left", "steer-right")
	rotation += rotate_direction * rotation_sensitivity * delta
	
	var thrust := Input.get_action_strength("forward")
	var thrust_direction := -transform.y
	if thrust:
		velocity += thrust_direction * thrust * thrust_power * delta
	
	# Drag and Max Speed
	velocity *= drag
	velocity.limit_length(max_speed)
	
	move_and_slide()

Some thoughts/info:

  1. You’re using a CharacterBody2D. Let it handle the physics.
  2. You don’t need lerp() all over the place if you use delta effectively.
  3. Do not try to create a global_delta value. delta is calculated every frame. Any value you assign to global_delta is going to be a frame behind and therefore useless.
  4. get_axis() not only combines the two values for turning, but gives you a relative action strength for the two.
  5. drag determines how fast the ship slows down. Change it to 0.90 and it might match your slowdown code a little better.
  6. _physics_process() by convention should be at the top of your file, after _ready(). check out the GDScript Style Guide.
  7. Do not rotate the Sprite2D character sprite - rotate the ship. Most of your problems are coming from this decision.

Thank you very much for help and for giving me some much needed pointers.

1 Like