feat: implement player movement controller and state machine with dash, slide, and wall-run mechanics
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1nbudf4cu2dl
|
||||
Reference in New Issue
Block a user