Files
2026-10-09 15:51:24 +08:00

94 lines
3.5 KiB
GDScript
Raw Permalink 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 PlayerLook
extends Node
## 视角组件:把鼠标位移转成 CameraRig 的 yaw 与 Camera 的 pitch,
## 并按蹲伏状态平滑升降摄像机。只管摄像机 —— 不碰速度、不碰动画。
##
## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 调 setup() 注入依赖。
@export_group("视角")
## 鼠标灵敏度(弧度/像素)
@export var mouse_sensitivity: float = 0.0025
## 最低俯角(向下看)
@export var pitch_min_deg: float = -89.0
## 最高仰角(向上看)
@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
var _pitch: float = 0.0
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:
_rig = rig
_camera = camera
_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:
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
_yaw -= event.relative.x * mouse_sensitivity
_pitch = clampf(
_pitch - event.relative.y * mouse_sensitivity,
deg_to_rad(pitch_min_deg),
deg_to_rad(pitch_max_deg)
)
_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:
if _rig == null:
return
_rig.rotation.y = _yaw
_camera.rotation.x = _pitch
_body.rotation.y = _yaw