可复用玩家状态机
This commit is contained in:
@@ -86,3 +86,58 @@
|
||||
- 与相机前向 `dot = 0.999` → 看到的是角色背面 ✅
|
||||
- `body_rot_deg = 0`、位移 3.51 m(0.75s × 5 m/s)✅
|
||||
|
||||
## 重构:动画状态机抽成 PlayerAnimator(与移动解耦)
|
||||
|
||||
用户先问「动画状态机和玩家代码耦合吗」。结论:是**结构性耦合**(5 个点),但只有**单向**
|
||||
—— `_update_animation()` 只读 `velocity`/`is_on_floor()`/`Input`,从不回写物理量。
|
||||
真实代价只有一条:这套状态机没法给 NPC 复用。用户选择抽成独立组件。
|
||||
|
||||
**新结构**(`character/player/player.tscn`):
|
||||
|
||||
Player (CharacterBody3D, player.gd) ← 输入 / 物理 / 相机 / 血条 / 联机分叉
|
||||
└─ Body ← 视觉层,换模型整棵替换
|
||||
├─ PlayerAnimator (Node, player_animator.gd) ← 动作状态机
|
||||
└─ character-male-e2 / character-male-e / Skeleton3D + AnimationPlayer
|
||||
|
||||
**接口只有一个**:`animator.set_motion(velocity, is_on_floor(), sprinting)`,在
|
||||
`move_and_slide()` 之后调用。控制器不认识任何片段名,动画器不认识输入。
|
||||
动画准备(补 loop、播 idle)搬到 `PlayerAnimator._ready()` —— 子节点先于父节点 ready,
|
||||
所以 player.gd 的 `_ready()` 里不再需要任何动画代码。
|
||||
|
||||
**关键设计决定:片段基准速度属于片段,不属于控制器。**
|
||||
`PlayerAnimator.walk_ref_speed = 5.0` / `sprint_ref_speed = 8.5` 语义是「这段动画在多少
|
||||
米/秒下按原速播」。控制器把 `walk_speed` 调到 6,动画自然播成 1.2 倍,两边不用同步改 ——
|
||||
这才是真正解掉了耦合,而不是把常量搬个家。
|
||||
|
||||
**同步路径变了**:`Body/PlayerAnimator:anim_state` 与 `Body/PlayerAnimator:anim_speed`
|
||||
(`Sync.root_path` 仍是 `..` 即 Player)。`anim_state` / `anim_speed` 现在是
|
||||
PlayerAnimator 的成员,player.gd 里已无这两个属性(测试里有一条专门守住这个边界)。
|
||||
|
||||
**`animation_player` 接线**:声明成 `@export var animation_player: AnimationPlayer`,
|
||||
场景里留空(原因见下),`_ready()` 用
|
||||
`get_parent().find_child("AnimationPlayer", true, false)` 自动找。实机确认能解析。
|
||||
|
||||
**player.gd 306 → 206 行**;测试 5 → 7 条 / 154 断言,全过。
|
||||
|
||||
**实机验证**(game_eval,跑着的游戏里):
|
||||
|
||||
- `animator_node = /root/Main/1/Body/PlayerAnimator`,`auto_find_resolved = true`
|
||||
- loop 模式 `[idle, walk, sprint, jump] = [LINEAR, LINEAR, LINEAR, NONE]`
|
||||
- idle / walk@2.5(0.65) / walk@5(1.0) / sprint@8.5(1.0) / jump / fall 全部正确
|
||||
- `controller_drives_animator = [WALK, "walk", 1.0]` —— 控制器→动画器链路真的在跑
|
||||
|
||||
## 本轮新增的 MCP 坑
|
||||
|
||||
1. **`node_set_property` 设不了 Node 类型的属性。** 传
|
||||
`/Player/Body/character-male-e2/AnimationPlayer` 会报
|
||||
`VALUE_OUT_OF_RANGE: value: Path must start with res://, uid://, or user://`
|
||||
—— 该工具只按「字符串=资源路径」加载。所以 `@export var x: AnimationPlayer`
|
||||
在场景里只能留空、靠代码兜底(或改用 `NodePath` 类型 export,那个能收字符串)。
|
||||
2. **`project_run` 会把编辑器当前场景切成主场景。** 跑完游戏再 `node_create` 会报
|
||||
`NODE_NOT_FOUND: Parent not found: /Player/Body. Scene root is "/Main"`。
|
||||
写场景节点前先 `scene_open("res://character/player/player.tscn")`。
|
||||
3. **删掉某个脚本里的 enum/常量后,所有引用处会立刻刷一屏 parse error。**
|
||||
分步 patch 时这是正常的中间态,把引用清完 diagnostics 自动归零 —— 别当成自己写错了。
|
||||
4. **中文字符串用 `\uXXXX` 转义极易打错字**(本轮把「抽搐」打成「抽搝」、「照抄」打成
|
||||
「照拄」,都要事后 grep 才发现)。直接传字面中文更稳。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user