extends CharacterBody3D @export var speed : float = 6.0 @export var jump_velocity : float = 8.0 @export var look_sensitivity : float = 0.001 @onready var camera : Camera3D = $Camera3D @onready var head : Node3D = $Head @onready var pivot : Node3D = $Head/Pivot @onready var collision_shape = $CollisionShape3D var gravity : float = ProjectSettings.get_setting("physics/3d/default_gravity")*2 var velocity_y : float = 0 var update : bool = false var gt_prev : Transform3D var gt_current : Transform3D var mesh_gt_prev : Transform3D var mesh_gt_current : Transform3D var obstacles : Array var is_climbing : bool = false func _ready(): # Camera set up to prevent jitter. camera_setup() Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _input(event): # Mouse movement. if event is InputEventMouseMotion: rotate_y(-event.relative.x * look_sensitivity) head.rotate_x(-event.relative.y * look_sensitivity) head.rotation.x = clamp(head.rotation.x, -PI/2, PI/2) # In-process func set up for preventing jitter. func _process(delta): if update: update_transform() update = false var f = clamp(Engine.get_physics_interpolation_fraction(), 0 ,1) camera.global_transform = gt_prev.interpolate_with(gt_current, f) func _physics_process(delta): update = true # Basic movement. var horizontal_velocity = Input.get_vector("left", "right", "forward", "backward").normalized() * speed velocity = horizontal_velocity.x * global_transform.basis.x + horizontal_velocity.y * global_transform.basis.z # Checking if the player is on the floor or climbing. if not is_on_floor() and not is_climbing: velocity_y -= gravity * delta else: velocity_y = 0 # Jump. if Input.is_action_just_pressed("jump") and is_on_floor(): velocity_y = jump_velocity # Vaulting with place_to_land detection and animation. vaulting(delta) velocity.y = velocity_y move_and_slide() # Camera set up to prevent jitter. func camera_setup(): camera.set_as_top_level(true) camera.global_transform = pivot.global_transform gt_prev = pivot.global_transform gt_current = pivot.global_transform # Updating transform to interpolate the camera's movement for smoothness. func update_transform(): gt_prev = gt_current gt_current = pivot.global_transform # Creating RayCast via code. func raycast(from: Vector3, to: Vector3): var space = get_world_3d().direct_space_state var query = PhysicsRayQueryParameters3D.create(from, to, 2) query.collide_with_areas = true return space.intersect_ray(query) # Calculating the place_to_land position and initiating the vault animation. func vaulting(delta): if Input.is_action_just_pressed("jump"): # Player's RayCast to detect climbable areas. var start_hit = raycast(camera.transform.origin, camera.to_global(Vector3(0, 0, -1))) if start_hit and obstacles.is_empty(): # RayCast to detect the perfect place to land. (Not that special, I just exaggerate :D) var place_to_land = raycast(start_hit.position + self.to_global(Vector3.FORWARD) * collision_shape.shape.radius + (Vector3.UP * collision_shape.shape.height), Vector3.DOWN * (collision_shape.shape.height)) if place_to_land: # Playing the animation vault_animation(place_to_land) # Animation for vaulting/climbing. func vault_animation(place_to_land): # Player is climbing. This variable prevents hiccups along the process of climbing. is_climbing = true # First Tween animation will make player move up. var vertical_climb = Vector3(global_transform.origin.x, place_to_land.position.y, global_transform.origin.z) # If your player controller's pivot is located in the middle use this: # var vertical_climb = Vector3(global_transform.origin.x, (place_to_land.position.y + collision_shape.shape.height / 2), global_transform.origin.z) var vertical_tween = get_tree().create_tween().set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN) vertical_tween.tween_property(self, "global_transform:origin", vertical_climb, 0.4) # We wait for the animation to finish. await vertical_tween.finished # Second Tween animation will make the player move forward where the player is facing. var forward = global_transform.origin + (-self.basis.z * 1.2) var forward_tween = get_tree().create_tween().set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT) forward_tween.tween_property(self, "global_transform:origin", forward, 0.3) # We wait for the animation to finish. await forward_tween.finished # Player isn't climbing anymore. is_climbing = false # Obstacle detection above the head. func _on_obstacle_detector_body_entered(body): if body != self: obstacles.append(body) func _on_obstacle_detector_body_exited(body): if body != self : obstacles.remove_at(obstacles.find(body))