Godot Version
4.6 dev 4
Question
Ask your question here! Try to give as many details as possible
Hi everyone. I’m currently implementing a top-down camera with lookahead in a 3d project, using the lerp()
function. The lookahead offset is obtained by getting the input direction, which is a Vector2, and multiply it with a distance multiplier (float).
Here is the code of CameraPoint.gd, that is the target the camera will follow :
class_name CameraPoint extends Marker3D
@export var target: Player
@export var distance_multiplier: float = 6
@export var responsiveness = 1.2
@export var max_offset = 5.0
func _ready() -> void:
self.set_as_top_level(true) # CameraPoint is a child of Player
CameraSignalBus.camera_position_updated.connect(update_y_position)
global_position.y = target.global_position.y
func _physics_process(delta: float) -> void:
var target_offset := action.move() * distance_multiplier # Offset // action.move() represents the input direction
var weight = 1 - exp(-responsiveness * delta)
target_offset.limit_length(max_offset)
# Apply offset
global_position.x = lerpf(global_position.x, target.global_position.x + target_offset.x, weight)
global_position.z = lerpf(global_position.z, target.global_position.z + target_offset.y, weight)
# Update y position when called
func update_y_position(delta: float) -> void:
global_position.y = lerpf(global_position.y, target.global_position.y, 5 * delta)
The problem i’m currently facing with implementation is that I don’t want CameraPoint to return to Player position when action.move().length() == 0. What I want to have is CameraPoint being always offseted in front of Player, even if input direction is equal to zero.
Note: I’m using the input vector instead of Player’s basis as it gives me a smoother result, especially when i rotate the player. Maybe I get something wrong 
Here is a video that showcases the problem:
Put the declaration of var target_offset outside of _physics_process(). Then only assign a new value if action.move() doesn’t return a zero vector.
var target_offset := Vector2(0, 1)
#...
func _physics_process(delta: float) -> void:
if not action.move().is_zero_approx()
target_offset = action.move() * distance_multiplier # Offset // action.move() represents the input direction
#...
Also, you should probably normalize the value you get from action.move() (if that isn’t the case already). Otherwise you’d have to implement some logic to prevent joystick inputs from moving the target point towards your player character when they get released.
(You won’t have to limit the target vector’s length then anymore, since it will always have a length equal to distance_multiplier.)
2 Likes
This is my new code.
class_name CameraPoint extends Marker3D
@export var target: Pirate
@export var distance_multiplier: float = 6
@export var responsiveness = 1.2
@export var max_offset = 5.0
var target_offset := Vector2(0, 1)
func _ready() -> void:
CameraSignalBus.camera_position_updated.connect(update_y_position)
global_position.y = target.global_position.y
target_offset *= -distance_multiplier
global_position.x = target.global_position.x + target_offset.x
global_position.z = target.global_position.z + target_offset.y
func _physics_process(delta: float) -> void:
if not action.move().is_zero_approx():
target_offset = action.move().normalized() * distance_multiplier
var weight = 1 - exp(-responsiveness * delta)
global_position.x = lerpf(global_position.x, target.global_position.x + target_offset.x, weight)
global_position.z = lerpf(global_position.z, target.global_position.z + target_offset.y, weight)
func update_y_position(delta: float) -> void:
global_position.y = lerpf(global_position.y, target.global_position.y, 5 * delta)
It is on the way but I get this strange result :
When I move right, it looks quite OK, but on the other directions it is quite a mess …
The weight variable and the position lerping weren’t supposed to be part of the if-statement, only assigning the new value to target_offset should be in there (though this doesn’t seem related to your new issue).
It looks like action.move() is still returning a vector to the right (could be a very short one) when there’s no input. This could either be some issue in action.move() or on the hardware (like joystick drift).
I’d start by trying to print the results of action.move() to see if this is actually causing the problem.
1 Like
Here it is ! I’ve finally figured out how to fix the problem ! Here is the code :
class_name CameraPoint extends Marker3D
@export var target: Pirate
@export var distance_multiplier: float = 2.0
@export var responsiveness = 1.0
@export var max_offset = 5.0
var target_offset := Vector2(0, 1)
var desired_target_offset := Vector2.ZERO
func _ready() -> void:
CameraSignalBus.camera_position_updated.connect(update_y_position)
global_position.y = target.global_position.y
# This code needs some small improvements
target_offset *= -distance_multiplier
global_position.x = target.global_position.x + target_offset.x
global_position.z = target.global_position.z + target_offset.y
func _physics_process(delta: float) -> void:
var weight = responsiveness * delta
if action.move().length() > 0.3:
# Convert player rotation angle into Vector2, so that target offset will always point in front of player's forward vector
var angle := target.model.rotation.y
var fx = -distance_multiplier * sin(angle)
var fy = -distance_multiplier * cos(angle)
var rotation_vector := Vector2(fx, fy)
# Set target offset
target_offset = Vector2(rotation_vector.x, rotation_vector.y).normalized() * distance_multiplier
# Lerp desired target offset to match target offset, without jitter
desired_target_offset = desired_target_offset.lerp(target_offset, weight)
# Add target offset to CameraPoint global position
global_position.x = target.global_position.x + desired_target_offset.x
global_position.z = target.global_position.z + desired_target_offset.y
func update_y_position(delta: float) -> void:
global_position.y = lerpf(global_position.y, target.global_position.y, 5 * delta)
And here is the result in video :
Thank you very much for your help @hyvernox, you’ve helped me resolve a longstanding issue, as there is so few tutorials on how to do it.
Again Thank you and I will not forget to credit you in my game
!
2 Likes