05.steering-the-ship-smoothly

Steering the ship smoothly

In this lesson, we will make our ship move much smoother using steering behaviors.

Steering behaviors are calculations that allow you to make characters and AI’s move smoothly with generally little code. They lead to a natural motion and smooth curves.

Currently, in every frame, we instantly change the velocity. The ship moves at maximum speed when the player presses a direction key. If the player releases the key, the ship stops.

We want the ship to accelerate and decelerate smoothly instead. To do so, we will use steering behaviors.

Steering behaviors are a category of algorithms we use to make objects move smoothly. Steering behaviors use vector calculations to make objects steer towards a target position.

The first steering algorithm we’ll see is the simplest: it only takes two or three lines of code and can improve movement immensely.

var desired_velocity := speed * direction
var steering_vector := desired_velocity - velocity
velocity += steering_vector * drag_factor

We’ll break it down below.

The follow steering behavior

This steering behavior gradually changes the ship’s velocity to move towards a target.

It works by calculating what we call the “desired velocity.” That’s the ship going instantly at the max speed in the input direction. We “compare” that with the current velocity.

We can represent the current and desired velocity vectors like so.

Imagine the ship is going towards the left, and the player presses the right key. How do we make the ship steer towards the right?

We start by calculating the difference between the two velocities. That’s what we call the steering vector.

To make the ship turn smoothly, instead of moving at the desired velocity, every frame, we add a small portion of the steering vector to the current velocity. Doing so makes the ship gradually steer towards the target velocity.

So far, we calculated the velocity with a single line of code.

func _process(delta: float) -> void:
    # ...
    velocity = direction * speed

Note: When we write #... in code listings, it represents the lines of code that came before or after the lines we are highlighting. Otherwise, if we included every line of code every time, important or new lines of code would become difficult to spot.

To get smooth movement, we need to replace the line that updates the ship’s velocity with three lines of code. We also add a new variable, the drag_factor:

var drag_factor := 0.1

func _process(delta: float) -> void:
    # ...
    var desired_velocity := max_speed * direction
    var steering_vector := desired_velocity - velocity
    velocity += steering_vector * drag_factor

    # ...

We first calculate the desired velocity: the ship moving at the maximum speed in the input direction.

    var desired_velocity := max_speed * direction

We then calculate the difference between the desired velocity and the current one.

    var steering_vector := desired_velocity - velocity

Note that the order in which you put the vectors matters a lot as vectors have a direction. The ship will move away from the target if you reverse the two terms.

    velocity += steering_vector * drag_factor

We add a portion of this steering vector to the current velocity on the third line. It changes the velocity gradually every frame, making our ship accelerate and decelerate depending on our input.

Without the drag_factor, we would instantly set our velocity to desired_velocity, making the ship always move at the maximum speed or not move at all.

To add only a portion of the steering vector every frame, you multiply the value steering_vector by a small number. Anywhere between 0.01 and 0.3 should work well.

The lower the value, the more inertia the ship will have: it will turn, accelerate, and decelerate much slower. Here is the ship with a drag_factor of 0.05.

The higher the value, the more reactive the ship will be: it will turn sharply, get to its maximum speed quickly, and stop in a split second. Here’s the ship moving with a drag_factor of 0.3.

Still, the motion will always feel much smoother than always moving at max speed with this steering algorithm.

Practice: Making the ship steer

In the Godot practices project, open the Making the ship steer.

The code

These steering equations are widely used in professional games. This lesson shows only the basic principle, but you will see in the course that we can push steering behaviors further to code impressive AI movement.

Here’s the complete code for the steering ship, including the boost mechanic you coded before.

extends Sprite

var boost_speed := 1500.0
var normal_speed := 600.0

var max_speed := normal_speed
var velocity := Vector2.ZERO
var drag_factor := 0.1

func _process(delta: float) -> void:
    var direction := Vector2.ZERO
    direction.x = Input.get_axis("move_left", "move_right")
    direction.y = Input.get_axis("move_up", "move_down")

    if direction.length() > 1.0:
        direction = direction.normalized()

    if Input.is_action_just_pressed("boost"):
        max_speed = boost_speed
        get_node("Timer").start()

    var desired_velocity := max_speed * direction
    var steering_vector := desired_velocity - velocity
    velocity += steering_vector * drag_factor
    position += velocity * delta
    rotation = velocity.angle()

func _on_Timer_timeout() -> void:
    max_speed = normal_speed