修复拖拽时物体一卡一卡

This commit is contained in:
2026-10-09 15:32:34 +08:00
parent bc88fef43a
commit d20d45fffc
3 changed files with 47 additions and 6 deletions
+2 -1
View File
@@ -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)
+38 -5
View File
@@ -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