2025-12-29 11:54:31 +08:00
|
|
|
|
'''移动控制组件
|
|
|
|
|
|
注:该组件只适用于继承自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相同
|
2026-01-16 13:22:02 +08:00
|
|
|
|
const GRAVITY_BASIC_MULT_FACTOR : float = 18
|
2025-12-29 11:54:31 +08:00
|
|
|
|
#下坠速度和最大下坠速度的阈值,超过了这个阈值会开启下坠速度修正,主要取决于设备的刷新率固写死。
|
|
|
|
|
|
const FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD = 40
|
|
|
|
|
|
|
|
|
|
|
|
@export_category("Locomotion Properties")
|
|
|
|
|
|
@export_subgroup("Move")
|
2026-01-02 18:37:09 +08:00
|
|
|
|
##存在輸入時,向最大移動輸入運動的加速度
|
2025-12-29 11:54:31 +08:00
|
|
|
|
@export var run_accel : float = 1200
|
2026-01-02 18:37:09 +08:00
|
|
|
|
##不存在輸入時,向Vector.ZERO運動的加速度
|
2025-12-29 11:54:31 +08:00
|
|
|
|
@export var run_reduce : float = 1600
|
2026-01-02 18:37:09 +08:00
|
|
|
|
##移動時的最大速度
|
2025-12-29 11:54:31 +08:00
|
|
|
|
@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
|
2026-01-10 14:04:34 +08:00
|
|
|
|
var _current_acceleration: Vector2
|
|
|
|
|
|
var _last_frame_character_velocity: Vector2
|
2025-12-29 11:54:31 +08:00
|
|
|
|
|
|
|
|
|
|
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() #移动更新
|
2026-01-10 14:04:34 +08:00
|
|
|
|
|
|
|
|
|
|
## 更新角色x轴上的加速度
|
|
|
|
|
|
_current_acceleration = (characterbody.velocity - _last_frame_character_velocity) / delta
|
|
|
|
|
|
_last_frame_character_velocity = characterbody.velocity
|
2025-12-29 11:54:31 +08:00
|
|
|
|
_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
|
2026-01-05 15:28:43 +08:00
|
|
|
|
var current_dir = sign(characterbody.velocity.x) as float
|
2026-01-10 14:04:34 +08:00
|
|
|
|
var accel: float = _get_acceleration(input_dir,current_dir)
|
2025-12-29 11:54:31 +08:00
|
|
|
|
|
|
|
|
|
|
#如果检测到当前速度和加速度方向不同,则发送移动方向更改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
|
2026-01-10 14:04:34 +08:00
|
|
|
|
var target_move_speed = move_speed_max * input_dir
|
2025-12-29 11:54:31 +08:00
|
|
|
|
|
2026-01-10 14:04:34 +08:00
|
|
|
|
characterbody.velocity.x = speed_approach(
|
|
|
|
|
|
characterbody.velocity.x,
|
|
|
|
|
|
target_move_speed,
|
|
|
|
|
|
applyed_air_control * accel * delta
|
|
|
|
|
|
)
|
2025-12-29 11:54:31 +08:00
|
|
|
|
|
|
|
|
|
|
#检测是否这帧开始了移动
|
|
|
|
|
|
_check_is_start_move()
|
|
|
|
|
|
|
2026-01-10 14:04:34 +08:00
|
|
|
|
##获取玩家当前帧的加速度
|
|
|
|
|
|
func _get_acceleration(input_dir:float,current_dir:float) -> float:
|
|
|
|
|
|
if abs(characterbody.velocity.x) > move_speed_max or \
|
|
|
|
|
|
input_dir * current_dir < 0 or \
|
|
|
|
|
|
(input_dir == 0 and abs(characterbody.velocity.x) > 0):
|
|
|
|
|
|
#如果:
|
|
|
|
|
|
# 1. 速度超过极限速度
|
|
|
|
|
|
# 2. 移动方向和输入方向相反
|
|
|
|
|
|
# 3. 输入为0,但仍然有速度。(放开输入,等待停止)
|
|
|
|
|
|
#那么apply 减速的加速度。
|
|
|
|
|
|
return run_reduce
|
|
|
|
|
|
else:
|
|
|
|
|
|
return run_accel #其他case都是加速的加速度
|
|
|
|
|
|
|
2025-12-29 11:54:31 +08:00
|
|
|
|
##用来检测玩家是否当前帧改变了状态。
|
|
|
|
|
|
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-----------
|