194 lines
6.7 KiB
GDScript
194 lines
6.7 KiB
GDScript
'''移动控制组件
|
||
注:该组件只适用于继承自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 = 1.8
|
||
#下坠速度和最大下坠速度的阈值,超过了这个阈值会开启下坠速度修正,主要取决于设备的刷新率固写死。
|
||
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
|
||
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
|
||
|
||
characterbody.velocity.x = speed_approach(
|
||
characterbody.velocity.x,
|
||
target_move_speed,
|
||
applyed_air_control * accel * delta
|
||
)
|
||
|
||
#检测是否这帧开始了移动
|
||
_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-----------
|