# 动态图块移动系统 > 本文档为 [动态图块系统](./dynamic-tile.md) 的移动子系统设计文档, > 重点描述双方向模型、`IDynamicMover` 计划式移动接口及执行流程。 > 本文档中新增的类型与接口同样需要添加至 `@user/data-base/src/map/types.ts`。 --- # 背景与动机 `IDynamicLayer.moveDynamicStep` / `moveDynamicWith` 是命令式简单接口,适用于 一次性的简单移动,但对以下场景力不从心: - **后退移动**:角色面朝上、向下移动时,朝向不变但位移反向, 单一 `FaceDirection` 参数无法同时描述朝向和移动方向; - **连续路径中途动态追加步骤**:命令式接口执行后无法继续追加; - **移动速度切换**:路径中途需要变速,命令式接口不支持; - **复杂动画编排**:需要按序执行多段计划时,纯 `Promise` 链表达不够直观。 因此引入以图块为粒度绑定的 `IDynamicMover`,采用**先建计划、再执行**的设计理念, 参考 `@motajs/animate/types.ts` 的链式计划构建模式, 以及 `@user/data-state/legacy/move.ts` 中 `ObjectMoverBase` 的双方向与队列设计。 --- # 实现思路 ## 1. 双方向模型 每个动态图块维护两个方向字段: | 字段 | 含义 | 影响 | | --------------- | ---------------------------- | --------------------------------------- | | `faceDirection` | **朝向**,视觉上「面朝哪里」 | 通过 `IRoleFaceBinder` 决定渲染图块数字 | | `moveDirection` | **移动方向**,「向哪边走」 | 结合 `getFaceMovement` 计算位移 | 两者分离后可以表达: - **后退**:`moveDirection = Up`,执行 `backward()` 步, `moveDirection = Down`,`faceDirection = Down`,位移向下,朝向同步翻转。 - **横向移动不转身**:使用 `stepFace(move, face)` 单独指定朝向, `moveDirection` 与 `faceDirection` 独立设置。 绝对方向步骤(传入 `FaceDirection`)默认将两者同时更新; `forward()` 沿当前 `moveDirection` 移动并对齐 `faceDirection`; `backward()` 将 `moveDirection` 取反并同步 `faceDirection`。 ## 2. 方向步语义 绝对方向步(`IDynamicMoveStepDir` / `IDynamicMoveStepDirFace`)直接传入 `FaceDirection`: - `step(dir, count?: number)`:追加 `count`(默认 1)个绝对方向步,`moveDirection` 和 `faceDirection` 均更新为 `dir`; - `stepFace(move, face, count?: number)`:追加 `count`(默认 1)个绝对方向步,`moveDirection` 更新为 `move`, `faceDirection` 更新为 `face`——用于移动方向与朝向不一致的场景(如斜切移动保持正向)。 相对方向步(`IDynamicMoveStepSpecial`)通过独立方法构建: - `forward(count?)`:沿当前 `tile.moveDirection` 移动,`moveDirection` 不变, `faceDirection` 对齐为 `moveDirection`; - `backward(count?)`:沿 `opposite(tile.moveDirection)` 移动,`moveDirection` 取反, `faceDirection` 同步更新为新的 `moveDirection`,自动使用逆向动画。 ## 3. DynamicMoveStep 类型 步骤类型通过 `const enum` 区分,避免字符串比较。 前进/后退方向用独立的 `DynamicSpecialStep` 枚举表示; 动画播放方向用独立的 `DynamicAnimDirection` 枚举表示,两者互不依赖: ```ts const enum DynamicMoveStepType { /** 绝对方向步,同步更新 moveDirection 和 faceDirection */ Dir, /** 绝对方向步,单独指定 faceDirection */ DirFace, /** 速度步 */ Speed, /** 纯转向步,不产生位移 */ Face, /** 特殊步(前进或后退) */ Special, /** 动画方向步,不影响 moveDirection/faceDirection */ AnimDir } /** 特殊步方向:前进或后退 */ const enum DynamicSpecialStep { Forward, Backward } /** 动画播放方向:正向或逆向 */ const enum DynamicAnimDirection { Forward, Backward } /** 绝对方向步:move 方向既是移动方向也是朝向 */ interface IDynamicMoveStepDir { type: DynamicMoveStepType.Dir; move: FaceDirection; } /** 绝对方向步:move 是移动方向,face 是显式指定的朝向 */ interface IDynamicMoveStepDirFace { type: DynamicMoveStepType.DirFace; move: FaceDirection; face: FaceDirection; } /** 速度步:修改后续移动的每格耗时(单位 ms,越小越快) */ interface IDynamicMoveStepSpeed { type: DynamicMoveStepType.Speed; value: number; } /** 转向步:仅更新 faceDirection,不产生位移,等待渲染动画完成 */ interface IDynamicMoveStepFace { type: DynamicMoveStepType.Face; value: FaceDirection; } /** 特殊步:前进(Forward)或后退(Backward)。`forward(n)`/`backward(n)` 构建时 n>1 会 push n 个相同步骤 */ interface IDynamicMoveStepSpecial { type: DynamicMoveStepType.Special; direction: DynamicSpecialStep; } /** 动画方向步:指定后续步骤动画正向播放(Forward)还是逆向播放(Backward) */ interface IDynamicMoveAnimDir { type: DynamicMoveStepType.AnimDir; dir: DynamicAnimDirection; } type DynamicMoveStep = | IDynamicMoveStepDir | IDynamicMoveStepDirFace | IDynamicMoveStepSpeed | IDynamicMoveStepFace | IDynamicMoveStepSpecial | IDynamicMoveAnimDir; ``` ## 4. IMoverController 接口 `IDynamicMover.start()` 返回 `IMoverController`,用于控制进行中的移动。 ```ts interface IMoverController { /** 本次移动是否已全部完成 */ readonly done: boolean; /** 当本次移动全部步骤完成时兑现 */ readonly onEnd: Promise; /** * 向当前队列末尾追加步骤(仅在移动进行中有效,完成后追加无效) */ push(...steps: DynamicMoveStep[]): void; /** * 停止移动,等待当前步骤完成后停止,返回的 Promise 在停止后兑现 */ stop(): Promise; } ``` ## 5. IDynamicMover 接口 每个 `IDynamicTile` 持有一个**绑定**的 `IDynamicMover` 实例(`tile.mover`), 无需单独创建或管理生命周期。 ### 5.1 状态读取 ```ts // 以下状态属性来自 IObjectMover,IDynamicMover 全部继承 interface IObjectMover { /** 是否正在移动 */ readonly moving: boolean; /** 当前朝向(与 tile.faceDirection 同步) */ readonly faceDirection: FaceDirection; /** 当前移动方向(与 tile.moveDirection 同步) */ readonly moveDirection: FaceDirection; /** 所属的可移动对象 */ readonly tile: T; // ... } ``` ### 5.2 计划构建(链式) 所有构建方法均返回 `this`,支持链式调用。 计划在调用 `start()` 前不执行,仅追加到内部队列。 ```ts interface IDynamicMover { // ... /** 追加 count(默认 1)个绝对方向步,同步更新 moveDirection 和 faceDirection */ step(dir: FaceDirection, count?: number): this; /** 追加 count(默认 1)个绝对方向步,单独指定 faceDirection(用于移动方向与朝向不一致的场景) */ stepFace(move: FaceDirection, face: FaceDirection, count?: number): this; /** 追加前进步,沿当前 moveDirection 移动,faceDirection 对齐为 moveDirection */ forward(count?: number): this; /** 追加后退步,moveDirection 取反,faceDirection 同步更新,自动使用逆向动画 */ backward(count?: number): this; /** 追加一个速度步,改变后续移动的每格耗时(ms) */ speed(value: number): this; /** 追加一个纯转向步,仅更新 faceDirection,不产生位移 */ face(dir: FaceDirection): this; /** 追加一个动画方向步,控制后续步骤的动画播放方向(Forward 正向 / Backward 逆向) */ animDir(dir: DynamicAnimDirection): this; /** 清空尚未执行的计划队列 */ clear(): this; // ... } ``` **用法示例**: ```ts // 先向下走 2 步,切换为半速,再向右走 1 步 tile.mover .step(FaceDirection.Down, 2) .speed(200) .step(FaceDirection.Right) .start(); // 后退 3 步(moveDirection/faceDirection 翻转,逆向动画) tile.mover.backward(3).start(); // 向右移动但保持朝下(如横向滑步) tile.mover.stepFace(FaceDirection.Right, FaceDirection.Down, 2).start(); ``` ### 5.3 执行控制 ```ts interface IDynamicMover { // ... /** * 开始执行计划队列,返回控制对象。 * 若已在移动则返回 null(不中断当前移动)。 */ start(): IMoverController | null; } ``` ## 6. 移动执行流程 每次 `start()` 启动后,执行器按以下总流程处理队列: 触发 `onMoveStart` → 循环每步:[ `abstract onStepStart(step, tile, mover)` 执行并返回 `code: number`(子类计算本步场景)→ 触发 `IObjectMoverHooks.onStepStart(code, step, tile, mover)` 钩子(渲染预处理,此时 `tile.x/y` 为**移动前坐标**)→ 等待所有 Promise → `abstract onStepEnd(code, step, tile, mover)` 执行**逻辑效果**(子类根据 code 决定如何执行)→ 触发 `IObjectMoverHooks.onStepEnd(code, step, tile, mover)` 钩子(此时 `tile.x/y` 为**移动后坐标**) ] → 触发 `onMoveEnd`。 ### 6.1 绝对方向步(`DynamicMoveStepType.Dir`) **onStepStart 阶段**(`tile.x/y` 为原始坐标): 1. `abstract onStepStart(step, tile, mover)` 执行,子类计算本步 `code`(正常移动、碰墙等)并返回 `Promise`。 2. 父类拿到 `code`,触发 `IObjectMoverHooks.onStepStart(code, step, tile, mover)` 钩子——渲染端在此阶段播放移动动画, 可由 `step.move` 与当前 `tile.x/y` 推算目标位置。 3. `await Promise.all(promises)`——等待全部动画完成。 **onStepEnd 阶段**(逻辑效果): 1. `abstract onStepEnd(code, step, tile, mover)` 执行——子类依据 `code` 决定本步实际效果: 更新 `tile.moveDirection = step.move`。 2. 计算 `{dx, dy} = getFaceMovement(step.move)`,**立即**更新 `tile.x += dx`、`tile.y += dy`。 3. 同步更新 `posMap`。 4. 执行转向逻辑(见 [dynamic-tile.md §8](./dynamic-tile.md#8-转向逻辑)), 更新 `tile.faceDirection` 与 `tile.num`。 5. 子类返回后,父类触发 `IObjectMoverHooks.onStepEnd(code, step, tile, mover)` 钩子,此时 `tile.x/y` 已为新坐标。 ### 6.1.1 含显式朝向的绝对方向步(`DynamicMoveStepType.DirFace`) 与 `Dir` 步骤相同,但步骤 5 改为直接将 `tile.faceDirection` 设为 `step.face`, 并通过 `getFaceOf(tile.num, step.face)` 更新 `tile.num`,不经过 `degradeFace` 降级。 ### 6.2 速度步(`DynamicMoveStepType.Speed`) 更新内部速度状态,立即生效,无位移,无需等待。 ### 6.3 纯转向步(`DynamicMoveStepType.Face`) 经由 `onStepStart` 触发钩子(`tile.x/y` 不变,`step.type === DynamicMoveStepType.Face` 供渲染端区分); 等待渲染端动画完成后,`onStepEnd` 阶段执行转向逻辑,更新 `faceDirection` 与 `tile.num`, 触发 `onStepEnd` 钩子。 ### 6.4 特殊步(`DynamicMoveStepType.Special`) **语义说明**:`forward`/`backward` 以**上一步的移动方向**(即当前 `tile.moveDirection`)为基准; 若无上一步,则回退到当前 `faceDirection`;若图块无朝向绑定,则抛出错误。 **Forward**(`DynamicSpecialStep.Forward`): 1. `actualDir = tile.moveDirection`(不改变 moveDirection); 2. 执行与 `Dir` 步骤相同的位移流程; 3. 步骤 2:跳过(`moveDirection` 保持不变); 4. 步骤 5:执行转向逻辑,以 `actualDir` 更新 `tile.faceDirection` 与 `tile.num`。 **Backward**(`DynamicSpecialStep.Backward`): 1. `actualDir = opposite(tile.moveDirection)`; 2. 在执行前临时将逆向动画标志设为开启(步骤完成后恢复); 3. 执行与 `Dir` 步骤相同的位移流程; 4. 步骤 2:`tile.moveDirection = actualDir`; 5. 步骤 5:执行转向逻辑,以 `actualDir` 更新 `tile.faceDirection` 与 `tile.num`。 ### 6.5 动画方向步(`DynamicMoveStepType.AnimDir`) 指定后续步骤动画是**正向播放**(`DynamicAnimDirection.Forward`)还是**逆向播放**(`DynamicAnimDirection.Backward`), 与图块朝向无关(朝向始终由 `faceDirection` 决定)。 修改 mover 内部的 `currentAnimDir` 状态。 渲染端在 `onStepStart` 收到 `AnimDir` 步骤时读取并更新本地动画方向状态,后续步骤的 `onStepStart` 触发时依据此状态决定播放方向。 `onStepEnd` 阶段无逻辑操作,立即完成,不影响坐标与方向。 ## 7. onStepStart / onStepEnd 钩子返回值 `IObjectMoverHooks.onStepStart` 与 `onStepEnd` 均为可选钩子(`?`),返回类型为 `Promise`, 执行器始终通过 `Promise.all` 等待所有订阅方完成后再继续。 关键时序:`onStepStart` 所有 Promise 兑现后,才执行逻辑效果(abstract onStepEnd); `onStepEnd` 全部兑现后,才进入下一步。 多个订阅方通过 `Promise.all` 并行等待,与 `forEachHook` 返回所有值的设计天然契合—— `const promises = forEachHook(hook => hook.onStepStart?.(code, step, tile, mover));` `await Promise.all(promises);` ## 8. 与 IDynamicLayer / IDynamicTile 接口的关系 `IDynamicLayer` 和 `IDynamicTile` **仅保留 `step(dir)` 单步便捷接口** (等价于 `tile.mover.step(dir).start()`),不再提供 `moveDynamicWith` / `moveWith` 等批量接口。 复杂移动能力(`forward`/`backward`/`animDir` 等)通过 `tile.mover` 访问。 **渲染端订阅移动事件的流程**: 1. 订阅 `IDynamicLayer.onCreateDynamicTile`; 2. 创建图块时获取 `tile.mover` 引用,直接向其添加钩子; 3. 移动事件从 `tile.mover` 触发,无需经过 `IDynamicLayer` 转发。 为此,`IDynamicMover` 扩展为可订阅对象, 实现 `IHookable>`(见接口定义汇总)。 --- # 涉及文件 ## 需要修改的文件 ### `@user/data-base/src/map/types.ts` 修改现有接口(移动相关新增类型已移至 `move/types.ts`): - [ ] `IDynamicTile` 新增 `readonly mover: IDynamicMover` - [ ] `IDynamicTile` 已有 `readonly faceDirection: FaceDirection` 与 `readonly moveDirection: FaceDirection`(已在主文档确认) - [ ] `IObjectMovable` 接口(`x`、`y`、`moveDirection`、`faceDirection` 只读属性 + `setPos`/`setMoveDirection`/`setFaceDirection`,定义在 `dynamic-tile.md`) - [ ] `IDynamicLayer` / `IDynamicTile` 仅保留 `step(dir)` 便捷接口, 移除 `moveDynamicWith` / `moveDynamicStep` / `moveWith` / `moveStep` (主文档接口汇总需同步更新) #### `@user/data-base/src/map/types.ts` - [ ] `DynamicMoveStepType` `const enum`: 含 `Dir`/`DirFace`/`Speed`/`Face`/`Special`/`AnimDir` 六个成员 - [ ] `DynamicSpecialStep` `const enum`:含 `Forward`/`Backward` 两个成员 - [ ] `DynamicAnimDirection` `const enum`:含 `Forward`/`Backward` 两个成员 - [ ] `IDynamicMoveStepDir` 接口(`type: DynamicMoveStepType.Dir`,`move: FaceDirection`) - [ ] `IDynamicMoveStepDirFace` 接口(`type: DynamicMoveStepType.DirFace`,`move`/`face: FaceDirection`) - [ ] `IDynamicMoveStepSpeed` 接口(`type: DynamicMoveStepType.Speed`) - [ ] `IDynamicMoveStepFace` 接口(`type: DynamicMoveStepType.Face`) - [ ] `IDynamicMoveStepSpecial` 接口(`type: Special`,`direction: DynamicSpecialStep`;无 `count` 字段,`forward(n)`/`backward(n)` 改为 push n 个步骤对象) - [ ] `IDynamicMoveAnimDir` 接口(`type: DynamicMoveStepType.AnimDir`,`dir: DynamicAnimDirection`) - [ ] `DynamicMoveStep` 联合类型(含以上六种步骤) - [ ] `IMoverController` 接口 - [ ] `IObjectMoverHooks` 接口(含 `onMoveStart`/`onMoveEnd`/`onStepStart`/`onStepEnd`; `onStepStart`/`onStepEnd` 参数顺序为 `(code, step, tile, mover)`,按使用频率排列) - [ ] `IObjectMover` 接口(含 `faceDirection`/`moveDirection` 只读属性及全部计划构建方法;`step`/`stepFace` 含 `count?` 参数) - [ ] `IDynamicMover extends IObjectMover`(无额外成员,继承基类全部能力;`IDynamicMoverHooks` 已移除,直接使用 `IObjectMoverHooks`) #### `@user/data-base/src/map/mover.ts` - [ ] `abstract class ObjectMover` 抽象基类: - 实现 `IObjectMover`,在此提供全部**计划构建方法**(`step`/`stepFace`/`forward`/ `backward`/`speed`/`face`/`animDir`/`clear`/`start`)及队列执行调度逻辑; - 含四个**抽象方法**,子类必须实现,组成移动核心控制流: - `abstract onMoveStart(tile, mover): Promise`: 整次移动开始时调用 - `abstract onMoveEnd(tile, mover): Promise`: 整次移动结束时调用 - `abstract onStepStart(step, tile, mover): Promise`: 单步逻辑执行前调用, 子类计算并**返回** `code`;父类将 `code` 传给 `IObjectMoverHooks.onStepStart` 钩子后等待 - `abstract onStepEnd(code, step, tile, mover): Promise`: 单步钩子等待完成后调用,子类依据 `code` 执行本步实际逻辑效果 - [ ] `class DynamicMover extends ObjectMover implements IDynamicMover`: 持有 `tile: IDynamicTile` 引用,维护 `moveQueue`、`moving`、 `faceDirection`、`moveDirection` 状态;实现四个抽象方法(含 posMap 更新、转向逻辑等) - [ ] `DynamicTile` 类新增 `readonly mover: DynamicMover`, 在构造时以 `this` 为参数创建 --- # 接口定义汇总 ```ts const enum DynamicMoveStepType { Dir, DirFace, Speed, Face, Special, AnimDir } const enum DynamicSpecialStep { Forward, Backward } const enum DynamicAnimDirection { Forward, Backward } interface IDynamicMoveStepDir { type: DynamicMoveStepType.Dir; move: FaceDirection; } interface IDynamicMoveStepDirFace { type: DynamicMoveStepType.DirFace; move: FaceDirection; face: FaceDirection; } interface IDynamicMoveStepSpeed { type: DynamicMoveStepType.Speed; value: number; } interface IDynamicMoveStepFace { type: DynamicMoveStepType.Face; value: FaceDirection; } /** forward(n)/backward(n) 构建时 n>1 会 push n 个相同步骤,步骤对象本身不携带 count */ interface IDynamicMoveStepSpecial { type: DynamicMoveStepType.Special; direction: DynamicSpecialStep; } interface IDynamicMoveAnimDir { type: DynamicMoveStepType.AnimDir; dir: DynamicAnimDirection; } type DynamicMoveStep = | IDynamicMoveStepDir | IDynamicMoveStepDirFace | IDynamicMoveStepSpeed | IDynamicMoveStepFace | IDynamicMoveStepSpecial | IDynamicMoveAnimDir; interface IMoverController { readonly done: boolean; readonly onEnd: Promise; push(...steps: DynamicMoveStep[]): void; stop(): Promise; } interface IObjectMoverHooks extends IHookBase { onMoveStart(mover: IObjectMover, tile: T): Promise; onMoveEnd(mover: IObjectMover, tile: T): Promise; /** 触发时 tile.x/y 为移动前坐标,适合渲染端在此播放动画 */ onStepStart?( code: number, step: DynamicMoveStep, tile: T, mover: IObjectMover ): Promise; /** 触发时 tile.x/y 已更新为移动后坐标 */ onStepEnd?( code: number, step: DynamicMoveStep, tile: T, mover: IObjectMover ): Promise; } interface IObjectMover extends IHookable< IObjectMoverHooks > { readonly moving: boolean; readonly tile: T; readonly faceDirection: FaceDirection; readonly moveDirection: FaceDirection; step(dir: FaceDirection, count?: number): this; stepFace(move: FaceDirection, face: FaceDirection, count?: number): this; forward(count?: number): this; backward(count?: number): this; speed(value: number): this; face(dir: FaceDirection): this; animDir(dir: DynamicAnimDirection): this; clear(): this; start(): IMoverController | null; } // 这里的 onStepStart 不再接受 code 作为参数,因为这应当是其返回值 `Promise`, // 具体来说,这四个方法是整个移动的核心方法而非钩子,它应当提供接口让子类实现, // 子类应当真正执行移动效果,并进行一定的控制。显然子类是没办法知道 onStepStart 中的信息的, // 所以才提供了一个 code,用于子类向 ObjectMover 提供信息,然后父类再传递给子类, // 从而子类了解到 onStepStart 中的信息,再进一步决定这一步真正应该如何执行。 // 而对于钩子,实际上是在子类的 onStepStart 执行完毕后进行的,所以是知道 code 的,所以才是参数。 // 你应该好好想一想这之间的关系。 // 以及子类是要求必须实现的,所以不应该会有返回 `void` 的场景,四个方法都应该返回 `Promise`。 // 总的来说,父类是一个“系统级”的流程控制器,它通过这四个接口来实现真正的移动控制, // 就像 legacy ObjectMoverBase 中的 startMove 一样。 abstract class ObjectMover< T extends IObjectMovable > implements IObjectMover { abstract onMoveStart(tile: T, mover: IObjectMover): Promise; abstract onMoveEnd(tile: T, mover: IObjectMover): Promise; /** 子类计算并返回本步 code;父类取得 code 后再触发 IObjectMoverHooks.onStepStart 钩子 */ abstract onStepStart( step: DynamicMoveStep, tile: T, mover: IObjectMover ): Promise; /** 子类依据 code 执行本步实际逻辑效果;父类在此之后触发 IObjectMoverHooks.onStepEnd 钩子 */ abstract onStepEnd( code: number, step: DynamicMoveStep, tile: T, mover: IObjectMover ): Promise; // 计划构建方法(step/stepFace/forward/backward/speed/face/animDir/clear/start)在此实现 } // faceDirection/moveDirection 已提升至 IObjectMover,IDynamicMover 无需额外声明 // IDynamicMoverHooks 已移除,直接使用 IObjectMoverHooks interface IDynamicMover extends IObjectMover { // 继承自 IObjectMover:moving, faceDirection, moveDirection, tile, 全部计划构建方法及钩子 } ``` --- # 问题 1. **`backward()` 的移动方向基准** ✅ 已确定 `backward()` 以 `opposite(tile.moveDirection)` 作为移动方向(基于 `moveDirection` 取反,非 `faceDirection`)。 `moveDirection` 更新为反向,`faceDirection` 通过转向逻辑跟随同步更新。 2. **`forward()` 是否更新 faceDirection** ✅ 已确定 `forward()` 沿当前 `moveDirection` 移动,`moveDirection` 不变, `faceDirection` 通过转向逻辑对齐为 `moveDirection`。 3. **转向步触发钩子方式** ✅ 已确定 统一经由 `onStepStart(code, step, tile, mover)` 钩子处理,在步骤逻辑执行前触发。 渲染端通过 `step.type === DynamicMoveStepType.Face` 区分纯转向步与位移步; `onStepStart` 触发时 `tile.x/y` 为原始坐标,渲染端可直接读取,无需反推。 转向逻辑(`faceDirection`/`num` 更新)在 `onStepEnd` 阶段执行。 4. **`animDir` 与渲染钩子集成** ✅ 已确定 `onStepStart` 统一接收所有步骤,`IDynamicMoveAnimDir` 步骤本身即为信息载体。 渲染端在 `onStepStart` 收到 `AnimDir` 步骤时更新本地动画方向状态; 后续方向步的 `onStepStart` 触发时,渲染端依据本地状态决定动画播放方向。 5. **玩家移动与图块移动的复用** ✅ 已确定 玩家(勇士)移动逻辑与 `IDynamicMover` 高度一致,共同抽象为 `IObjectMover`(参考 legacy `ObjectMoverBase`,适配新 API); 核心抽象类为 `abstract class ObjectMover`,含四个抽象方法组成控制流: - **计划构建方法**(`step`/`stepFace`/`forward`/`backward`/ `speed`/`face`/`animDir`/`clear`/`start`)全部在 `ObjectMover` 基类实现,子类无需重复声明。 - **移动流程钩子**(`IObjectMoverHooks`,实现 `IHookable>`): - `onMoveStart(mover, tile)`:整次移动队列开始时触发; - `onMoveEnd(mover, tile)`:整次移动队列结束时触发; - `onStepStart(code, step, tile, mover)`:单步开始前触发(`tile.x/y` 为移动前坐标); - `onStepEnd(code, step, tile, mover)`:单步完成后触发(`tile.x/y` 已更新); 其中 `code: number` 标识移动场景(如碰墙、遇敌、循环地图等),由具体实现层设置。 - **四个抽象方法**(`onMoveStart`/`onMoveEnd`/`onStepStart`/`onStepEnd`):子类(`DynamicMover`/`HeroMover`) 通过实现这四个方法完全控制移动行为,执行流程由 `ObjectMover` 基类统一调度。 关键:`onStepStart` 返回 `Promise`(code),父类将此 code 传给钩子后再调用 `onStepEnd(code, ...)`, 子类在 `onStepEnd` 中依据 code 执行实际状态变更。四个方法均返回 `Promise`,无 `void` 选项。 - **泛型参数** `T extends IObjectMovable`:`IObjectMovable` 是可移动对象的最小接口 (`x`、`y`、`moveDirection`、`faceDirection` 只读属性 + `setPos`/`setMoveDirection`/`setFaceDirection` 方法),定义在 `dynamic-tile.md`; `IDynamicTile extends IObjectMovable`,玩家侧使用 `IHeroMovable extends IObjectMovable`。 - **`IDynamicMover extends IObjectMover`**: 无额外成员,直接继承基类全部能力(含 `faceDirection`/`moveDirection` 及 `IObjectMoverHooks`)。