Skip to main content

Top-Down Movement with a Seek Steering Function

See also: Steering Behaviour

I sourced the original implementation of this from GDQuestโ€™s Top Down Movement tutorial. After a bit of extra research, this steering function seems to be a Seek steering function.

Typically this would be used to seek out some far off spot, but thereโ€™s nothing stopping us from that far off spot being the maximum speed in the direction the user specifies. Thatโ€™s how this works.

For deceleration, we simply seek to have our velocity vector be zero.

A problem with a basic implementation of this is that we get asymptotically close to our max speed or being stationary; that is, we will get really close to our max speed (or being completely stationary) but never hit that actual number. For acceleration that could be a problem, but for deceleration thatโ€™s almost guaranteed to be one. To combat this I added a small, arbitrary amount to the velocity each time (using the _adjust_velocity() function) to nudge the velocity in the right direction.

extends CharacterBody2D

## The maximum speed allowed, in pixels per second
@export var max_speed: float = 200.0
## This decides how much of the difference in current and target velocity we
## should be applying to the current velocity. A higher acceleration number
## means that we will apply a larger value. Valid values are between 0.0 and
## 1.0.[br]
## A value of [code]0.0[/code] means that you won't accelerate at all.
## A value of [code]1.0[/code] means that you'll accelerate instantaneously.
@export var acceleration_coefficient: float = .1


func _physics_process(delta: float) -> void:
var direction: Vector2 = _direction()
var target_velocity: Vector2 = direction * max_speed

# (target_velocity - velocity) gives us the difference between the top velocity and the
# current velocity. The bigger the difference between these, the faster we accelerate or
# decelerate. The closer we get to the target velocity, the slower we accelerate.
# When our direction is zero (i.e. no acceleration), then our target velocity is also zero.
velocity += (target_velocity - velocity) * acceleration_coefficient
_adjust_velocity(direction)
velocity = velocity.limit_length(max_speed)

print("Speed: %f" % velocity.length())
move_and_slide()


func _direction() -> Vector2:
return Input.get_vector("left", "right", "up", "down")


func _adjust_velocity(direction: Vector2) -> void:
# If we're accelerating, add 0.1 to the velocity just so that we eventually hit the max speed.
# Without this, we'll get closer and closer to max speed but never actually hit it.
if direction != Vector2.ZERO:
velocity += velocity.normalized() * 0.1
# If we're decelerating and we're not yet stationary, then subtract 0.1 from the current
# velocity, or at least get us to Vector2.ZERO
elif velocity != Vector2.ZERO:
velocity = velocity.normalized() * max(0, velocity.length() - 0.1)