How can I set lerp for Player Movement?

Godot Version

4.3

Question

Hi! I’m following a Godot tutorial and running into a problem. In my code, the player doesn’t move continuously when I hold the “forward” button. If I press it briefly, the player moves only a little, and if I press it again, they move a little more. However, holding the button doesn’t result in continuous movement.

Also, if I set the acceleration to anything above 2, like 2.1 or 2.001, the player starts accelerating forever and eventually moves out of the world.

Could anyone help me figure out what’s going wrong?

extends CharacterBody3D

@onready var player_mesh = get_node("Knight")
@onready var camera_h = get_node("cameraroot/horizontal")

@export var gravity: float = 9.8
@export var jump_force: int = 9
@export var walk_speed: int = 3
@export var run_speed: int = 10

var idle_node_name: String = "Idle"
var walk_node_name: String = "Walk"
var run_node_name: String = "Run"
var jump_node_name: String = "Jump"
var attack1_node_name: String = "Attack1"
var death_node_name: String = "Death"

var is_attacking: bool
var is_walking: bool
var is_running: bool
var is_dying: bool

var direction: Vector3
var horizontal_velocity: Vector3
var aim_turn: float
var movement: Vector3
var vertical_velocity: Vector3
var movement_speed: int
var angular_acceleration: float
var acceleration: float
var just_hit: bool

func _ready() -> void:
	pass

func _input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		aim_turn = -event.relative.x * 0.015
	if event.is_action_pressed("aim"):
		direction = camera_h.global_transform.basis.z

func _physics_process(delta: float) -> void:
	var on_floor = is_on_floor()
	if !is_dying:
		if !on_floor:
			vertical_velocity += Vector3.DOWN*gravity*2*delta
		else:
			vertical_velocity = Vector3.DOWN*gravity/10
		if Input.is_action_just_pressed("jump") and (!is_attacking) and on_floor:
			vertical_velocity = Vector3.UP*jump_force
		angular_acceleration = 10
		movement_speed = 0
		acceleration = 2.00
		var h_rot = camera_h.global_transform.basis.get_euler().y
		if (Input.is_action_just_pressed("forward") || Input.is_action_just_pressed("backward") || Input.is_action_just_pressed("left") || Input.is_action_just_pressed("right")):
			direction = Vector3(Input.get_action_strength("left") - Input.get_action_strength("right"),
								0,
								Input.get_action_strength("forward") - Input.get_action_strength("backward"))
			direction = direction.rotated(Vector3.UP, h_rot).normalized()
			if Input.is_action_pressed("sprint") and (is_walking):
				movement_speed = run_speed
				is_running = true
			else:
				is_walking = true
				movement_speed = walk_speed
		else:
			is_walking = false
			is_running = false
		if Input.is_action_pressed("aim"):
			player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, camera_h.rotation.y, delta*angular_acceleration)
		else:
			player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(direction.x, direction.z) - rotation.y, delta * angular_acceleration)
		
		if is_attacking:
			horizontal_velocity = horizontal_velocity.lerp(direction.normalized() * 0.1, acceleration)
		else:
			horizontal_velocity = horizontal_velocity.lerp(direction.normalized() * movement_speed, acceleration)
		velocity.z = horizontal_velocity.z
		velocity.y = vertical_velocity.y
		velocity.x = horizontal_velocity.x
		move_and_slide()
		

Use it instead

Input.is_action_pressed("forward")

Remove the “just”
Regarding the acceleration, try to use something like this:

var lerp_speed = 10.0 #or 5.0
whatever_property = whatever_propety.lerp(target_value, delta * lerp_speed)

@Monday already explained it about lerp (before me), but I also showed, actually I not had read the question properly at first :sweat_smile:.

1 Like

The weight for lerp() (your acceleration) should be between 0 and 1. And 1 means it will reach it’s target in 1 frame, so it should be less then that if you want acceleration. More than 1 will make it overshoot it’s target value. Usually it’s used with delta.

If you use delta as weight for lerp it will reach the taret value in one second. 0.5 * delta will be half as fast, so it’ll reach the target value in 2 seconds, ect…

1 Like

Potential Solutions:

Limit Acceleration:

