Files
agame/character/player/player_motor.gd
T
2026-10-09 16:00:23 +08:00

122 lines
5.6 KiB
GDScript
Raw 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.
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';跳跃那两个数(gravity_scale 与 jump_velocity)用下面的反解公式重算。
##
## 常规移动速度(米/秒)—— 约 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
## 起跳初速度(米/秒)—— 配下面的 gravity_scale 0.5(g = 4.9)时,
## 跳跃高度 ≈ 0.230 m(约 1.53 身高)、滞空 ≈ 0.61 s。
## 这两个数是**配对**的:改重力必须按「反解公式」重算它,否则跳跃高度按 1/g 暴涨。
@export var jump_velocity: float = 1.5
@export_group("重力")
## 重力缩放系数。0.5 = 重力减半(有效 g = 4.9 m/s²)。
##
## 为什么不用 1.0(物理真实的 9.8):重力加速度本身和物体大小无关,但我们的速度是按身材
## 等比缩过的,于是落体相对过快。更关键的是跳跃 —— 由 h = v²/2g 和 T = 2v/g 可得
## h = g·T²/8:**固定重力下滞空翻倍、高度要涨 4 倍**。所以「想跳得久又不想跳太高」
## 只能降重力。取 0.5 时:跳高 0.230 m(1.53 身高)、滞空 0.61 s;
## 若维持 1.0,同样 0.23 m 的高度只有 0.43 s 滞空,落地明显仓促。
##
## ⚠ 改这个值必须同步改 jump_velocity(跳跃高度 = v²/2g)。
## 反解公式 —— 定目标高度 h(米)与滞空 T(秒):
## g = 8h / T² → gravity_scale = g / 9.8
## jump_velocity = 4h / T
##
## 只影响本角色。**不要**去改 ProjectSettings 的 physics/3d/default_gravity ——
## 那是全局的,会连带把拖拽/投掷的刚体一起变飘。
@export_range(0.0, 2.0, 0.01) var gravity_scale: float = 0.5
## 本帧是否在疾跑 / 是否按住蹲键。
## Player 读这两个值转交给 PlayerAnimator 切片段,crouching 还用来压低摄像机。
var sprinting: bool = false
var crouching: bool = false
var _body: CharacterBody3D
var _look_rig: Node3D
## 项目设置里的默认重力(9.8)。实际用的是它 × gravity_scale。
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:
# gravity_scale 每帧现读,方便运行中拖着试手感(1.0 = 物理真实的 9.8)
_body.velocity.y -= _gravity * gravity_scale * 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