extends CharacterBody3D @onready var camera = %Camera @export var subviewport_camera: Camera3D @export var main_camera:Camera3D @export var animation_tree: AnimationTree var camera_rotation: Vector2 = Vector2(0.0,0.0) var mouse_sensitivity = 0.001 var crouched: bool = false var crouch_blocked: bool = false @export_category("Crouch Parametres") @export var enable_crouch: bool = true @export var crouch_toggle: bool = false @export var crouch_collision: ShapeCast3D @export_range(0.0,3.0) var crouch_speed_reduction = 2.0 @export_range(0.0,0.50) var crouch_blend_speed = .2 enum {GROUND_CROUCH = -1, STANDING = 0, AIR_CROUCH = 1} @export_category("Lean Parametres") @export var enable_lean: bool = true @export_range(0.0,1.0) var lean_speed: float = .2 @export var right_lean_collision: ShapeCast3D @export var left_lean_collision: ShapeCast3D var lean_tween enum {LEFT = 1, CENTRE = 0, RIGHT = -1} @export_category("speed Parameters") @export var enable_sprint: bool = true @export var sprint_timer: Timer @export var sprint_cooldown_time: float = 3.0 @export var sprint_time: float = 1.0 @export var sprint_replenish_rate: float = 0.30 @export var acceleration: float = 120 @export_range(0.01,1.0) var air_acceleration_modifier: float = 0.1 var sprint_on_cooldown: bool = false var sprint_time_remaining: float = sprint_time @onready var sprint_bar: Range = $CanvasLayer/SprintBar const NORMAL_speed = 1 @export_range(1.0,3.0) var sprint_speed: float = 2.0 @export_range(0.1,1.0) var walk_speed: float = 0.5 var speed_modifier: float = NORMAL_speed @export_category("Jump Parameters") @export var coyote_timer: Timer @export var jump_peak_time: float = .5 @export var jump_fall_time: float = .5 @export var jump_height: float = 2.0 @export var jump_distance: float = 4.0 @export var coyote_time: float = .1 @export var jump_buffer_time: float = .2 # Get the gravity from the project settings to be synced with RigidBody nodes. var jump_gravity = ProjectSettings.get_setting("physics/3d/default_gravity") var fall_gravity: float var jump_velocity: float var base_speed: float var _speed: float var jump_available: bool = true var jump_buffer: bool = false func _ready() -> void: update_camera_rotation() Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) calculate_movement_parameters() func update_camera_rotation() -> void: var current_rotation = get_rotation() camera_rotation.x = current_rotation.y camera_rotation.y = current_rotation.x func _input(event: InputEvent) -> void: if event.is_action_pressed("ui_cancel"): 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) if event is InputEventMouseMotion: var MouseEvent = event.relative * mouse_sensitivity camera_look(MouseEvent) if enable_crouch: if event.is_action_pressed("crouch"): crouch() if event.is_action_released("crouch"): if !crouch_toggle and crouched: crouch() if enable_lean: if Input.is_action_just_released("lean_left") or Input.is_action_just_released("lean_right"): if !(Input.is_action_pressed("lean_right") or Input.is_action_pressed("lean_left")): lean(CENTRE) if Input.is_action_just_pressed("lean_left"): lean(LEFT) if Input.is_action_just_pressed("lean_right"): lean(RIGHT) if enable_sprint: if Input.is_action_just_pressed("sprint") and !crouched: if !sprint_on_cooldown: speed_modifier = sprint_speed sprint_timer.start(sprint_time_remaining) if Input.is_action_just_released("sprint") or Input.is_action_just_released("walk"): if !(Input.is_action_pressed("walk") or Input.is_action_pressed("sprint")): speed_modifier = NORMAL_speed exit_sprint() if Input.is_action_just_pressed("walk") and !crouched: speed_modifier = walk_speed func calculate_movement_parameters() -> void: jump_gravity = (2*jump_height)/pow(jump_peak_time,2) fall_gravity = (2*jump_height)/pow(jump_fall_time,2) jump_velocity = jump_gravity * jump_peak_time base_speed = jump_distance/(jump_peak_time+jump_fall_time) _speed = base_speed func lean(blend_amount: int) -> void: if is_on_floor(): if lean_tween: lean_tween.kill() lean_tween = get_tree().create_tween() lean_tween.tween_property(animation_tree,"parameters/lean_blend/blend_amount", blend_amount, lean_speed) func lean_collision() -> void: animation_tree["parameters/left_collision_blend/blend_amount"] = lerp( float(animation_tree["parameters/left_collision_blend/blend_amount"]),float(left_lean_collision.is_colliding()),lean_speed ) animation_tree["parameters/right_collision_blend/blend_amount"] = lerp( float(animation_tree["parameters/right_collision_blend/blend_amount"]),float(right_lean_collision.is_colliding()),lean_speed ) func crouch() -> void: var Blend if !crouch_collision.is_colliding(): if crouched: Blend = STANDING else: speed_modifier = NORMAL_speed exit_sprint() if is_on_floor(): Blend = GROUND_CROUCH else: Blend = AIR_CROUCH var blend_tween = get_tree().create_tween() blend_tween.tween_property(animation_tree,"parameters/Crouch_Blend/blend_amount",Blend,crouch_blend_speed) crouched = !crouched else: crouch_blocked = true func camera_look(Movement: Vector2) -> void: camera_rotation += Movement transform.basis = Basis() camera.transform.basis = Basis() rotate_object_local(Vector3(0,1,0),-camera_rotation.x) # first rotate in Y camera.rotate_object_local(Vector3(1,0,0), -camera_rotation.y) # then rotate in X camera_rotation.y = clamp(camera_rotation.y,-1.5,1.2) func exit_sprint() -> void: if !sprint_timer.is_stopped(): sprint_time_remaining = sprint_timer.time_left sprint_timer.stop() func sprint_replenish(delta) -> void: var sprint_bar_Value if !sprint_on_cooldown and (speed_modifier != sprint_speed): if is_on_floor(): sprint_time_remaining = move_toward(sprint_time_remaining, sprint_time, delta*sprint_replenish_rate) sprint_bar_Value= (sprint_time_remaining/sprint_time)*100 else: sprint_bar_Value = (sprint_timer.time_left/sprint_time)*100 sprint_bar.value = sprint_bar_Value if sprint_bar_Value == 100: sprint_bar.hide() else: sprint_bar.show() func _process(_delta: float) -> void: if subviewport_camera: subviewport_camera.global_transform = main_camera.global_transform func _physics_process(_delta: float) -> void: sprint_replenish(_delta) lean_collision() if crouched and crouch_blocked: if !crouch_collision.is_colliding(): crouch_blocked = false if !Input.is_action_pressed("crouch") and !crouch_toggle: crouch() # Add the gravity. var _acceleration if not is_on_floor(): _acceleration = acceleration*air_acceleration_modifier if coyote_timer.is_stopped(): coyote_timer.start(coyote_time) if velocity.y>0: velocity.y -= jump_gravity * _delta else: velocity.y -= fall_gravity * _delta else: _acceleration = acceleration jump_available = true coyote_timer.stop() _speed = (base_speed / max((float(crouched)*crouch_speed_reduction),1)) * speed_modifier if jump_buffer: jump() jump_buffer = false # Handle Jump. if Input.is_action_just_pressed("ui_accept"): if jump_available: if crouched: crouch() else: lean(CENTRE) jump() else: jump_buffer = true get_tree().create_timer(jump_buffer_time).timeout.connect(on_jump_buffer_timeout) # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. var input_dir = Input.get_vector("left", "right", "up", "down") var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() velocity.x = move_toward(velocity.x, direction.x * _speed, _acceleration*_delta) velocity.z = move_toward(velocity.z, direction.z * _speed, _acceleration*_delta) move_and_slide() func jump()->void: velocity.y = jump_velocity jump_available = false func _on_sprint_timer_timeout() -> void: sprint_on_cooldown = true get_tree().create_timer(sprint_cooldown_time).timeout.connect(_on_sprint_cooldown_timeout) speed_modifier = NORMAL_speed sprint_time_remaining = 0 func _on_sprint_cooldown_timeout(): sprint_on_cooldown = false func _on_coyote_timer_timeout() -> void: jump_available = false func on_jump_buffer_timeout()->void: jump_buffer = false