人物控制器1

This commit is contained in:
2026-10-09 15:51:24 +08:00
parent d20d45fffc
commit 96e21757c3
6 changed files with 140 additions and 25 deletions
+41 -2
View File
@@ -1,7 +1,7 @@
class_name PlayerLook
extends Node
## 视角组件:把鼠标位移转成 CameraRig 的 yaw 与 Camera 的 pitch。
## 只管转摄像机 —— 不碰速度、不碰动画。
## 视角组件:把鼠标位移转成 CameraRig 的 yaw 与 Camera 的 pitch,
## 并按蹲伏状态平滑升降摄像机。只管摄像机 —— 不碰速度、不碰动画。
##
## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 调 setup() 注入依赖。
@@ -13,6 +13,12 @@ extends Node
## 最高仰角(向上看)
@export var pitch_max_deg: float = 89.0
@export_group("蹲伏")
## 蹲下时摄像机高度 = 站立高度 × 这个比例
@export_range(0.3, 1.0, 0.05) var crouch_height_ratio: float = 0.6
## 蹲起过渡速度。这是「每秒收敛掉的比例」,不是米/秒 —— 越大越干脆。
@export var crouch_transition_speed: float = 10.0
## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
## 分开是为了让 pitch 不掺进移动方向 —— 移动只用 rig 的 basis,低头看地也不会走歪。
var _yaw: float = 0.0
@@ -22,6 +28,15 @@ var _rig: Node3D
var _camera: Camera3D
var _body: Node3D
## 站立时 CameraRig 的本地高度(从场景读,不写死),以及当前要收敛到的高度。
var _stand_height: float = 0.0
var _target_height: float = 0.0
func _ready() -> void:
# 依赖是 setup() 注入的 —— 没接线(远端玩家不会调 setup)就不该跑 _process。
set_process(false)
## 由 Player 在 _ready() 注入依赖。组件不自己 get_node,才能脱离具体场景复用。
func setup(rig: Node3D, camera: Camera3D, body: Node3D) -> void:
@@ -30,7 +45,11 @@ func setup(rig: Node3D, camera: Camera3D, body: Node3D) -> void:
_body = body
_yaw = rig.rotation.y
_pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
# 站立高度直接读场景里的 CameraRig —— 以后在编辑器里把它挪高挪低,这里自动跟上。
_stand_height = rig.position.y
_target_height = _stand_height
_apply()
set_process(true)
func _unhandled_input(event: InputEvent) -> void:
@@ -44,6 +63,26 @@ func _unhandled_input(event: InputEvent) -> void:
_apply()
## 由 Player 每物理帧调用,把「是否蹲着」这个事实转成目标高度。
## 只在目标真的变了才写入,避免每帧无谓赋值。
func set_crouching(crouching: bool) -> void:
if _rig == null:
return
var target: float = _stand_height * crouch_height_ratio if crouching else _stand_height
if is_equal_approx(target, _target_height):
return
_target_height = target
## 蹲起过渡放在渲染帧而不是物理帧 —— 物理帧只有 60Hz,看起来会一顿一顿。
## 用指数收敛(1 - e^(-k·delta))而不是定量 lerp:过渡时间不受帧率影响。
func _process(delta: float) -> void:
if _rig == null:
return
var t := 1.0 - exp(-crouch_transition_speed * delta)
_rig.position.y = lerpf(_rig.position.y, _target_height, t)
## 把 _yaw / _pitch 落到节点上。鼠标一动就调一次(渲染帧),所以视角跟手、不等物理帧。
## 身体跟着一起转 —— 第一人称里「你看哪,身体就朝哪」,别人看你也是这个朝向。
func _apply() -> void: