Skidding in a 3D fps game

Godot Version

Godot 4

Question

I’m currently making a 3D fps game similar to Pizza Tower in movement. I have the acceleration mechanic for my game, but I add something else to it as well. I want to make sure whenever you’re holding down the shift key and you turn around, that you skid on the ground. The player will still keep their speed they had but will skid on the ground just like Pizza Tower. However, I’m unsure how to do this exactly with my CharacterBody3D code.

Here is my current code:


var speed
const WALK_SPEED = 10.0
const SPRINT_SPEED = 20.0
const JUMP_VELOCITY = 7.8
var accelerationx = 5.0
var dead = false

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = 18.8

@onready var camera = $Camera3D
@onready var animated_sprite_2d = $"CanvasLayer/Bare Hands/AnimatedSprite2D"
	
func _ready():
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	camera.current = true

func _unhandled_input(event: InputEvent) -> void:
	if dead:
		return
	if event is InputEventMouseButton:
		Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	elif event.is_action_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
		if event is InputEventMouseMotion:
			rotate_y(-event.relative.x * 0.005)
			camera.rotate_x(-event.relative.y * 0.005)
			camera.rotation.x = clamp(camera.rotation.x, -PI/4, PI/3)
			

func _physics_process(delta):
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (camera.transform.basis * transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if not is_on_floor():
		velocity.y -= gravity * delta

	# Handle Jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	# Handle Sprint.
	if Input.is_action_pressed("sprint") and Input.get_vector("left", "right", "forward", "backward"):
		speed = move_toward(speed, SPRINT_SPEED, accelerationx * delta)
	else:
		speed = WALK_SPEED
		
		
	if speed == 20:
		animated_sprite_2d.play("Running")
	elif speed == 0 or speed == WALK_SPEED:
		animated_sprite_2d.play("Idle")

	if is_on_floor():
		if direction:
			velocity.x = direction.x * speed
			velocity.z = direction.z * speed
		else:
			velocity.x = lerp(velocity.x, direction.x * speed, delta * 7.0)
			velocity.z = lerp(velocity.z, direction.z * speed, delta * 7.0)
		
	else:
		velocity.x = lerp(velocity.x, direction.x * speed, delta * 3.0)
		velocity.z = lerp(velocity.z, direction.z * speed, delta * 3.0)
	
	move_and_slide()

And here is a video demonstrating the acceleration mechanic:

You directly set the velocity when moving on the floor, but use lerp for decelerating otherwise. You must accelerate towards direction much like you decelerate towards zero, then you can detect when your input direction is opposite of your speed using a dot product and play an animation accordingly.

if direction.dot(velocity) < 0:
    $AnimationPlayer.play("skidding")

I’d highly recommend using move_toward instead of lerp as lerp will produce frame-dependent results, and using delta actually makes it worse.

If you use the same acceleration value you can remove if direction

if is_on_floor():
    velocity.x = move_toward(velocity.x, direction.x * speed, acceleration * delta)
    velocity.z = move_toward(velocity.z, direction.z * speed, acceleration * delta)

Using lerp and/or move_toward on each x/z component individually makes your deceleration/acceleration favor axis-alignment, I also recommend making a horizontal planar vector and operating on that.

# split vertical_velocity to preserve gravity and operate solely on x/z
var vertical_velocity := velocity.project(up_direction)
var horizontal_velocity := velocity - vertical_velocity

if is_on_floor():
    horizontal_velocity = horizontal_velocity.move_toward(direction * speed, acceleration * delta)
else:
    horizontal_velocity = horizontal_velocity.move_toward(direction * speed, air_acceleration * delta)

# combine into velocity, then use `move_and_slide()`
velocity = horizontal_velocity + vertical_velocity
move_and_slide()

I’m so sorry but I’m a little confused on where all of this code should go. I’m very new to gdscript.

move_and_slide should only be used in _physics_process, and your direction is only defined in _physics_process, so the example should be implemented in _physics_process, but I’d be surprised if you could just paste it in.

1 Like