From d20d45fffcbbe40cba76a44121b03472aefc5e3f Mon Sep 17 00:00:00 2001 From: yvmou_ Date: Fri, 9 Oct 2026 15:32:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8B=96=E6=8B=BD=E6=97=B6?= =?UTF-8?q?=E7=89=A9=E4=BD=93=E4=B8=80=E5=8D=A1=E4=B8=80=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .workbuddy/memory/2026-10-09.md | 7 +++++ character/player/player.gd | 3 ++- character/player/player_pickup.gd | 43 +++++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/.workbuddy/memory/2026-10-09.md b/.workbuddy/memory/2026-10-09.md index 9bb158f..6a697bb 100644 --- a/.workbuddy/memory/2026-10-09.md +++ b/.workbuddy/memory/2026-10-09.md @@ -47,3 +47,10 @@ - `player_pickup.gd`:信号由 `hover_changed(bool)` 改为 `interact_state_changed(hovering, holding)`,`_update_hover()` 改名 `_update_interact_state()` 并同时跟踪 `_holding`。 - `player.gd`:handler 改名 `_on_pickup_interact_state_changed(false)`,只负责把两个布尔量收敛成 `Crosshair.GrabState`(优先级 抓着 > 指着 > 没有)。 - 待用户确认:像素手在非整数倍分辨率缩放下发糊的话,需把 Crosshair 的 `texture_filter` 设为 Nearest。 + +### 拖拽时物体一卡一卡(已修) +- **根因(几何 + 控制律)**:`physics_step()` 用 `v = to_hold * pick_power` 是一阶跟随,人匀速前进时物体必有稳态滞后,滞后距离 = 移动速度 / pick_power。当时为 `walk_speed 5 ÷ pick_power 10 = 0.5 m`,而持握点离身体仅 `0.44 m`(InteractionPos local z = -0.4439,Player 缩放 0.2 × Camera 缩放 5 = 净 1.0,故世界距离就是 0.444 m)→ 物体退进玩家胶囊体 → 物理推出 / 拖拽拉回互相打架 → 高频抖动。 +- **修复 1(前馈)**:`desired = to_hold * pick_power + _body.velocity` —— 匀速行走时滞后归零。这是根治。 +- **修复 2(碰撞例外)**:`_grab()` 里 `_current.add_collision_exception_with(_body)`,`_drop()` / `_throw()` 经 `_forget()` 用 `remove_collision_exception_with` 解除。只解除「物体 ↔ 玩家」这一对,物体与场景其他碰撞体照常交互。 +- 另加 `@export var max_drag_speed: float = 30.0` 速度上限,物体离得远时不会被一股脑拽飞。 +- **接口变更**:`PlayerPickup.setup(ray, hold_pos, body)` 多了第三个参数(玩家本体),`player.gd` 里传 `self`。`_grab()` 加了 `is_instance_valid(_current)` 早退,避免重复抓取时漏掉碰撞例外。 diff --git a/character/player/player.gd b/character/player/player.gd index f9d3627..2750310 100644 --- a/character/player/player.gd +++ b/character/player/player.gd @@ -43,7 +43,8 @@ func _ready() -> void: motor.setup(self, camera_rig) pickup.setup( camera.get_node("InteractionRay") as RayCast3D, - camera.get_node("InteractionPos") as Marker3D + camera.get_node("InteractionPos") as Marker3D, + self ) # 准心接线:拾取组件一发现交互状态变化,就通知准心换外观(点 / 手 / 抓)。 pickup.interact_state_changed.connect(_on_pickup_interact_state_changed) diff --git a/character/player/player_pickup.gd b/character/player/player_pickup.gd index 8581054..2c03c64 100644 --- a/character/player/player_pickup.gd +++ b/character/player/player_pickup.gd @@ -5,12 +5,15 @@ extends Node ## 松开左键即原地放开,按 Q 则沿视线方向把手上的物体甩出去。 ## 只管「抓 / 拖 / 放 / 甩」,不碰移动与视角。 ## -## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 注入射线与持握点。 +## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 注入射线、持握点与玩家本体。 ## 拖拽力度:物体被拉向持握点的速度系数。越大跟手越紧,但过大容易来回抖。 +## 注意:它同时决定了拖拽时的稳态滞后 —— 滞后距离 = 移动速度 / pick_power。 @export var pick_power: float = 10.0 ## 投掷初速度(米/秒):按 Q 甩出去时给物体的速度大小。 @export var throw_speed: float = 12.0 +## 拖拽速度上限(米/秒)。防止物体离得远时被一股脑拽飞。 +@export var max_drag_speed: float = 30.0 ## 交互状态变化时发出。 ## hovering = 准心正指着可拖拽的刚体(还没抓);holding = 正抓着东西。 @@ -20,15 +23,18 @@ signal interact_state_changed(hovering: bool, holding: bool) var _current: RigidBody3D = null var _ray: RayCast3D var _hold_pos: Marker3D +## 玩家本体。用来给被拖物体加碰撞例外,以及做速度前馈。 +var _body: CharacterBody3D ## 上一次发出的状态,用来只在「变化那一刻」发信号。 var _hovering: bool = false var _holding: bool = false -## 由 Player 在 _ready() 注入依赖。 -func setup(ray: RayCast3D, hold_pos: Marker3D) -> void: +## 由 Player 在 _ready() 注入依赖。body 是玩家本体(CharacterBody3D)。 +func setup(ray: RayCast3D, hold_pos: Marker3D, body: CharacterBody3D) -> void: _ray = ray _hold_pos = hold_pos + _body = body func _unhandled_input(event: InputEvent) -> void: @@ -55,8 +61,21 @@ func physics_step() -> void: # 那种情况下引用不是 null 而是「已被释放」,直接取 global_position 会报错。 if not is_instance_valid(_current) or not is_instance_valid(_hold_pos): return + var to_hold := _hold_pos.global_position - _current.global_position - _current.set_linear_velocity(to_hold * pick_power) + var desired := to_hold * pick_power + # 速度前馈:把玩家自身的速度加进去。 + # + # 只靠 to_hold * pick_power 的话,它是一个一阶跟随 —— 人匀速前进时物体必然 + # 落后一个稳态距离,大小 = 移动速度 / pick_power。按 5 / 10 算就是 0.5 米, + # 比持握点离身体的 0.44 米还远,于是走着走着物体就退进了玩家碰撞体里, + # 被物理推出来、又被拽回去,两边打架 —— 看起来就是「一卡一卡」。 + # 加上玩家速度后,匀速行走时滞后归零,物体稳稳待在身前。 + if _body != null: + desired += _body.velocity + if desired.length() > max_drag_speed: + desired = desired.normalized() * max_drag_speed + _current.set_linear_velocity(desired) ## 检测「指着可拖拽物 / 正抓着东西」这两个状态,**只在变化时**发信号。 @@ -77,20 +96,33 @@ func _update_interact_state() -> void: func _grab() -> void: - if _ray == null: + if _ray == null or is_instance_valid(_current): return var collider := _ray.get_collider() if collider is RigidBody3D: _current = collider as RigidBody3D + # 让被拖的物体和玩家本体**互不碰撞**。 + # 否则物体一旦挨到身体就会被物理推出,而拖拽又立刻把它拉回来 —— + # 两个解算器互相打架,表现就是持续的高频抖动。 + # 只解除「物体 ↔ 玩家」这一对,物体和场景里其他东西照样正常碰撞。 + if _body != null: + _current.add_collision_exception_with(_body) func _drop() -> void: # 故意不清零线速度:松手时保留拖动惯性,甩出去的手感更自然。 # 想要「就地放下、不再滑出去」的话,这里补一句: # if is_instance_valid(_current): _current.set_linear_velocity(Vector3.ZERO) + _forget(_current) _current = null +## 清掉抓取时留下的碰撞例外。物体可能已被销毁,所以先判有效性。 +func _forget(body: RigidBody3D) -> void: + if is_instance_valid(body) and _body != null: + body.remove_collision_exception_with(_body) + + ## 按 Q 投掷:沿视线方向把物体甩出去,随后立刻松手。 func _throw() -> void: if not is_instance_valid(_current) or not is_instance_valid(_hold_pos): @@ -101,6 +133,7 @@ func _throw() -> void: _current.set_linear_velocity(dir * throw_speed) # 想让手感更「物理」(重物飞得慢、轻物飞得远)可以换成给冲量: # _current.apply_central_impulse(dir * throw_speed) + _forget(_current) _current = null