# 2026-10-12 ## 可拖拽判断(PickupItem 标记) **需求**:`scripts/item.gd` 提供 `can_pickup`,只有为 true 时才能拖拽。 **item.gd 本次改动**:加了 `class_name PickupItem` 和说明注释,功能不变。 ```gdscript class_name PickupItem extends Node @export var can_pickup: bool = true ``` ### ⚠ 关键发现:脚本挂载位置 ≠ 射线打中的节点 用户在 `world/main.tscn` 第 155 行把 item.gd 挂在了 `SM_Bottle_12` 上,但那个节点**不是刚体**。 实例来源 `res://assets/Furniture_FREE-af040129/SM_Bottle_1.glb`,实际结构是: ``` SM_Bottle_12 (Node3D) ← item.gd 挂在这一层(glb 实例的根) └── SM_Bottle_1 (RigidBody3D) ← 射线实际打中的是这一层 ├── SM_Bottle_1 (MeshInstance3D) └── CollisionShape3D ``` **实测确认**:glb 内部节点的 `owner` 指向实例根(`SM_Bottle_1.owner == SM_Bottle_12`), 再往上为 null。所以「沿 owner 链向上找标记」正好一步命中。 ### 实现(`player_pickup.gd`) - 新增 `@export var allow_unmarked: bool = false` - **false(默认)= 白名单**:只有挂了 `PickupItem` 且 `can_pickup=true` 的才拖得动 - true = 黑名单:没挂标记的默认可拖,只有显式设 false 的不能拖 - 新增 `can_grab(collider) -> bool`:先判 `is RigidBody3D`,再查标记 - 新增 `_find_item_marker(body)`:顺序 **刚体自身 → 直接子节点 → owner 链向上** - 向上时**只查节点自身、不查子节点** —— 否则走到 World 层会遍历几百个子节点,而这是每物理帧都要跑的 - `_update_interact_state()` 的 hovering 判定改用 `can_grab()` - `_grab()` 里再判一次(悬停状态来自上一物理帧,其间 can_pickup 可能已被改掉) ### 实测验证(headless,全部符合预期) ``` can_grab(SM_Bottle_1 RigidBody3D) = true ✓ can_grab(SM_Bottle_12 Node3D) = false ✓(不是刚体) can_grab(Can2 内部刚体) = false ✓(严格/白名单模式) allow_unmarked=true 后 Can2 = true ✓ ``` ### ⚠ 副作用(需告知用户) 默认白名单模式下,场景里**所有没挂 item.gd 的刚体都拖不动了** (`Can2`、饭盒、水果等)。要么逐个挂标记,要么把 `allow_unmarked` 勾上。 ### MCP 备注 4 次 `script_patch` 均返回 `replacements: 1` 但伴随 `gdscript_reload_failed (code 43)` 假报错(`fallback_line: true`)—— 与 10-10 记录的是同一现象。 离线 `--check-only` 两个脚本均通过;`PickupItem` 已进 `global_script_class_cache.cfg`。