5.2 KiB
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)。
踩到的坑(可复用)
- 编辑器进程里
.tres有 ResourceCache。 用 MCPfilesystem_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赋值就拿到新实例了。 Animation的 track 路径含:骨骼名,不能直接get_node(track_path)(get_node 只认节点段)。要用root.get_node(NodePath(track_path.get_concatenated_names())), 骨骼名单独用get_concatenated_subnames()+Skeleton3D.find_bone()校验。game_eval的真实签名是{"op": "game_eval", "params": {"code": "..."}}, 不是顶层code。它支持多语句 + 内部await,所以能在编辑器跑着的游戏里 直接net.host_game()→ 等物理帧 → 驱动状态机 → 读回结果,是最强的端到端验证手段。- 编辑器报告的
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)✅