'''移动控制组件 注:该组件只适用于继承自CharaterBody2D的角色。 基础移动控制: 外部调用移动: add_movement_input() stop_movement() 停止运动,可以根据输入的Flag来决定是否要完全打断运动 内部调用: speed_approach() 此函数将当前速度趋近向目标速度 ''' class_name LocomotionComponent extends ComponentBase @export_category("Component Setting") ##如果设置为false,则角色将完全不能移动 @export var _can_move : bool = true @export_category("Gravity Properties") @export_subgroup("Fall") ##角色受到的重力乘量 @export var default_gravity_scale: float = 1.0 ##如果设置为false,则角色将不会应用重力 @export var should_apply_gravity : bool = true ##角色下落的最大速度 @export var fall_maxium_speed : float = 480 ##当空中速度超过了阈值速度时,降速的加速度 @export var fall_reduce_acceleration: float = 4000 ##空中控制乘量,在空中移动时修改 @export var air_control_mult : float = .65 #基础的乘量,对所有Character相同 const GRAVITY_BASIC_MULT_FACTOR : float = 2.1 #下坠速度和最大下坠速度的阈值,超过了这个阈值会开启下坠速度修正,主要取决于设备的刷新率固写死。 const FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD = 40 @export_category("Locomotion Properties") @export_subgroup("Move") ##存在輸入時,向最大移動輸入運動的加速度 @export var run_accel : float = 1200 ##不存在輸入時,向Vector.ZERO運動的加速度 @export var run_reduce : float = 1600 ##移動時的最大速度 @export var move_speed_max : float = 280 var characterbody: CharacterBody2D var _movement_input : float = 0.0 var _was_moving : bool = false var _in_pivoting : bool = false var last_frame_character_on_floor: bool = false var is_first_update: bool = false signal move_dir_changed_in_moving(direction : float) signal start_move(direction : float) signal ground_state_changed(is_leave: bool) func _init_component() -> void: characterbody = component_owner as CharacterBody2D assert(characterbody,"组件没有正确的绑定CharacterBody2D") #region 外部调用函数 ##外部每帧调用这个方法,以让角色移动 func add_movement_input(input : float) -> void: _movement_input = input #外部调用这个函数,强制让角色停住 func stop_movement(force_static: bool = false) -> void: if force_static: #如果希望角色强制停下,则设置为真 characterbody.velocity = Vector2.ZERO characterbody.move_and_slide() # 立即重置输入状态 _movement_input = 0.0 func suspend_movement() -> void: characterbody.velocity = Vector2.ZERO characterbody.move_and_slide() _can_move = false func enable_movement() -> void: _can_move = true #endregion func _physics_process(delta: float) -> void: if !_can_move : return #不允许移动,直接停止退出 _update_movement(delta) #更新移动输入 _update_gravity(delta) #更新重力 characterbody.move_and_slide() #移动更新 _handle_body_ground_state(delta) ##更新重力相关的函数 func _update_gravity(delta: float) -> void: var speed_on_gravity_dir = characterbody.velocity.dot(characterbody.get_gravity().normalized()) if should_apply_gravity and speed_on_gravity_dir <= _get_max_fall_speed(): characterbody.velocity += characterbody.get_gravity() * GRAVITY_BASIC_MULT_FACTOR * _get_gravity_scale() * delta if (abs(characterbody.velocity.dot(characterbody.get_gravity().normalized()))- _get_max_fall_speed()) > FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD: characterbody.velocity = velocity_approach(characterbody.velocity,characterbody.get_gravity().normalized() * _get_max_fall_speed(), fall_reduce_acceleration * delta) ##更新移动相关的函数 func _update_movement(delta : float) -> void: var input_dir = sign(_movement_input) as float var current_dir = sign(characterbody.velocity.x) as float var accel: float if abs(characterbody.velocity.x) > move_speed_max and input_dir * current_dir <= 0 and input_dir != 0: accel = run_reduce #如果移动方向和当前的速度方向不同向,且不是起步,那么apply 减速的加速度。 else: accel = run_accel #其他case都是加速的加速度 #如果检测到当前速度和加速度方向不同,则发送移动方向更改event if current_dir * input_dir < 0 and not _in_pivoting: move_dir_changed_in_moving.emit(input_dir) _in_pivoting = true if current_dir * input_dir >= 0: _in_pivoting = false var applyed_air_control = 1 if characterbody.is_on_floor() else air_control_mult var target_move_speed = move_speed_max * input_dir * applyed_air_control characterbody.velocity.x = speed_approach(characterbody.velocity.x,target_move_speed,accel * delta) #检测是否这帧开始了移动 _check_is_start_move() ##用来检测玩家是否当前帧改变了状态。 func _handle_body_ground_state(delta: float) -> void: if is_first_update: is_first_update = false last_frame_character_on_floor = characterbody.is_on_floor() return if last_frame_character_on_floor != characterbody.is_on_floor(): ground_state_changed.emit(last_frame_character_on_floor) last_frame_character_on_floor = characterbody.is_on_floor() func _get_gravity_scale() -> float: return default_gravity_scale func _get_max_fall_speed() -> float: return fall_maxium_speed # -----------HELPER----------- ##将一个速度持续Lerp向目标速度 func speed_approach(current: float, target: float, delta: float) -> float: if current < target: return min(current + delta, target) elif current > target: return max(current - delta, target) return target ##将一个向量速度持续lerp向目标向量速度 func velocity_approach(current: Vector2, target: Vector2, delta: float) -> Vector2: var result = current result.x = speed_approach(current.x, target.x, delta) result.y = speed_approach(current.y, target.y, delta) return result ##用于检测是否开始移动 func _check_is_start_move() -> void: # 判断是否开始移动 var is_moving_now = not is_zero_approx(characterbody.velocity.x) if is_moving_now and not _was_moving: start_move.emit(sign(characterbody.velocity.x)) # 发送开始移动事件 _was_moving = is_moving_now # -----------HELPER-----------