A game prototype built with Godot 4 exploring in-world inventory management mechanics.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

123 lines
3.7 KiB

class_name Player
extends CharacterBody3D
@export var camera: Camera
@export_group("Movement")
@export var move_accel := 6.0
@export var move_max_speed := 4.0
@export var friction_floor := 12.0
@export var friction_air := 2.0
@export var gravity := -12.0
@export_group("Jumping")
@export var jump_velocity := 5.0
@export var jump_early_time := 0.0 # Time (in seconds) after pressing the jump button a jump may occur late.
@export var jump_coyote_time := 0.0 # Time (in seconds) after leaving a jumpable surface when a jump may still occur.
enum MovementModeEnum { DEFAULT, FLYING, NO_CLIP }
var movement_mode := MovementModeEnum.DEFAULT
var is_moving := false
var is_sprinting := false
var time_since_jump_pressed := INF
var time_since_on_floor := INF
func _input(event: InputEvent) -> void:
# Inputs that are valid when the game is focused.
# ===============================================
if event.is_action("move_sprint"):
is_sprinting = event.is_pressed()
get_viewport().set_input_as_handled()
if event.is_action_pressed("move_jump"):
time_since_jump_pressed = 0
get_viewport().set_input_as_handled()
# Cycle movement mode between default, flying and noclip.
if event.is_action_pressed("cycle_movement_mode"):
if (+movement_mode > MovementModeEnum.NO_CLIP):
movement_mode = MovementModeEnum.DEFAULT;
get_viewport().set_input_as_handled()
# Inputs that are valid only when the mouse is captured.
# ======================================================
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
pass
func _physics_process(delta: float) -> void:
match movement_mode:
MovementModeEnum.DEFAULT:
process_movement_default(delta)
MovementModeEnum.FLYING:
process_movement_flying(delta, false)
MovementModeEnum.NO_CLIP:
process_movement_flying(delta, true)
func process_movement_default(delta: float) -> void:
var input := Input.get_vector("move_strafe_left", "move_strafe_right", "move_forward", "move_backward")
velocity.y += gravity * delta
var hor_vel := Vector3(velocity.x, 0, velocity.z)
var target := basis.rotated(Vector3.UP, camera.current_yaw) * Vector3(input.x, 0, input.y) * move_max_speed
is_moving = target.dot(hor_vel) > 0
var accel: float
if is_moving: accel = move_accel
elif is_on_floor(): accel = friction_floor
else: accel = friction_air
if is_sprinting:
target *= 5
accel *= 5
hor_vel = hor_vel.lerp(target, accel * delta)
velocity = Vector3(hor_vel.x, velocity.y, hor_vel.z)
# TODO: Check if this still applies.
# Sometimes, when pushing into a wall, jumping wasn't working.
# Possibly due to `IsOnFloor` returning `false` for some reason.
# The `JumpEarlyTime` feature seems to avoid this issue, thankfully.
if is_on_floor(): time_since_on_floor = 0
else: time_since_on_floor += delta
if time_since_jump_pressed <= jump_early_time && time_since_on_floor <= jump_coyote_time:
velocity.y = jump_velocity
time_since_jump_pressed = INF
time_since_on_floor = INF
move_and_slide()
func process_movement_flying(delta: float, no_clip: bool) -> void:
var input := Vector3(
Input.get_axis("move_strafe_left", "move_strafe_right"),
Input.get_axis("move_downward", "move_upward"),
Input.get_axis("move_forward", "move_backward"))
velocity *= 1 - friction_air * delta;
var target := camera.global_transform.basis.get_rotation_quaternion() * input * move_max_speed
is_moving = target.dot(velocity) > 0
var accel: float
if is_moving: accel = move_accel
else: accel = friction_air
target *= 4
accel *= 4
if is_sprinting:
target *= 5
accel *= 5
velocity = velocity.lerp(target, accel * delta)
if no_clip: translate(velocity * delta)
else: move_and_slide()