拾起物品同步

This commit is contained in:
2026-10-03 18:48:04 +08:00
parent 0dcfb88bfe
commit 3d23804fa5
10 changed files with 359 additions and 86 deletions
+51 -15
View File
@@ -39,8 +39,8 @@ const JUMP_BUFFER_FRAMES: int = 6
@export_group("视角")
## 眼睛高度(米)。相机就架在这个高度上 —— 按碰撞胶囊定,跟模型高矮无关。
@export var eye_height: float = 1.6
## 第一人称下藏掉自己的模型。不藏的话相机要么在模型里、要么贴着头顶,画面糊一片。
## 想做成「看得见自己身体」的第一人称,就把这个关掉、并把 eye_height 降到模型头部高度。
## 第一人称下藏掉自己的身体。**两条胳膊会留下来** —— 抱东西时得看得见自己的手。
## 想连胳膊也看不见就把这个关掉(那样相机要么在模型里、要么贴着头顶,画面会糊一片)。
## 只影响本机这一份 —— 远端玩家的模型照常显示。
@export var hide_own_body: bool = true
## 鼠标灵敏度(弧度/像素)
@@ -73,6 +73,8 @@ const JUMP_BUFFER_FRAMES: int = 6
@export var throw_charge_time: float = 0.8
## 扔出去时额外叠上玩家自身的速度(跑着扔会飞更远)
@export var throw_inherits_velocity: bool = true
## 等服务器确认抱起的宽限时间(秒)。联机下被别人先抢走时,超时就自动松手
@export var pickup_confirm_time: float = 0.6
@onready var body: Node3D = $Body
## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
@@ -83,9 +85,9 @@ const JUMP_BUFFER_FRAMES: int = 6
@onready var _health_label: Label3D = $HealthLabel
## 滑索操作提示,同样在 player.tscn 里配好,默认隐藏
@onready var _hint: Label3D = $InteractHint
## 抱持点:抱起来的物品每物理帧摆到它上面
@onready var _carry_socket: Node3D = $CameraRig/CarrySocket
## 抱东西时的胳膊表现层(挂 carry_arms.gd):只收「抱没抱、走多快」
## 抱东西时的胳膊表现层(挂 carry_arms.gd):只收「抱没抱、走多快」。
## 抱持点(CameraRig/CarrySocket)不在这里引用 —— 物品自己按名字找持有者的抱持点,
## 这样每端算出来的位置都一样,联机不用同步物品位置。
@onready var _carry_arms: CarryArms = $Body/CarryArms
## 动画状态机(挂在 Body/AnimationPlayer 上)。只把「运动结果」交给它,也只知道它会自己播动画。
@@ -110,12 +112,14 @@ var _zip_speed: float = 0.0
## 挂上瞬间「人相对缆」的偏移,随后衰减到 0 —— 吸附是滑过去的,不是瞬移
var _zip_grab_offset: Vector3 = Vector3.ZERO
## 手上抱着的物品。非 null 时每物理帧把它摆到抱持点上。
## 只在权威端写;远端玩家的物品位置由物品自己的 Sync 复制过来。
## 手上抱着的物品。位置由物品自己跟住抱持点(见 Carryable),玩家不插手 ——
## 这样每端算出来的位置都一样,联机不用同步物品位置。
var _carried: Carryable = null
## 长按 Q 的蓄力进度 0~1
var _throw_charge: float = 0.0
var _throw_charging: bool = false
## 等服务器确认抱起的剩余时间。服务器没给(被别人先抢了)就自己松手
var _pickup_wait: float = 0.0
func _ready() -> void:
@@ -134,7 +138,7 @@ func _ready() -> void:
return
camera_rig.position.y = eye_height
if hide_own_body:
body.visible = false
_hide_body_keep_arms()
_yaw = camera_rig.rotation.y
_pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
_apply_look()
@@ -143,6 +147,25 @@ func _ready() -> void:
_refresh_health_label()
## 第一人称:藏掉身体,但**把两条胳膊留下** —— 抱东西时得看得见自己的手。
## 模型各部位是独立 mesh,按名字挑出来即可;名字对不上(换模型了)就退化成整个藏掉。
func _hide_body_keep_arms() -> void:
var stack: Array[Node] = [body]
var kept := 0
while not stack.is_empty():
var node: Node = stack.pop_back()
for child in node.get_children():
stack.append(child)
var mesh := child as MeshInstance3D
if mesh == null:
continue
var is_arm := str(mesh.name).begins_with("arm_")
mesh.visible = is_arm
kept += 1 if is_arm else 0
if kept == 0:
body.visible = false
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
_yaw -= event.relative.x * mouse_sensitivity
@@ -218,11 +241,15 @@ func _physics_process(delta: float) -> void:
else:
_hint.visible = false
# 抱着东西:每帧把它摆到抱持点上,并推进 Q 的蓄力。
# 物品的位置由持有者写 —— 远端玩家的那份由物品自己的 Sync 复制过去。
# 抱着东西:推进 Q 的蓄力。物品位置不归玩家管 —— 它自己跟住抱持点(见 Carryable),
# 每端都这么算,所以联机不用同步物品位置。
if _carried != null:
_carried.apply_hold_transform(_carry_socket)
if _throw_charging:
if _carried.carrier_peer != multiplayer.get_unique_id():
# 服务器还没确认,或者被别人先抢走了:给一小段时间,超时就松手
_pickup_wait -= delta
if _pickup_wait <= 0.0:
_carried = null
elif _throw_charging:
_throw_charge = minf(1.0, _throw_charge + delta / maxf(throw_charge_time, 0.01))
# 胳膊表现层只收「抱没抱、走多快」,摆成什么样是它自己的事
_carry_arms.set_carrying(
@@ -310,15 +337,20 @@ func _find_carryable() -> Carryable:
## 抱起最近的可抱物品。手上已经有东西、或者正挂在缆上都不抱。
## 实际「谁抱到了」由服务器仲裁(先到先得),这里只是发起请求并乐观地先认领,
## 服务器不给就在 _physics_process 里超时松手。
func _pick_up() -> void:
if _carried != null or _zipline != null:
return
var item := _find_carryable()
if item == null:
return
item.begin_carry(self)
item.apply_hold_transform(_carry_socket)
_carried = item
_pickup_wait = pickup_confirm_time
if multiplayer.is_server():
item.request_pickup(multiplayer.get_unique_id())
else:
item.request_pickup.rpc_id(1, multiplayer.get_unique_id())
## 松手扔出:方向就是视线方向,速度按蓄力插值(轻点也能扔出去,只是近)。
@@ -334,7 +366,11 @@ func _release_throw() -> void:
if throw_inherits_velocity:
launch += velocity
_throw_charge = 0.0
item.end_carry(launch)
# 出手速度交给服务器:它收回模拟权,让物品带着这个速度接着飞
if multiplayer.is_server():
item.request_release(launch)
else:
item.request_release.rpc_id(1, launch)
# ---------------- 滑索 ----------------