Godot Version
godot 4
Question
i am probably missing a small detail but I can’t find out why it’s defined, here’s the code:
extends CharacterBody3D
variables
Speed
var current_speed = 5.0
var lerp_speed = 10.0
var air_lerp_speed = 3.0
var direction = Vector3.ZERO
Crouching
var crouching_depth = -0.5
Sliding
var slide_timer = 0.0
var slide_timer_max = 1.0
var slide_vector = Vector2.ZERO
var slide_speed = 10.0
head bobbing
var head_bobbing_vector = Vector2.ZERO
var head_bobbing_index = 0.0
var head_bobbing_current_intensity = 0.0
Stairs
var _last_frame_was_on_floor = -INF
var _snapped_to_stairs_last_frame := false
State variables
var walking = false
var sprinting = false
var crouching = false
var free_looking = false
var sliding = false
camera dir
var cam_aligned_wish_dir := Vector3.ZERO
var wish_dir := Vector3.ZERO
onready variables
@onready var Neck: Node3D = $Neck
@onready var head: Node3D = $Neck/head
@onready var eyes: Node3D = $Neck/head/Eyes
@onready var camera_3d: Camera3D = $Neck/head/Eyes/Camera3D
@onready var standing_collision: CollisionShape3D = $Standing_Collision
@onready var crouching_collision: CollisionShape3D = $Crouching_Collision
@onready var ray_cast_3d: RayCast3D = $RayCast3D
exported variables
speed
@export var walking_speed = 5.0
@export var sprinting_speed = 8.0
@export var crouching_speed = 3.0
@export var swim_up_speed := 10.0
free looking
@export var mouse_sens = 0.25
@export var free_looking_tilt_amount = 8
head bobbing
@export var head_bobbing_sprinting_speed = 22.0
@export var head_bobbing_walking_speed = 14.0
@export var head_bobbing_crouching_speed = 10.0
head bobbing intensity
@export var head_bobbing_sprinting_intensity = 0.2
@export var head_bobbing_walking_intensity = 0.1
@export var head_bobbing_crouching_intensity = 0.05
constants
const jump_velocity = 4.5
const MAX_STEP_HEIGHT = 0.5
#--------------------------------------------------------------------------------------------------------------------------------------------
This runs at the beginning of the game
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
#----------------------------------------------------------------------------------------------------------------------------------
var _saved_camera_global_pos = null
func _save_camera_pos_for_smoothing():
if _saved_camera_global_pos == null:
_saved_camera_global_pos = %Eyes.global_position
func _slide_camera_smooth_back_to_origin(delta):
if _saved_camera_global_pos == null: return
%Eyes.global_position.y = _saved_camera_global_pos.y
%Eyes.position.y = clamp(%Eyes.position.y, -0.7, 0.7) # Clamp incase teleported
var move_amount = max(self.velocity.length() * delta, current_speed/2 * delta)
%Eyes.position.y = move_toward(%Eyes.position.y, 0.0, move_amount)
_saved_camera_global_pos = %Eyes.global_position
if %Eyes.position.y == 0:
_saved_camera_global_pos = null
func _snap_down_to_stairs_check() → void:
var did_snap := false
var floor_below : bool = %“Stairs Below”.is_colliding() and not is_surface_too_steep(%“Stairs Below”.get_collision_normal())
var was_on_floor_last_frame = Engine.get_physics_frames() == _last_frame_was_on_floor
if not is_on_floor() and velocity.y <= 0 and (was_on_floor_last_frame or _snapped_to_stairs_last_frame) and floor_below:
var body_test_result = PhysicsTestMotionResult3D.new()
if _run_body_test_motion(self.global_transform, Vector3(0,-MAX_STEP_HEIGHT,0), body_test_result):
_save_camera_pos_for_smoothing()
var translate_y = body_test_result.get_travel().y
self.position.y += translate_y
apply_floor_snap()
did_snap = true
_snapped_to_stairs_last_frame = did_snap
func _snap_up_stairs_check(delta) → bool:
if not is_on_floor() and not _snapped_to_stairs_last_frame: return false
if self.velocity.y > 0 or (self.velocity * Vector3(1,0,1)).length() == 0: return false
var expected_move_motion = self.velocity * Vector3(1,0,1) * delta
var step_pos_with_clearance = self.global_transform.translated(expected_move_motion + Vector3(0, MAX_STEP_HEIGHT * 2, 0))
var down_check_result = PhysicsTestMotionResult3D.new()
if (_run_body_test_motion(step_pos_with_clearance, Vector3(0,-MAX_STEP_HEIGHT*2,0), down_check_result)
and (down_check_result.get_collider().is_class(“StaticBody3D”) or down_check_result.get_collider().is_class(“CSGShape3D”))):
var step_height = ((step_pos_with_clearance.origin + down_check_result.get_travel()) - self.global_position).y
if step_height > MAX_STEP_HEIGHT or step_height <= 0.01 or (down_check_result.get_collision_point() - self.global_position).y > MAX_STEP_HEIGHT: return false
%“Stairs Above”.global_position = down_check_result.get_collision_point() + Vector3(0,MAX_STEP_HEIGHT,0) + expected_move_motion.normalized() * 0.1
%“Stairs Above”.force_raycast_update()
if %“Stairs Above”.is_colliding() and not is_surface_too_steep(%“Stairs Above”.get_collision_normal()):
_save_camera_pos_for_smoothing()
self.global_position = step_pos_with_clearance.origin + down_check_result.get_travel()
apply_floor_snap()
_snapped_to_stairs_last_frame = true
return true
return false
This is if inputs/buttons are pressed
func _input(event):
if Input.is_action_just_pressed(“Quit”):
get_tree().quit()
if event is InputEventMouseMotion:
if free_looking:
Neck.rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
Neck.rotation.y = clamp(Neck.rotation.y, deg_to_rad(-120), deg_to_rad(120))
else:
rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
head.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
head.rotation.x = clamp(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))
#------------------------------------------------------------------------------------------------------------------------------
func _physics_process(delta):
var input_dir = Input.get_vector(“Left”, “Right”, “Forward”, “Backward”)
if is_on_floor(): _last_frame_was_on_floor = Engine.get_physics_frames()
current_speed = lerp(current_speed, crouching_speed, delta * lerp_speed)
head.position.y = lerp(head.position.y,crouching_depth, delta * lerp_speed)
standing_collision.disabled = true
crouching_collision.disabled = false
if sprinting && input_dir != Vector2.ZERO:
sliding = true
slide_timer = slide_timer_max
slide_vector = input_dir
free_looking = true
walking = false
sprinting = false
crouching = true
elif !ray_cast_3d.is_colliding():
standing_collision.disabled = false
crouching_collision.disabled = true
head.position.y = lerp(head.position.y,0.0, delta * lerp_speed)
if Input.is_action_pressed("Sprint"):
current_speed = lerp(current_speed, sprinting_speed, delta * lerp_speed)
walking = false
sprinting = true
crouching = false
else:
current_speed = lerp(current_speed, walking_speed, delta * lerp_speed)
walking = true
sprinting = false
crouching = false
# Free looking
if Input.is_action_pressed("free_look") || sliding:
free_looking = true
if sliding:
camera_3d.rotation.z = lerp(camera_3d.rotation.z,-deg_to_rad(7.0), delta * lerp_speed)
else:
camera_3d.rotation.z = -deg_to_rad(Neck.rotation.y * free_looking_tilt_amount)
else:
free_looking = false
Neck.rotation.y = lerp(Neck.rotation.y,0.0, delta * lerp_speed)
camera_3d.rotation.z = lerp(camera_3d.rotation.z,0.0, delta * lerp_speed)
if sliding:
slide_timer -= delta
if slide_timer <= 0:
sliding = false
free_looking = false
if sprinting:
head_bobbing_current_intensity = head_bobbing_sprinting_intensity
head_bobbing_index += head_bobbing_sprinting_speed * delta
elif walking:
head_bobbing_current_intensity = head_bobbing_walking_intensity
head_bobbing_index += head_bobbing_walking_speed * delta
elif crouching:
head_bobbing_current_intensity = head_bobbing_crouching_intensity
head_bobbing_index += head_bobbing_crouching_speed * delta
if is_on_floor() && !sliding && input_dir != Vector2.ZERO:
head_bobbing_vector.y = sin(head_bobbing_index)
head_bobbing_vector.x = sin(head_bobbing_index / 2) + 0.5
eyes.position.y = lerp(eyes.position.y, head_bobbing_vector.y * (head_bobbing_current_intensity / 2.0), delta * lerp_speed)
eyes.position.x = lerp(eyes.position.x, head_bobbing_vector.x * head_bobbing_current_intensity, delta * lerp_speed)
else:
eyes.position.y = lerp(eyes.position.y, 0.0 , delta * lerp_speed)
eyes.position.x = lerp(eyes.position.x, 0.0 , delta * lerp_speed)
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
func _handle_water_physics(delta) → bool:
if get_tree().get_nodes_in_group(“water_area”).all(func(area): return !area.overlaps_body(self)):
return false
if not is_on_floor():
velocity.y -= ProjectSettings.get_setting("physics/3d/default_gravity") * 0.1 * delta
self.velocity += cam_aligned_wish_dir * current_speed * delta
if Input.is_action_pressed("Jump"):
self.velocity.y += swim_up_speed * delta
# Dampen velocity when in water
self.velocity = self.velocity.lerp(Vector3.ZERO, 2 * delta)
return true
# Handle jump.
if is_on_floor() or _snapped_to_stairs_last_frame:
if Input.is_action_just_pressed("Jump") and is_on_floor():
velocity.y = jump_velocity
sliding = false
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
THIS IS THE PROBLEM
if is_on_floor():
if direction != Vector2.ZERO:
direction = lerp(direction,(transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(),delta * lerp_speed)
else:
direction = lerp(direction,(transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(),delta * air_lerp_speed)
if sliding:
direction = (transform.basis * Vector3(slide_vector.x, 0, slide_vector.y)).normalized()
current_speed = (slide_timer + 0.1) * slide_speed
if direction:
velocity.x = direction.x * current_speed
velocity.z = direction.z * current_speed
else:
velocity.x = move_toward(velocity.x, 0, current_speed)
velocity.z = move_toward(velocity.z, 0, current_speed)
if not _snap_up_stairs_check(delta):
move_and_slide()
_snap_down_to_stairs_check()
func is_surface_too_steep(normal : Vector3) → bool:
return normal.angle_to(Vector3.UP) > self.floor_max_angle
func _run_body_test_motion(from : Transform3D, motion : Vector3, result = null) → bool:
if not result: result = PhysicsTestMotionParameters3D.new()
var params = PhysicsTestMotionParameters3D.new()
params.from = from
params.motion = motion
return PhysicsServer3D.body_test_motion(self.get_rid(), params, result)