角色动画
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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)✅
|
||||
|
||||
Reference in New Issue
Block a user