Godot Version
4.5
Question
I just started learning programming a few weeks ago by following along a pretty intensive Udemy course. I’m about halfway through it so I thought its a good time take a break to practice writing a script without direct guidance from the course.
I decided to upgrade the default player controller script to something that feels more smooth and polished. I looked at some related YouTube videos and forum posts to understand some techniques that people use (mostly for smooth motion), but I didn’t copy any code and really challenged myself to make something that’s my own.
After about a day and half of trial and error, I landed on this script; and it feels pretty good in my opinion. However, being such a beginner I thought it would be a good idea to post it here and see if I made any mistakes. I know that just because it plays doesn’t necessarily mean that the code is efficient or won’t cause unforeseen issues in particular circumstances.
extends CharacterBody3D
class_name Player
# Scene References
@onready var camera_3d: Camera3D = $Camera3D
@onready var label_3d: Label3D = $Camera3D/Label3D
# Constants
const SENSITIVITY_BASE: float = 0.001
const GRAVITY: float = 15
# Variables
var _base_speed: float = 3.0
var _sprint_speed: float = 4.5
var _speed: float = _base_speed # the current maximum speed we can move toward.
var _movement_acceleration: float = 0.25 # how fast to move toward _speed when grounded.
var _sprint_acceleration: float = 0.1 # how fast to move toward _speed when sprinting.
var _air_resistance: float = 0.1 # how fast to move toward _speed when airbourne.
var _jump_strength: float = 5.3
var _sensitivity_multiplier: float = 2.0 # mulitplied by SENSITIVY_BASE.
var _can_sprint: bool = true # prevents initianting sprint while airbourne.
var _direction = Vector3.ZERO
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
rotate_y(-event.relative.x * SENSITIVITY_BASE * _sensitivity_multiplier)
camera_3d.rotate_x(-event.relative.y * SENSITIVITY_BASE * _sensitivity_multiplier)
camera_3d.rotation.x = clamp(camera_3d.rotation.x, deg_to_rad(-60), deg_to_rad(90))
func _physics_process(delta: float) -> void:
# Apply gravity
if !is_on_floor():
velocity.y -= GRAVITY * delta
handle_movement()
handle_jump()
move_and_slide()
func handle_movement() -> void:
var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
# _can_sprint check:
if Input.is_action_just_pressed("sprint") and !is_on_floor():
_can_sprint = false
elif is_on_floor():
_can_sprint = true
# Sprint logic
if Input.is_action_pressed("move_forward") and Input.is_action_pressed("sprint") and _can_sprint:
_speed = move_toward(_speed, _sprint_speed, _sprint_acceleration)
else:
_speed = move_toward(_speed, _base_speed, _sprint_acceleration)
# Grounded movement logic
if is_on_floor():
_direction = _direction.move_toward((transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(), _movement_acceleration)
if _direction:
velocity.x = _direction.x * _speed
velocity.z = _direction.z * _speed
else:
velocity.x = move_toward(velocity.x, 0.0, _movement_acceleration)
velocity.z = move_toward(velocity.z, 0.0, _movement_acceleration)
# Airbourne movment logic
else:
_direction = _direction.move_toward((transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(), _air_resistance)
if _direction:
velocity.x = _direction.x * _speed
velocity.z = _direction.z * _speed
else:
velocity.x = move_toward(velocity.x, 0.0, _air_resistance)
velocity.z = move_toward(velocity.z, 0.0, _air_resistance)
# Debug Label
label_3d.text = "_speed: %s\nVelocity: %s\nForward Vel: %.2f\nis_on_floor: %s" % [ _speed, velocity, -global_transform.basis.z.dot(velocity), is_on_floor()]
func handle_jump() -> void:
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = _jump_strength
I know it’s not a lot of code for over a days work lol, but that’s what I landed on.
If anyone has thoughts, I would love to hear it!