class_name StepHandlerComponet extends Node @export_category("References") @export var player: PlayerController @export_category("Step Settings") @export var surface_threshold: float = 0.3 @export var step_height: float = 0.3 const FEET_ADJUSTED_HEIGHT = 0.05 const MIN_MOVEMENT_LENGTH: float = 0.1 const MIN_DOT_VALUE: float = 0.5 const MIN_STEP_HEIGHT: float = 0.1 func handle_step_climbing(): for i in player.get_slide_collision_count(): var collision = player.get_slide_collision(i) if _is_surface_verical(collision): var measured_height = _measure_step_height(collision) if measured_height > MIN_STEP_HEIGHT and measured_height <= step_height and _is_valid_step_direction(collision): player.global_position.y += measured_height player.velocity = player.previous_velocity player.camera.smooth_step(measured_height) func _is_valid_step_direction(collision: KinematicCollision3D): var collision_normal = collision.get_normal() var input_dir = player.get_input_direction() var movement_direction = player.transform.basis * Vector3(input_dir.x, 0, input_dir.y) if movement_direction.length() > MIN_MOVEMENT_LENGTH: movement_direction = movement_direction.normalized() var dot_product = movement_direction.dot(-collision_normal) return dot_product > MIN_DOT_VALUE return false func _measure_step_height(collision: KinematicCollision3D) -> float: var space_state = player.get_world_3d().direct_space_state var collision_point = collision.get_position() var player_feet = _get_player_feet_position() var player_head_y = player.global_position.y + (player.standing_collision.shape.height / 2) var ray_start = Vector3(collision_point.x, player_head_y, collision_point.z) var ray_end = Vector3(collision_point.x, player_feet.y, collision_point.z) var query = PhysicsRayQueryParameters3D.create(ray_start, ray_end) query.collision_mask = player.collision_mask query.exclude = [player.get_rid()] var result = space_state.intersect_ray(query) if result: return result.position.y - player_feet.y return 0.0 func _is_surface_verical(collision: KinematicCollision3D) -> bool: var normal = collision.get_normal() if abs(normal.y) <= surface_threshold: return true return _check_collison_surface(collision) # additional work to validate func _check_collison_surface(collision: KinematicCollision3D) -> bool: var space_state = player.get_world_3d().direct_space_state var collision_point = collision.get_position() var player_feet: Vector3 = _get_player_feet_position() collision_point.y = player_feet.y var query = PhysicsRayQueryParameters3D.create(player_feet, collision_point) query.collision_mask = player.collision_mask query.exclude = [player.get_rid()] var result = space_state.intersect_ray(query) if result and abs(result.normal.y) <= surface_threshold: return true return false func _get_player_feet_position() -> Vector3: var feet_pos = player.global_position feet_pos.y -= player.standing_collision.shape.height / 2 feet_pos.y += FEET_ADJUSTED_HEIGHT return feet_pos