feat: implement player movement controller and state machine with dash, slide, and wall-run mechanics

This commit is contained in:
DottsGit
2026-06-03 21:55:10 -04:00
parent 5726aa1043
commit da277e9d35
14 changed files with 1092 additions and 329 deletions
+95
View File
@@ -0,0 +1,95 @@
extends Node3D
class_name FPSCameraRig
## FPS camera rig — handles mouse look, head bob, FOV kick, wall-run tilt.
## Attach as child of the CharacterBody3D player. Camera3D is a child of this node.
@export var sensitivity: float = 0.002
@export var pitch_limit: float = 89.0
var params: MovementParams
var camera: Camera3D
var _bob_timer: float = 0.0
var _target_tilt: float = 0.0
var _current_tilt: float = 0.0
var _target_fov: float = 90.0
func _ready() -> void:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
camera = get_node_or_null("Camera3D")
if camera and params:
camera.fov = params.base_fov
_target_fov = params.base_fov
# Explicitly enable callbacks — set_script() at runtime doesn't auto-register them
set_process_input(true)
set_process(true)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
var sens := sensitivity
if params:
sens = params.mouse_sensitivity
# Yaw: rotate the player (parent)
get_parent().rotate_y(-event.relative.x * sens)
# Pitch: rotate this pivot
rotation.x = clampf(rotation.x - event.relative.y * sens, deg_to_rad(-pitch_limit), deg_to_rad(pitch_limit))
# Toggle mouse capture with Escape
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _process(delta: float) -> void:
if not camera or not params:
return
var player: CharacterBody3D = get_parent() as CharacterBody3D
if not player:
return
# ── FOV kick ──────────────────────────────────────────────────────────
_target_fov = params.base_fov
var hspeed := Vector2(player.velocity.x, player.velocity.z).length()
# Dash FOV (highest priority)
if hspeed > params.dash_speed * 0.8:
_target_fov = params.dash_fov
elif hspeed > params.sprint_speed * 0.8:
_target_fov = params.sprint_fov
camera.fov = lerpf(camera.fov, _target_fov, 1.0 - exp(-params.fov_lerp_speed * delta))
# ── Head bob ──────────────────────────────────────────────────────────
if player.is_on_floor() and hspeed > 1.0:
_bob_timer += delta * params.head_bob_frequency * (hspeed / params.walk_speed)
var bob_y := sin(_bob_timer) * params.head_bob_amplitude
var bob_x := cos(_bob_timer * 0.5) * params.head_bob_amplitude * 0.5
camera.position.y = lerp(camera.position.y, bob_y, 0.3)
camera.position.x = lerp(camera.position.x, bob_x, 0.3)
else:
_bob_timer = 0.0
camera.position.y = lerp(camera.position.y, 0.0, 0.15)
camera.position.x = lerp(camera.position.x, 0.0, 0.15)
# ── Wall-run tilt ─────────────────────────────────────────────────────
_current_tilt = lerpf(_current_tilt, _target_tilt, 1.0 - exp(-params.wall_run_tilt_speed * delta))
camera.rotation.z = deg_to_rad(_current_tilt)
## Called by the movement system to set wall-run tilt direction.
## side: -1.0 (left wall), 1.0 (right wall), 0.0 (no tilt)
func set_wall_tilt(side: float) -> void:
if params:
_target_tilt = side * params.wall_run_tilt_angle
else:
_target_tilt = side * 12.0
## Called to reset tilt (e.g., on landing).
func clear_wall_tilt() -> void:
_target_tilt = 0.0
+1
View File
@@ -0,0 +1 @@
uid://b1nbudf4cu2dl