godot-plateformer/addons/reedcomponent/locomotion/locomotion_component.gd

194 lines
6.7 KiB
GDScript3
Raw Permalink Normal View History

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-05 17:38:43 +08:00
const GRAVITY_BASIC_MULT_FACTOR : float = 1.8
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-----------