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

200 lines
7.0 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'''移动控制组件
该组件只适用于继承自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 = 18
#下坠速度和最大下坠速度的阈值,超过了这个阈值会开启下坠速度修正,主要取决于设备的刷新率固写死。
const FALL_SPEED_EXCEED_TOLERANCE_THRESHOLD = 40
@export_category("Locomotion Properties")
@export_subgroup("Move")
##如果为true则使用加速度逐渐达到目标速度如果为false则直接设置速度
@export var use_acceleration: bool = true
##存在輸入時,向最大移動輸入運動的加速度
@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
var _current_acceleration: Vector2
var _last_frame_character_velocity: Vector2
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() #移动更新
## 更新角色x轴上的加速度
_current_acceleration = (characterbody.velocity - _last_frame_character_velocity) / delta
_last_frame_character_velocity = characterbody.velocity
_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 = _get_acceleration(input_dir,current_dir)
#如果检测到当前速度和加速度方向不同则发送移动方向更改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
if use_acceleration:
characterbody.velocity.x = speed_approach(
characterbody.velocity.x,
target_move_speed,
applyed_air_control * accel * delta
)
else:
# 直接设置速度,无加速度
characterbody.velocity.x = target_move_speed
#检测是否这帧开始了移动
_check_is_start_move()
##获取玩家当前帧的加速度
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都是加速的加速度
##用来检测玩家是否当前帧改变了状态。
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-----------