拾起物品同步
This commit is contained in:
@@ -123,6 +123,56 @@
|
||||
**未做**:联机同步(第 2 步)。现在物品位置只有持有者本地在写,远端玩家看不到箱子被抱走 —— 需要给物品加
|
||||
`MultiplayerSynchronizer` + 跟随持有者的 authority 切换 + 抢占仲裁。
|
||||
|
||||
## 补上:第一人称看得见胳膊 + 抱起物品联机同步
|
||||
|
||||
用户要求「玩家需要看到自己胳膊,联机也需要同步」,并给了 `CLAUDE.md`:
|
||||
**「游戏内测试交给用户来,不要自己做」** —— 已记进 MEMORY.md,之后不再自己跑 `project_run` 截图验证。
|
||||
|
||||
**第一人称留胳膊**:`player.gd` 的 `_hide_body_keep_arms()` 用栈遍历 Body 下所有 MeshInstance3D,
|
||||
按 `arm_` 前缀筛出胳膊留着、其余藏掉;一个都没筛到(换模型改名)就退化成整个 `body.visible = false`。
|
||||
|
||||
**联机同步**(关键设计:只同步一个整数):
|
||||
- 新增 `world/items/carryable_sync.tres`(同步 `.:position` / `.:rotation` / `.:carrier_peer`),
|
||||
`carryable_box.tscn` 里加 `Sync`(MultiplayerSynchronizer)节点。
|
||||
- `Carryable.carrier_peer` 带 setter:非 0 就 `freeze = true` + `collision_layer = 0`;
|
||||
为 0 时 `freeze = not multiplayer.is_server()`(只有服务器模拟,其余端跟着同步走)。
|
||||
- **抱着时位置不走同步**:`_physics_process` 里 `_find_carrier_socket()`(玩家节点名 = peer id,组 `net_player`)
|
||||
把物品摆到持有者抱持点上。每端都这么算 → 天然一致,也就没有「写位置 / 同步写回来」打架的问题。
|
||||
- **权威永远在服务器**:`request_pickup(peer_id)` / `request_release(velocity)` 两个 RPC,
|
||||
客户端 `rpc_id(1, ...)`、单机(is_server)直接调;服务器只改 `carrier_peer`,放下时顺手写 `linear_velocity`。
|
||||
- 玩家侧:`_pick_up()` 乐观认领 + `pickup_confirm_time`(0.6s)超时松手;`_release_throw()` 把初速度发给服务器。
|
||||
|
||||
**顺手发现(重要,未改,等用户确认)**:`player.tscn` 的根节点 `Player` 现在带
|
||||
`transform = Transform3D(0.2, 0, 0, 0, 0.2, 0, 0, 0, 0.2, 0, 0, 0)` —— 整只玩家被缩到 20%
|
||||
(胶囊 0.36m、相机 0.32m、模型 0.23m),而且这个缩放会漏给「挂在编辑中场景根下」的节点:
|
||||
`tests/test_zipline.gd` 就是这么挂滑索的,于是 `test_tangent_points_from_a_to_b` 红了
|
||||
(实测 tangent(0)·along = 0.888 < 0.9;zipline.gd 的 `sample()` 用全局锚点 lerp 却加**未缩放的 sag**,
|
||||
缩放一进来形状就偏了)。这行是我读 scene 之后才出现的,来源是提交 `6f3c89a 抱起物品`(用户提交的工作区)。
|
||||
|
||||
`test_run` **40/41**(唯一红的就是上面那条滑索测试)。
|
||||
|
||||
## 用户确认:玩家缩放 0.2 是有意的 → 滑索改成缩放无关
|
||||
|
||||
用户明确「就是想将玩家缩小到 0.2 倍」,所以那行不改。但滑索那条测试还得救,而且它暴露的是**真 bug**:
|
||||
|
||||
- `zipline.gd` 的 `sample()` 用 `anchor_a.global_position.lerp(anchor_b.global_position, t)` 再减
|
||||
`UP * sag * 4t(1-t)` —— 前半截跟着父节点缩放、后半截不跟,父节点一有缩放曲线形状就偏。
|
||||
`tangent()` / `ride_point()` 同样的混用。
|
||||
- 改成**先在本地空间算曲线**(`_curve_local()`,原来的 `_local_sample` 从「把世界点转回本地」变成直接算本地),
|
||||
`sample()`/`ride_point()` 用 `to_global()` 转世界,`tangent()` 用 `global_transform.basis * d` 转方向。
|
||||
缆绳网格本来就在本地空间生成,现在少了一次多余的 `to_local`。
|
||||
- 测试夹具原来挂在**编辑中场景根**下,于是玩家根的 0.2 缩放漏进滑索。改成挂在
|
||||
`Engine.get_main_loop().root` 下的中性父节点(`_make_holder()`),夹具不再受编辑中场景影响。
|
||||
- 新增 `test_shape_survives_parent_scale`:故意套一个 0.2 缩放的父节点,验 tangent 不歪、
|
||||
中点下垂 = sag × 0.2。这条把刚踩的坑钉住了。
|
||||
|
||||
`test_run` **42/42**。
|
||||
|
||||
**留给用户决定的尺度问题**(我只报告,没改):玩家缩到 0.2 之后,世界单位的参数都还是老尺度 ——
|
||||
`walk_speed 5 / sprint 8.5 / jump 5`(= 14 身高/秒)、`pickup_radius 3.0`(8 身高)、
|
||||
滑索 `attach_radius 4.0` / `hang_offset 1.15`(人会吊在缆下方 3 个身高)、物品 `throw_speed 5~14`、
|
||||
示例箱子 0.6m(比 0.36m 的玩家还高)。要不要按 0.2 一起缩,等用户拍板。
|
||||
|
||||
## 给「抱起物品系统」出了方案(未动代码)
|
||||
|
||||
用户要新功能:玩家抱起物品,先要方案。我在对话里给了设计,没有写文件。
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
## 工程约定
|
||||
|
||||
- **`CLAUDE.md` 里写着「游戏内测试交给用户来,不要自己做」** —— 别用 `project_run` + 截图去自己验游戏,
|
||||
编辑器里的 `test_run` 照常跑。画面问题让用户看,或者用数值探针验。
|
||||
- **玩家根节点 `Player` 有意缩放 0.2**(用户明确说「就是想将玩家缩小到 0.2 倍」):胶囊 0.36m、相机 0.32m、
|
||||
模型 0.23m。挂在玩家下的东西(`CameraRig/CarrySocket`、`Body/*`)**本地坐标会自动跟着缩**,所以抱持点不用改;
|
||||
但**世界单位**的参数没跟着缩:`walk_speed/sprint_speed/jump_velocity`、`pickup_radius`、
|
||||
滑索的 `attach_radius/hang_offset`、物品的 `throw_speed_*`、示例箱子尺寸(0.6m 比玩家还高)都还是老尺度。
|
||||
- **滑索的曲线在「本节点局部空间」算**(`_curve_local()`),`sample/tangent/ride_point` 再转成世界 ——
|
||||
父节点带缩放时整条缆一起缩、形状才不走样(早前混用 global 锚点 lerp + 未缩放的 sag,父节点一缩放
|
||||
tangent 就歪 27°)。测试夹具同理**别挂在编辑中场景根下**:玩家的 0.2 缩放会把滑索几何带偏
|
||||
(实测中点下垂 1.2 → 0.24)。滑索测试现在挂在编辑器根节点下的中性父节点里,另有一条
|
||||
`test_shape_survives_parent_scale` 专门钉住缩放行为。
|
||||
- 玩家控制器只认「运动结果」,不认识任何动画片段名;表现层脚本 `player_animator.gd` 直接挂在
|
||||
`Body/AnimationPlayer` 上(`animation_player` 留空即自绑自身,所以两者是同一个节点)。
|
||||
新增动作状态改 `ANIM_CLIPS`,不要往 `player.gd` 里塞片段名。
|
||||
@@ -89,12 +100,19 @@
|
||||
**参数按「世界方向」语义**(`rest_basis.inverse() * world * rest_basis` 换算到骨骼本地空间)——
|
||||
直接写骨骼本地欧拉角会得到「手往上后方摆」这种莫名其妙的结果,换模型必错。
|
||||
默认 swing 40 / tuck 50 / roll 0 / elbow 15 / stretch 1.25 → 手落在模型本地 (±1.17, 0.08, 0.95)。
|
||||
- 本地玩家第一人称藏了自己的模型(`hide_own_body`),所以**自己看不到自己的胳膊**,晃动只有别的玩家能看到;
|
||||
想自己看就把 `hide_own_body` 关掉并把 `eye_height` 降下来。
|
||||
- **未做**:联机同步。现在物品位置只有持有者本地在写,远端看不到箱子被抱走 ——
|
||||
要给物品加 `MultiplayerSynchronizer`、authority 随持有者切换、再加抢占仲裁。
|
||||
- 本地玩家第一人称只藏身体、**留下两条胳膊**(`_hide_body_keep_arms()` 按 `arm_` 前缀筛,找不到就整个藏掉)——
|
||||
抱东西时得看得见自己的手。
|
||||
- **联机只同步 `carrier_peer` 一个整数**(物品自己带 `MultiplayerSynchronizer`,配置 `world/items/carryable_sync.tres`)。
|
||||
**抱着的时候位置根本不走同步**:每一端都从「持有者的抱持点」把物品摆出来(`Carryable._physics_process` →
|
||||
`_find_carrier_socket()`,靠玩家节点名 = peer id、组 `net_player` 找到),结果天然一致,
|
||||
也避免了「持有者写位置 / 同步又把位置写回来」互相打架。空着的时候才同步 position/rotation。
|
||||
- **权威永远在服务器**(不切换):物品位置只由服务器模拟,其余端 `freeze = true` 跟着同步走;
|
||||
抱起/放下都是客户端 `rpc_id(1, ...)` 请求服务器,服务器改 `carrier_peer` 就完事。
|
||||
- 抱起是乐观认领 + 超时松手:`_pick_up()` 先把自己记成持有者,`pickup_confirm_time`(默认 0.6s)内
|
||||
服务器没把 `carrier_peer` 改成自己就自动松手 —— 联机下被别人先抢走时会走到这一步。
|
||||
- 测试 `tests/test_carry.gd`:`Carryable` / `CarryArms` 都标了 `@tool`,否则编辑器里是 placeholder、
|
||||
方法调不动、`_ready` 不跑(组也加不上);改完脚本要 `filesystem_manage(op="scan")` 才重新解析。
|
||||
玩家脚本不是 @tool,所以「第一人称留胳膊」那条测试查的是源码 + 模型里真有 `arm_*` mesh。
|
||||
|
||||
## Godot 4 踩坑
|
||||
|
||||
|
||||
Reference in New Issue
Block a user