Files
agame/.workbuddy/memory/2026-09-23.md
T
2026-09-23 16:27:02 +08:00

89 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-09-23
## 玩家动画(KayKit character-male-e 动作库)
用户把玩家模型换成 `character/player/assets/character-male-e.glb`,并在
`Player/Body/character-male-e2/` 下加了 `AnimationPlayer`(动作库内嵌在 player.tscn 里,
共 32 个片段,11 条 track,root_node = `..`)。
### 关键结构事实(别猜,实测)
- track 路径形如 `character-male-e/Skeleton3D:root`,比玩家本体多一层
`character-male-e/` 前缀 —— 换模型/改层级会让整组 track 静默失效。
- glb 里所有片段的 `loop_mode` 全是 `NONE`,`idle/walk/sprint` 必须在运行时补
`Animation.LOOP_LINEAR`,否则播一遍就定在最后一帧。
- 片段长度:idle 1.333s / walk 0.667s / sprint 0.5s / jump 0.5s / fall 0.333s。
jump 时长恰好约等于 `jump_velocity=5 / gravity=9.8` 的上升时间。
### 实现(`character/player/player.gd`)
- `enum AnimState { IDLE, WALK, SPRINT, JUMP, FALL }` + `ANIM_CLIPS` / `ANIM_LOOPED` 常量。
- `_prepare_animation()` 在 `_ready` 里、**本地/远端分支之前**调用(两端都要跑)。
- `_update_animation()` 在 `_physics_process` 末尾调用,只读 `velocity` / `is_on_floor()`。
- 两个被同步的新属性:
- `anim_state: int` —— setter 里切片段(`play(name, anim_blend_time)`),
状态不变时 setter 不触发,所以 `_prepare_animation` 里必须显式 `play("idle")`。
- `anim_speed: float` —— 播放倍率 = `水平速度 / 该状态设计速度`(walk→walk_speed,
sprint→sprint_speed),clamp 到 `[0.65, 1.6]`。远端只有 position 算不出速度,必须复制。
- 远端玩家物理与输入都关着,动画完全靠复制过来的 `anim_state` / `anim_speed` 回放。
### 同步配置
`character/player/player_sync.tres` 从 3 项扩到 5 项,新增
`.:anim_state` 与 `.:anim_speed`,`replication_mode` 全部保持 1 (ALWAYS)。
## 踩到的坑(可复用)
1. **编辑器进程里 `.tres` 有 ResourceCache。** 用 MCP `filesystem_manage(op="write_text")`
改完 `player_sync.tres` 后,`filesystem_manage(op="scan")` 也刷新不掉缓存 ——
`test_run` 里的 `load()` 仍读到旧的内存副本(只认 3 个字段)。
→ 测试里改用 `ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)`;
它同时把正确的实例灌回缓存,之后再 `node_set_property` 赋值就拿到新实例了。
2. **`Animation` 的 track 路径含 `:骨骼名`,不能直接 `get_node(track_path)`**
(get_node 只认节点段)。要用
`root.get_node(NodePath(track_path.get_concatenated_names()))`,
骨骼名单独用 `get_concatenated_subnames()` + `Skeleton3D.find_bone()` 校验。
3. **`game_eval` 的真实签名是 `{"op": "game_eval", "params": {"code": "..."}}`**,
不是顶层 `code`。它支持多语句 + 内部 `await`,所以能在编辑器跑着的游戏里
直接 `net.host_game()` → 等物理帧 → 驱动状态机 → 读回结果,是最强的端到端验证手段。
4. 编辑器报告的 `play_state/is_playing=playing` 可能是陈旧的(`project_manage(op="stop")`
回「was_running: false」即证)。
## 顺带修的东西
- `tests/test_net_replication.gd` 之前就坏了:路径还写着 `res://player/`(项目已迁到
`res://character/player/`),且断言同步字段数为 2(实际 3)。已修正路径、改为断言
5 个必需字段,并新增「片段存在」「5 个片段的每条 track 路径都能解析 + 骨骼名存在」两类测试。
现在 5 个测试全过(143 条断言)。
## 未处理的既有问题
`res://addons/srcoder_thirdperson_controller/assets/mouse_lock.gd` 与 `res://mouse_lock.tscn`
缺失,编辑器日志持续刷 `File not found`(约 60 条)。与本任务无关,未动。
## 模型朝向修正(按 W 却面朝相机)
**原因**:`player.gd` 的约定是「`body.rotation.y = 0` 即朝 -Z」(按 W 时
`wish=(0,0,-1)` → `target_yaw = atan2(-wish.x,-wish.z) = 0`)。
但 KayKit 的 `character-male-e.glb` 模型正面朝 **+Z**,
场景里 `Body / character-male-e2 / character-male-e` 三个节点的 rotation 全是 0,
没有做任何补偿 → 视觉上倒着走。
**修法**:给模型包装节点 `Body/character-male-e2` 加 `rotation.y = PI`(已 `scene_save`,
磁盘上是 `transform = Transform3D(-2.5, 0, -2.19e-7, 0, 2.5, 0, 2.19e-7, 0, -2.5, 0, 0, 0)`)。
**为什么不是改代码**:
- 往场景里给 `Body` 加 180° 没用 —— `body.rotation.y` 每帧被 `lerp_angle` 覆写。
- 往 `target_yaw` 上加 PI 会污染 `Body` 的语义(`Body:rotation` 是要同步的「世界朝向」),
而且静止时的初始朝向还得额外补一笔。
- 加在模型根节点上:代码里的 -Z 约定保持干净,静止与移动两种状态同时正确。
- 动画 track 是 `character-male-e/Skeleton3D:骨骼` 的相对路径,旋转包装节点不会破坏它们。
**验证**(`game_eval`,实测通过):`Input.action_press("move_forward")` 等 45 物理帧,
用左右手臂骨算世界空间的 model forward(`Vector3.UP.cross(右轴)`),结果:
- `move_dir` = (0, -1)、`model_fwd` = (0.05, -1.0) → `dot = 0.999`
- 与相机前向 `dot = 0.999` → 看到的是角色背面 ✅
- `body_rot_deg = 0`、位移 3.51 m(0.75s × 5 m/s)✅