class_name PlayerPickup extends Node ## 拾取组件:**按住左键拖拽,松手放下,按 Q 朝准心方向甩出去**。 ## 按下左键时用射线抓起正前方的 RigidBody3D,按住期间持续吸在 InteractionPos 前; ## 松开左键即原地放开,按 Q 则沿视线方向把手上的物体甩出去。 ## 只管「抓 / 拖 / 放 / 甩」,不碰移动与视角。 ## ## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 注入射线与持握点。 ## 拖拽力度:物体被拉向持握点的速度系数。越大跟手越紧,但过大容易来回抖。 @export var pick_power: float = 10.0 ## 投掷初速度(米/秒):按 Q 甩出去时给物体的速度大小。 @export var throw_speed: float = 12.0 var _current: RigidBody3D = null var _ray: RayCast3D var _hold_pos: Marker3D ## 由 Player 在 _ready() 注入依赖。 func setup(ray: RayCast3D, hold_pos: Marker3D) -> void: _ray = ray _hold_pos = hold_pos func _unhandled_input(event: InputEvent) -> void: # 只有鼠标锁进游戏(真正在玩)时才响应抓 / 拖 / 放 / 甩。 # 否则「点一下把鼠标锁回来」的那次点击会顺带抓起一个物体。 if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED: return # 长按拖拽:按下抓起,松开放下;拖拽途中按 Q 可以把手上的东西甩出去。 # 按住期间不需要任何输入处理 —— 物体靠 physics_step() 每物理帧持续吸住。 if event.is_action_pressed("carry"): _grab() elif event.is_action_released("carry"): _drop() elif event.is_action_pressed("throw_carry"): _throw() ## 每物理帧由 Player 调用一次,排在 move_and_slide() 之后。 ## 用速度把物体拽向持握点,而不是直接改 global_position —— ## 这样它仍会和场景里其他刚体正常碰撞,不会被硬塞进墙里。 func physics_step() -> void: # 用 is_instance_valid 而不是 `== null`:物体可能在拖拽途中被 destroy, # 那种情况下引用不是 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) func _grab() -> void: if _ray == null: return var collider := _ray.get_collider() if collider is RigidBody3D: _current = collider as RigidBody3D func _drop() -> void: # 故意不清零线速度:松手时保留拖动惯性,甩出去的手感更自然。 # 想要「就地放下、不再滑出去」的话,这里补一句: # if is_instance_valid(_current): _current.set_linear_velocity(Vector3.ZERO) _current = null ## 按 Q 投掷:沿视线方向把物体甩出去,随后立刻松手。 func _throw() -> void: if not is_instance_valid(_current) or not is_instance_valid(_hold_pos): return # 持握点挂在摄像机下、且只有平移没有旋转,所以它的 basis 就是视线朝向, # 其 -Z 即「准心正前方」。这样不必再给组件注入一台摄像机。 var dir := -_hold_pos.global_transform.basis.z _current.set_linear_velocity(dir * throw_speed) # 想让手感更「物理」(重物飞得慢、轻物飞得远)可以换成给冲量: # _current.apply_central_impulse(dir * throw_speed) _current = null ## 当前是否抓着东西。调试用,也方便以后加「搬运时不能开枪」这类规则。 func has_object() -> bool: return is_instance_valid(_current)