改为第一人称控制器

This commit is contained in:
2026-10-02 10:34:57 +08:00
parent d367c8370b
commit 39790aecea
4 changed files with 87 additions and 41 deletions
+44 -32
View File
@@ -1,16 +1,15 @@
extends CharacterBody3D
## 第三人称控制器:只管「怎么动」—— 输入、移动物理、相机、血条、联机分叉。
## 第一人称控制器:只管「怎么动」—— 输入、移动物理、视角、血条、联机分叉。
## 表现层(动画)在 Body/AnimationPlayer 上(挂 player_animator.gd),本文件不认识任何片段名。
##
## 场景结构:
## Player (CharacterBody3D) ← 只负责移动与碰撞,自身不旋转
## ├─ Collision (CollisionShape3D, CapsuleShape3D)
## ├─ Body (Node3D) ← 视觉层,朝移动方向转身;换角色模型时挂在它下面
## ├─ CameraRig (Node3D) ← 眼睛,架在 eye_height 高,承担偏航 yaw
## │ └─ Camera (Camera3D) ← 承担俯仰 pitch(第一人称,没有弹簧臂)
## ├─ Body (Node3D) ← 视觉层,始终朝视线方向;换角色模型时挂在它下面
## │ ├─ mouse (Node3D) ← 模型(mouse.glb):MouseRig/Skeleton3D + 各部位 MeshInstance3D
## │ └─ AnimationPlayer ← 动作库 + 表现层(挂 player_animator.gd):只读运动、只写动画
## ├─ CameraRig (Node3D) ← 承担偏航 yaw,独立于本体旋转
## │ └─ SpringArm (SpringArm3D) ← 承担俯仰 pitch,自动避障
## │ └─ Camera (Camera3D)
## ├─ Sync (MultiplayerSynchronizer) ← 同步 position、Body.rotation、health、动画状态
## ├─ HealthLabel (Label3D) ← 头顶血条(节点配置,不是代码建的)
## └─ InteractHint (Label3D) ← 靠近滑索时的「按 E」提示,同样在场景里配好
@@ -30,6 +29,20 @@ const JUMP_BUFFER_FRAMES: int = 6
health = clampi(value, 0, 100)
_refresh_health_label()
@export_group("视角")
## 眼睛高度(米)。相机就架在这个高度上 —— 按碰撞胶囊定,跟模型高矮无关。
@export var eye_height: float = 1.6
## 第一人称下藏掉自己的模型。不藏的话相机要么在模型里、要么贴着头顶,画面糊一片。
## 想做成「看得见自己身体」的第一人称,就把这个关掉、并把 eye_height 降到模型头部高度。
## 只影响本机这一份 —— 远端玩家的模型照常显示。
@export var hide_own_body: bool = true
## 鼠标灵敏度(弧度/像素)
@export var mouse_sensitivity: float = 0.0025
## 最低俯角(向下看)。第一人称得能抬头低头,所以放到接近 ±90°
@export var pitch_min_deg: float = -89.0
## 最高仰角(向上看)
@export var pitch_max_deg: float = 89.0
@export_group("移动")
## 常规移动速度(米/秒)
@export var walk_speed: float = 5.0
@@ -41,20 +54,12 @@ const JUMP_BUFFER_FRAMES: int = 6
@export var air_acceleration: float = 12.0
## 起跳初速度(米/秒)
@export var jump_velocity: float = 5.0
## 视觉层转身平滑速度
@export var turn_speed: float = 10.0
@export_group("相机")
## 鼠标灵敏度(弧度/像素)
@export var mouse_sensitivity: float = 0.0025
## 最低俯角(向下看)
@export var pitch_min_deg: float = -80.0
## 最高仰角(向上看)
@export var pitch_max_deg: float = 45.0
@onready var body: Node3D = $Body
## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
## 分开是为了让 pitch 不掺进移动方向 —— 移动只用 rig 的 basis,低头看地也不会走歪。
@onready var camera_rig: Node3D = $CameraRig
@onready var spring_arm: SpringArm3D = $CameraRig/SpringArm
@onready var camera: Camera3D = $CameraRig/Camera
## 头顶血条是 player.tscn 里的 Label3D 节点(3D 世界里的文字,会跟着角色走)
@onready var _health_label: Label3D = $HealthLabel
## 滑索操作提示,同样在 player.tscn 里配好,默认隐藏
@@ -97,9 +102,12 @@ func _ready() -> void:
set_physics_process(false)
set_process_unhandled_input(false)
return
camera_rig.position.y = eye_height
if hide_own_body:
body.visible = false
_yaw = camera_rig.rotation.y
_pitch = clampf(spring_arm.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
spring_arm.rotation.x = _pitch
_pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
_apply_look()
set_mouse_captured(true)
# 同步可能在 _ready() 之前就把 health 写进来了,那时血条还没刷新,这里补一次
_refresh_health_label()
@@ -113,8 +121,7 @@ func _unhandled_input(event: InputEvent) -> void:
deg_to_rad(pitch_min_deg),
deg_to_rad(pitch_max_deg)
)
camera_rig.rotation.y = _yaw
spring_arm.rotation.x = _pitch
_apply_look()
elif event.is_action_pressed("jump"):
# 缆上按跳 = 主动脱手,额外给一个向上的小跳,方便够到旁边的平台
if _zipline != null:
@@ -140,6 +147,14 @@ func set_mouse_captured(captured: bool) -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if captured else Input.MOUSE_MODE_VISIBLE
## 把 _yaw / _pitch 落到节点上。鼠标一动就调一次(渲染帧),所以视角是跟手的、不等物理帧。
## 身体跟着一起转 —— 第一人称里「你看哪,身体就朝哪」,别人看你也是这个朝向。
func _apply_look() -> void:
camera_rig.rotation.y = _yaw
camera.rotation.x = _pitch
body.rotation.y = _yaw
func _physics_process(delta: float) -> void:
if not _is_local:
return
@@ -169,18 +184,17 @@ func _physics_process(delta: float) -> void:
velocity.y = jump_velocity
_jump_buffer = 0
# --- 相对相机的水平输入 ---
# --- 相对视线方向的水平输入 ---
# 只用 rig 的 basis(只有 yaw),低头看地时前进方向不会跟着往下扎。
var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
var cam_basis := camera_rig.global_transform.basis
var wish := cam_basis.x * input_dir.x + (-cam_basis.z) * input_dir.y
var look_basis := camera_rig.global_transform.basis
var wish := look_basis.x * input_dir.x + (-look_basis.z) * input_dir.y
wish.y = 0.0
var wants_move := wish.length() > 0.001
if wants_move:
wish = wish.normalized()
# 只转视觉层,不动本体,也不影响相机(Godot 前进方向为 -Z)
var target_yaw := atan2(-wish.x, -wish.z)
body.rotation.y = lerp_angle(body.rotation.y, target_yaw, clampf(turn_speed * delta, 0.0, 1.0))
# 身体朝向由 _apply_look() 跟着视线走 —— 第一人称不再朝移动方向转身
var speed := sprint_speed if Input.is_action_pressed("sprint") else walk_speed
if not wants_move:
@@ -280,12 +294,10 @@ func _ride_zipline(delta: float) -> void:
_zip_grab_offset = _zip_grab_offset.lerp(Vector3.ZERO, clampf(delta / 0.12, 0.0, 1.0))
global_position = _zipline.ride_point(_zip_t) + _zip_grab_offset
velocity = tan_dir * _zip_speed
var travel_yaw := atan2(-tan_dir.x, -tan_dir.z)
body.rotation.y = lerp_angle(body.rotation.y, travel_yaw, clampf(turn_speed * delta, 0.0, 1.0))
# 相机也跟着摆到行进方向背后 —— 否则人是横着飞的,看不见前面要去哪。
# 速率刻意比转身慢:玩家用鼠标看的视角不会被立刻拽回去。
_yaw = lerp_angle(_yaw, travel_yaw, clampf(3.0 * delta, 0.0, 1.0))
camera_rig.rotation.y = _yaw
# 视线跟着摆到行进方向 —— 否则人是横着飞的,看不见前面要去哪。
# 速率刻意放慢:玩家自己用鼠标看的视角不会被一下子拽回去。
_yaw = lerp_angle(_yaw, atan2(-tan_dir.x, -tan_dir.z), clampf(3.0 * delta, 0.0, 1.0))
_apply_look()
# 缆上没有「落地」概念,交给动画器当下落姿态处理(模型里没有专门的滑索片段)
animator.set_motion(velocity, false, false)
+3 -8
View File
@@ -24,15 +24,10 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CapsuleShape3D_rkbax")
[node name="CameraRig" type="Node3D" parent="." unique_id=1774966304]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.45, 0)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0)
[node name="SpringArm" type="SpringArm3D" parent="CameraRig" unique_id=917938711]
transform = Transform3D(1, 0, 0, 0, 0.9659257, 0.25881964, 0, -0.25881964, 0.9659257, 0, 0, 0)
spring_length = 4.0
[node name="Camera" type="Camera3D" parent="CameraRig/SpringArm" unique_id=52294557]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
fov = 82.2
[node name="Camera" type="Camera3D" parent="CameraRig" unique_id=52294557]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.6066488, -0.11179829)
[node name="Body" type="Node3D" parent="." unique_id=394283421]