Camera Smoothing Not Working Properly

Godot Version

v4.2.stable.official [46dc27791]

Question

I’m trying to make the player camera smoothly move in front of the player if they accelerate. Unfortunately, if I try to use the move_toward command to make this work, the camera movement doesn’t happen.

VIDEO DEMONSTRATION (Too big to be put on post, sorry)
First clip shows the intended behavior without smoothing; second clip shows my failed attempt to add smoothing using move_toward.

Camera code:

extends Camera2D
@onready var player = %Player

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	if player.alive:
		if abs(player.velocity.x) > player.SPEED:
			#position.x = player.position.x + (100 * player.directionX)
			#position.x = move_toward(player.position.x, player.position.x + (100 * player.directionX), 50)
			position.y = player.position.y
		else:
			#position.x = player.position.x
			#position.x = move_toward(player.position.x + (100 * player.directionX), player.position.x, 50)
			position.y = player.position.y

NOTE: There are two lines for the camera’s X position that I intentionally commented out on this post.

The top line is the intended camera behavior without smoothing.
The bottom line is my failed attempt to smooth the camera’s movement using move_toward.

Camera2D has it’s own position smoothing properties you should try. I think your third parameter is too high, it will move 50 pixels per physics frame, you could multiply it by delta to get 50 pixels per second.

Tried enabling camera smoothing (Camera2D > Position Smoothing > Enabled = true). The camera lags behind the player as a result of this, which is something I don’t want.

I applied this change to my code:

if abs(player.velocity.x) > player.SPEED:
	position.x = move_toward(player.position.x, player.position.x + (100 * player.directionX), 50 * delta)
	position.y = player.position.y
else:
	position.x = move_toward(player.position.x + (100 * player.directionX), player.position.x, 50 * delta)
	position.y = player.position.y

Now, the camera settings are, strangely, reversed. It’s now in front of the player when not accelerating and right on the player when accelerating. On top of that, there’s still no smoothing. Here’s a video to show what I mean.

Aha one key issue is using the player position in both the from and to arguments. the first argument from should always be the camera’s position.x. I’m pretty confident the second argument can always be player.position.x + (100 * player.directionX) if directionX can be negative.

Since you are moving the camera from it’s position toward the front of the player.

position.x = move_toward(position.x, player.position.x + (100 * player.directionX), 50 * delta)
position.y = player.position.y
2 Likes

This line:

move_toward(player.position.x, player.position.x + (100 * player.directionX), 50)

Finds the position 50px ahead of the player, regardless of where the camera is located. The other version, as far as I can tell, does the same - the first two parameters are swapped, but the distance between them is 100 and we move 50px along that distance, so we end up with the same result.

In order to actually smooth anything, you have to take the camera position into account by starting the movement from the camera position:

move_toward(position.x, player.position.x + (100 * player.directionX), 50)

However, we still have a problem: The camera moves with a constant speed. Either that speed is faster than the player’s speed, in which case we’ll just match the player’s movement exactly (because move_toward doesn’t overshoot its target), or its slower, in which case the camera will fall behind.

There are a couple of common approaches to smooth camera movement:

  • Lerp smoothing, applied to the camera’s position
  • Giving the camera a speed parameter, and applying some kind of smoothing to that

The first could look like this:

position.x = lerp(position.x, player.position.x + (100 * player.directionX), delta * 10)

Here, you can adjust that last 10 up or down until you get a result you like. This will make the camera move fast when it’s far from the player, and slowly when it’s close.

The second approach could look something like this:

# pixels/second, should be faster than player speed
var max_speed = 1000

# acceleration in pixels/second
var acceleration = 10000

var current_speed = 0


func _physics_process(delta):
    var target_position = player.position.x + (500 * player.directionX)
    var distance_to_player = abs(target_position - position.x)
    var desired_speed = min(distance_to_player, max_speed) # calculate this however you like
    current_speed = move_toward(current_speed, desired_speed, acceleration*delta)
    position.x = move_toward(position.x, target_position, current_speed*delta)
    position.y = player.position.y

Note that I’ve set the camera’s target position further ahead than you have - that was the value that happened to work well with the scale of my scene. You might need to tweak basically all of the parameters here to fit your scene.

2 Likes

Here’s (hopefully it works) a video of the second approach from my post above - it looks kinda jittery here because I’m using shitty screen recording software, it’s much smoother on my actual screen.

Alright I’ve done a bit of tweaking with your code and am mostly satisfied with how it works.

Here is is so far:

extends Camera2D
@onready var player = %Player

# pixels/second, should be faster than player speed
var max_speed = 1200

# acceleration in pixels/second
var acceleration = 500000

var current_speed = 0
var target_position = 0

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	if player.alive:
		if abs(player.velocity.x) > player.SPEED:
			target_position = player.position.x + (2000 * player.directionX)
		elif abs(player.velocity.x) == 0:
			target_position = player.position.x
		else:
			target_position = player.position.x + (800 * player.directionX)
		var distance_to_player = abs(target_position - position.x)
		var desired_speed = min(distance_to_player, max_speed) # calculate this however you like
		current_speed = move_toward(current_speed, desired_speed, acceleration*delta)
		position.x = move_toward(position.x, target_position, current_speed*delta)
		position.y = player.position.y

And here’s a video of it in action.

I’ll reply again once I’ve fully fleshed out the code (I feel like the camera’s movement speed is a bit slow right now, especially when it hits a wall and changes direction)

Glad you could use it! One of the things you can fiddle with is how you calculate desired_speed - if you want the camera to move faster even when it’s close to the player, the formula I used probably isn’t great.

Here’s the code I ended up using:

extends Camera2D
@onready var player = %Player

# pixels/second, should be faster than player speed
var max_speed = 2000

# acceleration in pixels/second
var acceleration = 10000

var current_speed = 0
var target_position = 0

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	if player.alive:
		if abs(player.velocity.x) > player.SPEED:
			target_position = player.position.x + (1200 * player.directionX)
		elif abs(player.velocity.x) == 0:
			target_position = player.position.x
		else:
			target_position = player.position.x + (800 * player.directionX)
		var distance_to_player = abs(target_position - position.x)
		var desired_speed = min(distance_to_player, max_speed) # calculate this however you like
		current_speed = move_toward(current_speed, desired_speed, acceleration*delta)
		position.x = move_toward(position.x, target_position, current_speed*delta)
		position.y = player.position.y

I haven’t changed the desired_speed variable because I kinda don’t know how, but at the same time, it’s not that big of an issue and I’d rather work on other parts of the game. Might revisit later, though.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.