template/docs/dev/map/dynamic-tile-move.md
2026-05-11 23:35:46 +08:00

26 KiB
Raw Blame History

动态图块移动系统

本文档为 动态图块系统 的移动子系统设计文档, 重点描述双方向模型、IDynamicMover 计划式移动接口及执行流程。 本文档中新增的类型与接口同样需要添加至 @user/data-base/src/map/types.ts


背景与动机

IDynamicLayer.moveDynamicStep / moveDynamicWith 是命令式简单接口,适用于 一次性的简单移动,但对以下场景力不从心:

  • 后退移动:角色面朝上、向下移动时,朝向不变但位移反向, 单一 FaceDirection 参数无法同时描述朝向和移动方向;
  • 连续路径中途动态追加步骤:命令式接口执行后无法继续追加;
  • 移动速度切换:路径中途需要变速,命令式接口不支持;
  • 复杂动画编排:需要按序执行多段计划时,纯 Promise 链表达不够直观。

因此引入以图块为粒度绑定的 IDynamicMover,采用先建计划、再执行的设计理念, 参考 @motajs/animate/types.ts 的链式计划构建模式, 以及 @user/data-state/legacy/move.tsObjectMoverBase 的双方向与队列设计。


实现思路

1. 双方向模型

每个动态图块维护两个方向字段:

字段 含义 影响
faceDirection 朝向,视觉上「面朝哪里」 通过 IRoleFaceBinder 决定渲染图块数字
moveDirection 移动方向,「向哪边走」 结合 getFaceMovement 计算位移

两者分离后可以表达:

  • 后退moveDirection = Up,执行 backward() 步, moveDirection = DownfaceDirection = Down,位移向下,朝向同步翻转。
  • 横向移动不转身:使用 stepFace(move, face) 单独指定朝向, moveDirectionfaceDirection 独立设置。

绝对方向步骤(传入 FaceDirection)默认将两者同时更新; forward() 沿当前 moveDirection 移动并对齐 faceDirection backward()moveDirection 取反并同步 faceDirection

2. 方向步语义

