101 lines
4.2 KiB
GDScript
101 lines
4.2 KiB
GDScript
class_name PlayerMotor
|
||
extends Node
|
||
## 移动组件:重力、跳跃缓冲、水平加减速。
|
||
##
|
||
## 只算速度写回 body.velocity,**不调 move_and_slide()** —— 推进由 Player 统一做。
|
||
## 这样「算速度 → 推进 → 播动画」的顺序永远固定,不受节点树顺序影响。
|
||
|
||
## 跳跃输入缓冲(秒):按下跳跃后这段时间内若落地,自动补上一次起跳
|
||
const JUMP_BUFFER_TIME: float = 0.1
|
||
|
||
@export_group("移动")
|
||
## 【设计基准】以人类为参照做等比缩放:人 1.75 m,游戏惯例 walk 5.0 / sprint 8.5 m/s。
|
||
## 本角色身高 0.15 m,系数 k = 0.15 / 1.75 ≈ 0.0857,下面所有线性量都是原值 × k。
|
||
## 这样「每秒走过几个自身体长」和人类一致,动作比例看起来才对。
|
||
##
|
||
## 换尺寸时的公式:线性量(速度/加速度)× k',跳跃初速度 × √k'(因为跳跃高度 = v²/2g)。
|
||
##
|
||
## 常规移动速度(米/秒)—— 约 2.9 身长/秒,和人类走路同一比例
|
||
@export var walk_speed: float = 0.4286
|
||
## 疾跑速度(米/秒)—— 约 4.9 身长/秒
|
||
@export var sprint_speed: float = 0.7286
|
||
## 蹲行速度(米/秒)—— 默认是走速的一半。蹲下时按住 Shift 也不会疾跑。
|
||
## 注意:改这个值要同步改 player_animator.gd 的 sneak_ref_speed,否则蹲走时脚步会打滑。
|
||
@export var crouch_speed: float = 0.2143
|
||
## 地面加速度(米/秒²)—— 约 0.11 秒加到全速,起步干脆
|
||
@export var acceleration: float = 3.8571
|
||
## 空中加速度(米/秒²)
|
||
@export var air_acceleration: float = 1.0286
|
||
## 起跳初速度(米/秒)—— 跳跃高度 ≈ 0.109 m,约 0.73 身高
|
||
## 注意是 ×√k 而不是 ×k:跳跃高度 = v² / 2g,想让高度也等比缩,速度就得开方。
|
||
@export var jump_velocity: float = 1.4639
|
||
|
||
## 本帧是否在疾跑 / 是否按住蹲键。
|
||
## Player 读这两个值转交给 PlayerAnimator 切片段,crouching 还用来压低摄像机。
|
||
var sprinting: bool = false
|
||
var crouching: bool = false
|
||
|
||
var _body: CharacterBody3D
|
||
var _look_rig: Node3D
|
||
var _gravity: float = 9.8
|
||
var _jump_buffer: float = 0.0
|
||
|
||
|
||
## 由 Player 在 _ready() 注入依赖。look_rig 用来把输入转成「相对视线」的方向。
|
||
func setup(body: CharacterBody3D, look_rig: Node3D) -> void:
|
||
_body = body
|
||
_look_rig = look_rig
|
||
_gravity = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
|
||
|
||
|
||
func _unhandled_input(event: InputEvent) -> void:
|
||
if event.is_action_pressed("jump"):
|
||
_jump_buffer = JUMP_BUFFER_TIME
|
||
|
||
|
||
## 每物理帧由 Player 调用一次,必须排在 move_and_slide() 之前。
|
||
## 只写 _body.velocity,不做位移推进。
|
||
func step(delta: float) -> void:
|
||
if _body == null or _look_rig == null:
|
||
return
|
||
|
||
# --- 重力 ---
|
||
if _body.is_on_floor():
|
||
if _body.velocity.y < 0.0:
|
||
_body.velocity.y = 0.0
|
||
else:
|
||
_body.velocity.y -= _gravity * delta
|
||
|
||
# --- 跳跃(读取输入缓冲)---
|
||
if _jump_buffer > 0.0:
|
||
_jump_buffer -= delta
|
||
if _body.is_on_floor():
|
||
_body.velocity.y = jump_velocity
|
||
_jump_buffer = 0.0
|
||
|
||
# --- 相对视线方向的水平输入 ---
|
||
# 只用 rig 的 basis(只有 yaw),低头看地时前进方向不会跟着往下扎。
|
||
var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
|
||
var look_basis := _look_rig.global_transform.basis
|
||
var wish := look_basis.x * input_dir.x - look_basis.z * input_dir.y
|
||
wish.y = 0.0
|
||
if wish.length_squared() > 0.000001:
|
||
wish = wish.normalized()
|
||
|
||
# 只有真正按了方向键才允许疾跑(避免原地"疾跑"触发跑动动画)
|
||
var wants_sprint := Input.is_action_pressed("sprint") and input_dir.length_squared() > 0.000001
|
||
crouching = Input.is_action_pressed("crouch")
|
||
# 蹲伏优先于疾跑:蹲着时按住 Shift 不算疾跑,速度取蹲行速度。
|
||
sprinting = wants_sprint and not crouching
|
||
var target_speed := walk_speed
|
||
if crouching:
|
||
target_speed = crouch_speed
|
||
elif sprinting:
|
||
target_speed = sprint_speed
|
||
var accel := acceleration if _body.is_on_floor() else air_acceleration
|
||
|
||
var horizontal := Vector2(_body.velocity.x, _body.velocity.z)
|
||
horizontal = horizontal.move_toward(Vector2(wish.x, wish.z) * target_speed, accel * delta)
|
||
_body.velocity.x = horizontal.x
|
||
_body.velocity.z = horizontal.y
|