抱起物品

This commit is contained in:
2026-10-03 18:04:07 +08:00
parent bc90e6d7b2
commit 6f3c89a4d2
17 changed files with 635 additions and 44 deletions
+51
View File
@@ -80,3 +80,54 @@
**教训**:改 `.gdshader` / `.tscn` 这类编辑器可能正开着的文件之后,最好提醒用户先关掉或重开那个文件再保存;
否则他那边一保存就会把磁盘上的新版本覆盖掉,而且不会有任何提示。
## 实现:抱起物品系统(第 1 步,单机跑通)
用户要求:左键抱起、长按 Q 扔出、抱起后胳膊伸出去抱住且走路会晃、胳膊可延伸、只有特定物品能抱并给个示例。
新增/改动:
- `world/items/carryable.gd`(`class_name Carryable extends RigidBody3D`,**标了 @tool**):
门槛在物品自己身上 —— 进 `carryable` 组才算「可抱」。参数:`hold_offset` / `hold_rotation_deg` /
`freeze_while_carried` / `throw_speed_min` / `throw_speed_max`。
`begin_carry()` 冻结物理并**把 collision_layer 清 0**(不清会把抱着它的人顶飞);`end_carry(v)` 恢复并给初速度;
`apply_hold_transform(socket)` 由持有者每帧调用,偏移/朝向由物品自己决定。
- `world/items/carryable_box.tscn`:示例物品(0.6³ 橙箱子,RigidBody3D + Mesh + Collision + 脚本)。
- `character/player/carry_arms.gd`(`class_name CarryArms extends Node`,也标了 @tool):
抱持姿势 + 走路晃动的表现层。**动画之后叠加**(`process_priority = 100`),只动 arm_l_*/arm_r_* 六根骨骼,
晃动同时作用在骨骼和抱持点上,所以人和东西一起晃。
- `player.gd`:`_find_carryable / _pick_up / _release_throw` 三个小函数;交互提示与判定同源(优先级:手上有东西 > 可抱物 > 缆上 > 可挂的缆);抱东西时禁疾跑 + `carry_speed_scale` 降速;抱着时不给挂滑索。
- `player.tscn`:新增 `CameraRig/CarrySocket`(Marker3D,本地 (0,-0.8,-1.0)) 和 `Body/CarryArms`。
- `world/main.tscn`:放两个示例箱子。
- 输入动作:`carry` = 鼠标左键、`throw_carry` = Q(`input_map_manage ensure_binding`,直接落 project.godot)。
- `tests/test_carry.gd`:7 条(动作存在、玩家场景节点、示例物品可抱、抱起/放下冻结与恢复、贴到抱持点、投掷速度随蓄力、抱持姿势真的伸到身前)。
踩到的坑:
- **`CarryArms` 的 NodePath 少了一级**:默认写成 `mouse/MouseRig/Skeleton3D`,但节点挂在 `Body` 下,
应该是 `../mouse/MouseRig/Skeleton3D`;抱持点同理要 `../../CameraRig/CarrySocket`。
解析不到只会 push_warning,姿势完全不生效、画面看不出原因。
- **骨骼本地轴不能猜**:一开始按骨骼本地欧拉角写,结果手是往**上后方**摆的。
改成「世界方向语义」——参数按世界方向理解,内部用 `rest_basis.inverse() * world * rest_basis`
换算到骨骼本地空间,再乘到动画姿态上。这样换模型也不会错。
- 参数是扫出来的(临时探针一次跑 24 组 swing/tuck/roll,打印 `arm_l_03` 的全局位置):
最终 `shoulder_swing_deg = 40 / tuck = 50 / roll = 0 / elbow = 15 / stretch = 1.25`
→ 手落在模型本地 (±1.17, 0.08, 0.95),即身前中部。
- **非 @tool 脚本在编辑器里是 placeholder**,测试里方法调不动、`_ready` 也不跑(组也加不上)。
`carryable.gd` / `carry_arms.gd` 都标了 `@tool` 才跑得通;改完脚本要 `filesystem_manage(op="scan")`
才会重新解析(不 scan 的话测试还按旧代码跑)。
- 顺手纠正:脸部贴花的正面是模型**本地 +Z**(尾巴在 -Z),我之前写成 `-normalize(object_normal).z` 是错的,
已改回 `+`,`tests/test_face.gd` 的断言也跟着反过来。
验证:`test_run` **39/39**;实机跑通完整链路 —— 提示「左键 抱起」→ 左键抱起(箱子出现在身前)→
长按 Q(提示「长按 Q 扔出 100%」)→ 松手箱子飞出去落地。
**未做**:联机同步(第 2 步)。现在物品位置只有持有者本地在写,远端玩家看不到箱子被抱走 —— 需要给物品加
`MultiplayerSynchronizer` + 跟随持有者的 authority 切换 + 抢占仲裁。
## 给「抱起物品系统」出了方案(未动代码)
用户要新功能:玩家抱起物品,先要方案。我在对话里给了设计,没有写文件。
要点:`Carryable` 组件挂在物品上(组 `carryable`,照抄 Zipline 的「参数放组件、玩家只读」套路);
玩家侧只加 `_find_carryable/_pick_up/_drop` 三个小函数 + `CameraRig/CarrySocket` 标记点;
交互优先级和提示文案共用一处 `_resolve_interaction()`(滑索踩过「提示亮着但按 E 没反应」的坑);
抱持**不 reparent**,改成 `carried_by` 状态 + 持有者每帧摆到 socket,authority 随持有者切换;
分三步:单机跑通 → 联机仲裁/同步 → 持物动画与扔出。等用户拍板几个点(E 是否复用、要不要真物理/扔、模型有没有持物动画)。