绝对方向步(IDynamicMoveStepDir / IDynamicMoveStepDirFace)直接传入 FaceDirection

  • step(dir, count?: number):追加 count(默认 1个绝对方向步moveDirectionfaceDirection 均更新为 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 枚举表示,两者互不依赖:

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,用于控制进行中的移动。

interface IMoverController {
    /** 本次移动是否已全部完成 */
    readonly done: boolean;
    /** 当本次移动全部步骤完成时兑现 */
    readonly onEnd: Promise<void>;
    /**
     * 向当前队列末尾追加步骤(仅在移动进行中有效,完成后追加无效)
     */
    push(...steps: DynamicMoveStep[]): void;
    /**
     * 停止移动,等待当前步骤完成后停止,返回的 Promise 在停止后兑现
     */
    stop(): Promise<void>;
}

5. IDynamicMover 接口

每个 IDynamicTile 持有一个绑定IDynamicMover 实例(tile.mover 无需单独创建或管理生命周期。

5.1 状态读取

// 以下状态属性来自 IObjectMover<IDynamicTile>IDynamicMover 全部继承
interface IObjectMover<T extends IObjectMovable> {
    /** 是否正在移动 */
    readonly moving: boolean;
    /** 当前朝向(与 tile.faceDirection 同步) */
    readonly faceDirection: FaceDirection;
    /** 当前移动方向(与 tile.moveDirection 同步) */
    readonly moveDirection: FaceDirection;
    /** 所属的可移动对象 */
    readonly tile: T;
    // ...
}

5.2 计划构建(链式)

所有构建方法均返回 this,支持链式调用。 计划在调用 start() 前不执行,仅追加到内部队列。

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;
    // ...
}

用法示例

// 先向下走 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 执行控制

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<number>
  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 += dxtile.y += dy
  3. 同步更新 posMap
  4. 执行转向逻辑(见 dynamic-tile.md §8 更新 tile.faceDirectiontile.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 阶段执行转向逻辑,更新 faceDirectiontile.num 触发 onStepEnd 钩子。

6.4 特殊步(DynamicMoveStepType.Special

语义说明forward/backward上一步的移动方向(即当前 tile.moveDirection)为基准; 若无上一步,则回退到当前 faceDirection;若图块无朝向绑定,则抛出错误。

ForwardDynamicSpecialStep.Forward

  1. actualDir = tile.moveDirection(不改变 moveDirection
  2. 执行与 Dir 步骤相同的位移流程;
  3. 步骤 2跳过moveDirection 保持不变);
  4. 步骤 5执行转向逻辑actualDir 更新 tile.faceDirectiontile.num

BackwardDynamicSpecialStep.Backward

  1. actualDir = opposite(tile.moveDirection)
  2. 在执行前临时将逆向动画标志设为开启(步骤完成后恢复);
  3. 执行与 Dir 步骤相同的位移流程;
  4. 步骤 2tile.moveDirection = actualDir
  5. 步骤 5执行转向逻辑actualDir 更新 tile.faceDirectiontile.num

6.5 动画方向步(DynamicMoveStepType.AnimDir

指定后续步骤动画是正向播放DynamicAnimDirection.Forward)还是逆向播放DynamicAnimDirection.Backward 与图块朝向无关(朝向始终由 faceDirection 决定)。 修改 mover 内部的 currentAnimDir 状态。 渲染端在 onStepStart 收到 AnimDir 步骤时读取并更新本地动画方向状态,后续步骤的 onStepStart 触发时依据此状态决定播放方向。 onStepEnd 阶段无逻辑操作,立即完成,不影响坐标与方向。

7. onStepStart / onStepEnd 钩子返回值

IObjectMoverHooks.onStepStartonStepEnd 均为可选钩子(?),返回类型为 Promise<void> 执行器始终通过 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 接口的关系

IDynamicLayerIDynamicTile 仅保留 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<IObjectMoverHooks<IDynamicTile>>(见接口定义汇总)。


涉及文件

需要修改的文件

@user/data-base/src/map/types.ts

修改现有接口(移动相关新增类型已移至 move/types.ts

  • IDynamicTile 新增 readonly mover: IDynamicMover
  • IDynamicTile 已有 readonly faceDirection: FaceDirectionreadonly moveDirection: FaceDirection(已在主文档确认)
  • IObjectMovable 接口(xymoveDirectionfaceDirection 只读属性 + setPos/setMoveDirection/setFaceDirection,定义在 dynamic-tile.md
  • IDynamicLayer / IDynamicTile 仅保留 step(dir) 便捷接口, 移除 moveDynamicWith / moveDynamicStep / moveWith / moveStep (主文档接口汇总需同步更新)

@user/data-base/src/map/types.ts

  • DynamicMoveStepType const enumDir/DirFace/Speed/Face/Special/AnimDir 六个成员
  • DynamicSpecialStep const enum:含 Forward/Backward 两个成员
  • DynamicAnimDirection const enum:含 Forward/Backward 两个成员
  • IDynamicMoveStepDir 接口(type: DynamicMoveStepType.Dirmove: FaceDirection
  • IDynamicMoveStepDirFace 接口(type: DynamicMoveStepType.DirFacemove/face: FaceDirection
  • IDynamicMoveStepSpeed 接口(type: DynamicMoveStepType.Speed
  • IDynamicMoveStepFace 接口(type: DynamicMoveStepType.Face
  • IDynamicMoveStepSpecial 接口(type: Specialdirection: DynamicSpecialStep;无 count 字段,forward(n)/backward(n) 改为 push n 个步骤对象)
  • IDynamicMoveAnimDir 接口(type: DynamicMoveStepType.AnimDirdir: DynamicAnimDirection
  • DynamicMoveStep 联合类型(含以上六种步骤)
  • IMoverController 接口
  • IObjectMoverHooks<T extends IObjectMovable> 接口(含 onMoveStart/onMoveEnd/onStepStart/onStepEnd onStepStart/onStepEnd 参数顺序为 (code, step, tile, mover),按使用频率排列)
  • IObjectMover<T extends IObjectMovable> 接口(含 faceDirection/moveDirection 只读属性及全部计划构建方法;step/stepFacecount? 参数)
  • IDynamicMover extends IObjectMover<IDynamicTile>(无额外成员,继承基类全部能力;IDynamicMoverHooks 已移除,直接使用 IObjectMoverHooks<IDynamicTile>

@user/data-base/src/map/mover.ts

  • abstract class ObjectMover<T extends IObjectMovable> 抽象基类:
    • 实现 IObjectMover<T>,在此提供全部计划构建方法step/stepFace/forward/ backward/speed/face/animDir/clear/start)及队列执行调度逻辑;
    • 含四个抽象方法,子类必须实现,组成移动核心控制流:
      • abstract onMoveStart(tile, mover): Promise<void>: 整次移动开始时调用
      • abstract onMoveEnd(tile, mover): Promise<void>: 整次移动结束时调用
      • abstract onStepStart(step, tile, mover): Promise<number>: 单步逻辑执行前调用, 子类计算并返回 code;父类将 code 传给 IObjectMoverHooks.onStepStart 钩子后等待
      • abstract onStepEnd(code, step, tile, mover): Promise<void>: 单步钩子等待完成后调用,子类依据 code 执行本步实际逻辑效果
  • class DynamicMover extends ObjectMover<IDynamicTile> implements IDynamicMover 持有 tile: IDynamicTile 引用,维护 moveQueuemovingfaceDirectionmoveDirection 状态;实现四个抽象方法(含 posMap 更新、转向逻辑等)
  • DynamicTile 类新增 readonly mover: DynamicMover 在构造时以 this 为参数创建

接口定义汇总

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<void>;
    push(...steps: DynamicMoveStep[]): void;
    stop(): Promise<void>;
}

interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
    onMoveStart(mover: IObjectMover<T>, tile: T): Promise<void>;
    onMoveEnd(mover: IObjectMover<T>, tile: T): Promise<void>;
    /** 触发时 tile.x/y 为移动前坐标,适合渲染端在此播放动画 */
    onStepStart?(
        code: number,
        step: DynamicMoveStep,
        tile: T,
        mover: IObjectMover<T>
    ): Promise<void>;
    /** 触发时 tile.x/y 已更新为移动后坐标 */
    onStepEnd?(
        code: number,
        step: DynamicMoveStep,
        tile: T,
        mover: IObjectMover<T>
    ): Promise<void>;
}

interface IObjectMover<T extends IObjectMovable> extends IHookable<
    IObjectMoverHooks<T>
> {
    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<number>`
// 具体来说,这四个方法是整个移动的核心方法而非钩子,它应当提供接口让子类实现,
// 子类应当真正执行移动效果,并进行一定的控制。显然子类是没办法知道 onStepStart 中的信息的,
// 所以才提供了一个 code用于子类向 ObjectMover 提供信息,然后父类再传递给子类,
// 从而子类了解到 onStepStart 中的信息,再进一步决定这一步真正应该如何执行。
// 而对于钩子,实际上是在子类的 onStepStart 执行完毕后进行的,所以是知道 code 的,所以才是参数。
// 你应该好好想一想这之间的关系。
// 以及子类是要求必须实现的,所以不应该会有返回 `void` 的场景,四个方法都应该返回 `Promise<void>`。
// 总的来说,父类是一个“系统级”的流程控制器,它通过这四个接口来实现真正的移动控制,
// 就像 legacy ObjectMoverBase 中的 startMove 一样。
abstract class ObjectMover<
    T extends IObjectMovable
> implements IObjectMover<T> {
    abstract onMoveStart(tile: T, mover: IObjectMover<T>): Promise<void>;
    abstract onMoveEnd(tile: T, mover: IObjectMover<T>): Promise<void>;
    /** 子类计算并返回本步 code父类取得 code 后再触发 IObjectMoverHooks.onStepStart 钩子 */
    abstract onStepStart(
        step: DynamicMoveStep,
        tile: T,
        mover: IObjectMover<T>
    ): Promise<number>;
    /** 子类依据 code 执行本步实际逻辑效果;父类在此之后触发 IObjectMoverHooks.onStepEnd 钩子 */
    abstract onStepEnd(
        code: number,
        step: DynamicMoveStep,
        tile: T,
        mover: IObjectMover<T>
    ): Promise<void>;
    // 计划构建方法step/stepFace/forward/backward/speed/face/animDir/clear/start在此实现
}

// faceDirection/moveDirection 已提升至 IObjectMoverIDynamicMover 无需额外声明
// IDynamicMoverHooks 已移除,直接使用 IObjectMoverHooks<IDynamicTile>
interface IDynamicMover extends IObjectMover<IDynamicTile> {
    // 继承自 IObjectMovermoving, 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<T extends IObjectMovable>(参考 legacy ObjectMoverBase,适配新 API 核心抽象类为 abstract class ObjectMover<T>,含四个抽象方法组成控制流:

    • 计划构建方法step/stepFace/forward/backward/ speed/face/animDir/clear/start)全部在 ObjectMover 基类实现,子类无需重复声明。
    • 移动流程钩子IObjectMoverHooks<T>,实现 IHookable<IObjectMoverHooks<T>>
      • 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<number>code父类将此 code 传给钩子后再调用 onStepEnd(code, ...) 子类在 onStepEnd 中依据 code 执行实际状态变更。四个方法均返回 Promise,无 void 选项。
    • 泛型参数 T extends IObjectMovableIObjectMovable 是可移动对象的最小接口 xymoveDirectionfaceDirection 只读属性 + setPos/setMoveDirection/setFaceDirection 方法),定义在 dynamic-tile.md IDynamicTile extends IObjectMovable,玩家侧使用 IHeroMovable extends IObjectMovable
    • IDynamicMover extends IObjectMover<IDynamicTile> 无额外成员,直接继承基类全部能力(含 faceDirection/moveDirectionIObjectMoverHooks)。