Clamp the acceleration value: Ensure that acceleration remains within a reasonable range, such as between 0 and 2.00.
Use a maximum velocity: Set a maximum speed for the player and prevent them from exceeding it.
Adjust Acceleration Based on Movement:

Reduce acceleration when moving towards a target: If the player is moving towards a specific point or enemy, gradually decrease acceleration as they get closer.
Increase acceleration when moving away from a target: Increase acceleration when the player is moving away from a target, encouraging them to catch up.
Implement a Deceleration Mechanism:

Apply a deceleration force: When the player releases the movement keys, introduce a deceleration force to slow them down gradually.
Use a friction coefficient: Simulate friction between the player and the ground to gradually reduce their speed.
Revised Code Example:

Here’s a revised version of the _physics_process function with a limited acceleration and a deceleration mechanism:

GDScript
func _physics_process(delta: float) → void:
# … (other code)

# Limit acceleration
acceleration = clamp(acceleration, 0.0, 2.0)

# Deceleration
if !Input.is_action_pressed("forward") && !Input.is_action_pressed("backward") && !Input.is_action_pressed("left") && !Input.is_action_pressed("right"):   

    horizontal_velocity = horizontal_velocity.lerp(Vector3.ZERO, 0.2)

# ... (rest of the code)

Hey I have no life and restructured your code (your problem should be fixed):

extends CharacterBody3D

#region references
@export_group("references")
@onready var player_mesh = get_node("Knight")
@onready var camera_h = get_node("cameraroot/horizontal")
#endregion

#region movement constants
@export_group("movement preferences")
@export var gravity: float = 9.8
@export var jump_height: float = 1 ## jump height in units
@export var walk_speed: float = 3
@export var run_speed: float = 10
const ANGULAR_ACCELERATION: float = 10
const ACCELERATION: float = 2.0
#endregion

#region node names
const idle_node_name: String = "Idle"
const walk_node_name: String = "Walk"
const run_node_name: String = "Run"
const jump_node_name: String = "Jump"
const attack1_node_name: String = "Attack1"
const death_node_name: String = "Death"
#endregion

#region variables
enum MovementState {
	WALKING,
	RUNNING
}
var movement_state : MovementState
var direction: Vector3
var aim_turn: float
var movement_speed: float
var just_hit: bool
var is_attacking: bool
var is_dying: bool
#endregion


func _input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		aim_turn = -event.relative.x * 0.015
	if event.is_action_pressed("aim"):
		direction = camera_h.global_transform.basis.z

func _physics_process(delta: float) -> void:
	# if is_dying do nothing
	if is_dying:
		return
	
	# apply gravity
	var vertical_velocity = velocity.y
	vertical_velocity += gravity * delta
	if is_on_floor():
		vertical_velocity = gravity / 10
	
	# jumping
	if Input.is_action_just_pressed("jump") and !is_attacking and is_on_floor():
		vertical_velocity =  sqrt(jump_height * 2 * gravity)
	
	# convert input direction in put to local coordinates
	var h_rot = camera_h.global_transform.basis.get_euler().y
	var direction := Vector3(Input.get_axis("right", "left"), 0, Input.get_axis("backward", "forward"))
	if (direction):
		direction = direction.rotated(Vector3.UP, h_rot).normalized()
	
	# check if sprinting & set movement_state
	if Input.is_action_pressed("sprint"):
		movement_state = MovementState.RUNNING
	else:
		movement_state = MovementState.WALKING
	
	# set movement_speed according to movement_state
	match (movement_state):
		MovementState.WALKING:
			movement_speed = walk_speed
		MovementState.RUNNING:
			movement_speed = run_speed
	
	# rotate player mesh according to direction
	if Input.is_action_pressed("aim"):
		player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, camera_h.rotation.y, delta * ANGULAR_ACCELERATION)
	else:
		player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(direction.x, direction.z) - rotation.y, delta * ANGULAR_ACCELERATION)
	
	# apply horizontal velocity
	velocity.y = 0
	if is_attacking:
		velocity = velocity.lerp(direction * 0.1, ACCELERATION * delta)
	else:
		velocity = velocity.lerp(direction * movement_speed, ACCELERATION * delta)
	
	# apply vertial velocity
	velocity.y = vertical_velocity
	
	# apply velocity to CharacterBody2D
	move_and_slide()