extends Node3D ## Give the camera a little kick when the player falls, proportional to landing velocity @export var enabled := true @export var character: CharacterBody3D ## Minimum landing speed before kick is applied, in m/s @export var min_landing_speed := 4.0 ## How far the camera gets pitched on kick in degrees per m/s of landing velocity @export var angle_curve: Curve ## How far down the camera gets kicked in meters per m/s of landing velocity @export var offset_curve: Curve ## How long the rebound takes in seconds per m/s of landing velocity @export var rebound_time_curve: Curve var _last_speed := 0.0 var _in_air := false var _tween: Tween func _get_tween() -> Tween: if _tween and _tween.is_valid(): _tween.kill() _tween = create_tween() return _tween func do_kick(landing_speed: float) -> void: if landing_speed < min_landing_speed: return # Initial kick rotation.x = -deg_to_rad(angle_curve.sample(landing_speed)) position.y = -offset_curve.sample(landing_speed) # Rebound var rebound_time := rebound_time_curve.sample(landing_speed) var tween := _get_tween() tween.set_parallel(true) tween.tween_property(self, "rotation:x", 0, rebound_time).set_trans(Tween.TRANS_CUBIC).set_ease( Tween.EASE_OUT ) tween.tween_property(self, "position:y", 0, rebound_time).set_trans(Tween.TRANS_CUBIC).set_ease( Tween.EASE_OUT ) func _process(_delta: float) -> void: if character.is_on_floor(): if _in_air: # First frame after land do_kick(absf(_last_speed)) _in_air = false else: _in_air = true _last_speed = character.velocity.y