Here's a 3D platformer character controller free to copy!

extends CharacterBody3D

# The reason why we have this when defining a variable ":=" is due to godot being able...
# to infer types of variables, it's the same as static typing a variable such as 'var example: float = 2.0'

# Horizontal movement of the player

#Speed of the player, in m/s
@export var speed := 5.0
#Speed of the player, in m/s whilst sprinting
@export var sprint_speed := 8.0
#Take the springarm pos, so that our direction isn't based off the global axis, but rather the rotated camera
@onready var camera := $Node3D/CameraPivot

# Jump movement of the player

#How high the player jumps, keep in mind no longer negative due to cartisian plane no longer being inverted
@export var jump_velocity := 4.5

#Dashing of the player

#How far the player moves in metres when pressed
@export var dash_velocity := 50
# This is a flag check to see if the player has clicked dash, if so, halt movement such that only dash movement applies
var player_dashing := false
# To check if the player can dash, if this check does not exist, the player movement would purely be dashing
var can_dash := true

#Other variables needed
var levels: int

#Functions that the game calls/godot built in functions 

func _physics_process(delta: float) -> void:
	# In order to make code cleaner, code that needs to run every frame is first...
	# defined and then called up in the process function
	if player_dashing == false:
		movement()
	
	dash()
	gravity(delta)
	jump()
	move_and_slide()



#Functions that have been made/non-godot specific functions

func movement() -> void:
	# Get the input direction and handle the movement/deceleration.
	var input_dir := Input.get_vector("left", "right", "forward", "backward")
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	#Rotate the global axis by the camera rotaion so that the player moves with the camera
	direction = direction.rotated(Vector3.UP, camera.global_rotation.y)
	
	if direction: # When a key is pressed
		# Note that it's the XZ plane, as the Y plane handles vertical movement
		velocity.x = direction.x * speed # When x direction key pressed, move in x direction
		velocity.z = direction.z * speed # Same here for z
	else: # Friction
		velocity.x = move_toward(velocity.x, 0, speed)
		velocity.z = move_toward(velocity.z, 0, speed)
	
	#Handles sprint, when shift pressed; run, else walk
	if Input.is_action_pressed("sprint"):
		velocity.x = direction.x * sprint_speed 
		velocity.z = direction.z * sprint_speed 
	


func gravity(delta) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta


func jump() -> void:
	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_velocity

func dash():
	if Input.is_action_just_released("dash") and can_dash == true:
		#In order to dash, we need the forward vector, this var grabs it
		var forward: Vector3 = -camera.transform.basis.z
		#When the camera faces a certain direction, vector3 may not have the xyz plane be equal, leading to...
		#inconsistant dashing, when normalized, the xyz plane where the player is facing is equal
		forward = forward.normalized()
		# Flag to say the player is dashing
		player_dashing = true
		# Flag to say the player can no longer dash
		can_dash = false
		# Player velocity is equal to the directon of the camera direction times by the dash vel.
		velocity += forward * dash_velocity
		#A dash timer, when ended, dashing ends
		$DashTimer.start()
		


#Functions that are connected, such as signals


func _on_dash_timer_timeout() -> void:
	#Play is no longer dashing
	player_dashing = false
	#Player can dash
	can_dash = true
	#When the dash ends, the xz plane stops due to the movement func, this ensures consistancy in the y plane
	velocity.y = velocity.y / 4

For convenience sake, I’ve also commented the code if you want to understand it!

(Do keep in mind I’ve only been using the engine for 2 months, so some code may be wrong/could be optimised)

This is the structure of the player.

1 Like