Files
agame/character/player/player_motor.gd
T
2026-10-15 16:56:45 +08:00

129 lines
5.9 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 统一做。
## 这样「算速度 → 推进 → 播动画」的顺序永远固定,不受节点树顺序影响。
##
## 「走多快、蹲不蹲、跑不跑」由 hsm/ 的 LimboHSM(LimboAI)决定:
## 状态在 _enter 时写 target_speed / crouching / sprinting,
## 本组件只负责把这份「意图」用物理方式变成速度。
##
## 设计基准:**小人国玩法,角色身高约 0.2 m**。速度不按「人类等比缩放」推导,
## 直接按鼠类疾走的手感定标:走 ≈ 12 身长/秒、疾跑 ≈ 18 身长/秒、
## 跳高 ≈ 1.15 身高。速度/加速度按手感单独调即可,不必成套换算;
## 唯独 jump_velocity 与 gravity_scale 是**配对**的(反解公式见重力一节)。
## 跳跃输入缓冲(秒):按下跳跃后这段时间内若落地,自动补上一次起跳
const JUMP_BUFFER_TIME: float = 0.1
@export_group("移动")
## 常规移动速度(米/秒)—— ≈ 12 身长/秒(0.2 m 身高),小鼠疾走的步频感
@export var walk_speed: float = 2.419
## 疾跑速度(米/秒)—— ≈ 18 身长/秒
@export var sprint_speed: float = 3.624
## 蹲行速度(米/秒)—— 走速的一半。蹲下时按住 Shift 也不疾跑(蹲伏优先)。
@export var crouch_speed: float = 1.21
## 地面加速度(米/秒²)—— 约 0.11 秒加到走速全速,起步干脆(鼠类的一窜一动)
@export var acceleration: float = 22.0
## 空中加速度(米/秒²)—— 约地面的 1/4:空中能修正方向,但不至于完全失控
@export var air_acceleration: float = 5.9
## 起跳初速度(米/秒)—— 配 gravity_scale 0.5(有效 g = 4.9)时,
## 跳高 ≈ 0.23 m(约 1.15 身高)、滞空 ≈ 0.61 s。**与 gravity_scale 配对**,见下。
@export var jump_velocity: float = 1.5
@export_group("重力")
## 重力缩放系数。0.5 = 有效重力 4.9 m/s²。
##
## 小个子角色若用真实 9.8,同样的起跳速度滞空短一半、跳起来像被拍回去 ——
## 降重力换滞空,跳跃才有「腾空感」。0.5 时:跳高 0.23 m、滞空 0.61 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
## 当前目标水平速度(米/秒)—— 由 HSM 状态在 _enter 时写入,step() 朝它加减速。
## Idle / Crouch 写 0(自然减速到停),Walk / Sprint / Sneak 写各自速度。
var target_speed: float = 0.0
## 是否蹲着 / 是否疾跑 —— 由 HSM 状态写入的**事实镜像**,本组件不再自己读输入推断:
## crouching 给 Look 压低摄像机,两者都给 DebugPanel 显示。
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
## 本帧是否按着方向键。HSM 状态(Idle/Walk 等的转换判定)与 step() 共用这个口径。
func has_move_input() -> bool:
return Input.get_vector("move_left", "move_right", "move_back", "move_forward") \
.length_squared() > 0.000001
## 由 HSM 地面状态每帧调用:跳跃缓冲还有效且脚下有地板,就当场起跳并返回 true。
## 起跳「动作」放在状态判定处执行(而不是 step() 里),
## 状态机同一帧就能拿到 vy > 0 这个事实、转进 Jump,动画不迟到一帧。
func try_jump() -> bool:
if _jump_buffer > 0.0 and _body != null and _body.is_on_floor():
_body.velocity.y = jump_velocity
_jump_buffer = 0.0
return true
return false
## 每物理帧由 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
# --- 跳跃缓冲倒计时(执行挪到 try_jump(),由 HSM 地面状态调用)---
if _jump_buffer > 0.0:
_jump_buffer -= delta
# --- 相对视线方向的水平输入 ---
# 只用 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()
# 目标速度来自 HSM 状态(target_speed),不在本组件里按蹲键/Shift 分档。
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