import { Hookable, HookController, IHookable, IHookBase, IHookController, ITileLocator } from '@motajs/common'; import { FaceDirection } from './types'; import { IFaceHandler } from './faceManager'; //#region 对象移动 export const enum ObjectMoveStepType { /** 绝对方向步,同步更新移动方向与朝向 */ Dir, /** 绝对方向步,显式指定朝向 */ DirFace, /** 速度步 */ Speed, /** 纯转向步 */ Face, /** 特殊步,如前进或后退 */ Special, /** 动画方向步 */ AnimDir } export const enum ObjectSpecialStep { /** 前进 */ Forward, /** 后退 */ Backward } export const enum ObjectAnimDirection { /** 正向播放动画 */ Forward, /** 反向播放动画 */ Backward } export interface IObjectMovable { /** 当前横坐标 */ readonly x: number; /** 当前纵坐标 */ readonly y: number; /** * 设置对象位置 * @param x 横坐标 * @param y 纵坐标 */ setPos(x: number, y: number): void; /** * 获取当前朝向 */ getCurrentFaceDirection(): FaceDirection; } export interface IObjectMoveStepDir { /** 步骤类型 */ type: ObjectMoveStepType.Dir; /** 本步移动方向 */ move: FaceDirection; } export interface IObjectMoveStepDirFace { /** 步骤类型 */ type: ObjectMoveStepType.DirFace; /** 本步移动方向 */ move: FaceDirection; /** 本步显式朝向 */ face: FaceDirection; } export interface IObjectMoveStepSpeed { /** 步骤类型 */ type: ObjectMoveStepType.Speed; /** 后续移动的每格耗时,单位为 ms */ value: number; } export interface IObjectMoveStepFace { /** 步骤类型 */ type: ObjectMoveStepType.Face; /** 要设置的朝向 */ value: FaceDirection; } export interface IObjectMoveStepSpecial { /** 步骤类型 */ type: ObjectMoveStepType.Special; /** 特殊步方向 */ direction: ObjectSpecialStep; } export interface IObjectMoveAnimDir { /** 步骤类型 */ type: ObjectMoveStepType.AnimDir; /** 动画播放方向 */ dir: ObjectAnimDirection; } export type ObjectMoveStep = | IObjectMoveStepDir | IObjectMoveStepDirFace | IObjectMoveStepSpeed | IObjectMoveStepFace | IObjectMoveStepSpecial | IObjectMoveAnimDir; export interface IMoverController { /** 本次移动是否已经全部完成 */ done: boolean; /** 当本次移动结束时兑现 */ onEnd: Promise; /** * 向当前移动队列末尾追加步骤 * @param steps 要追加的步骤列表 */ push(...steps: ObjectMoveStep[]): void; /** * 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序 * @param steps 要插入的步骤列表 */ insert(...steps: ObjectMoveStep[]): void; /** * 停止当前移动,在当前步骤完成后兑现 */ stop(): Promise; } export interface IObjectMoverHooks extends IHookBase { /** * 当移动开始时触发 * @param tile 移动器绑定的图块 * @param mover 当前移动器 */ onMoveStart?(tile: T, mover: IObjectMover): Promise; /** * 当移动结束时触发 * @param tile 移动器绑定的图块 * @param mover 当前移动器 */ onMoveEnd?(tile: T, mover: IObjectMover): Promise; /** * 当单步移动开始前触发,此时对象坐标仍为移动前坐标 * @param code 当前步移动代码 * @param step 当前步骤 * @param tile 移动器绑定的图块 * @param mover 当前移动器 */ onStepStart?( code: number, step: ObjectMoveStep, tile: T, mover: IObjectMover ): Promise; /** * 当单步移动结束后触发,此时对象坐标已更新为移动后坐标 * @param code 当前步移动代码 * @param step 当前步骤 * @param tile 移动器绑定的图块 * @param mover 当前移动器 */ onStepEnd?( code: number, step: ObjectMoveStep, tile: T, mover: IObjectMover ): Promise; } export interface IObjectMover extends IHookable< IObjectMoverHooks > { /** 当前是否正在移动 */ readonly moving: boolean; /** 当前绑定的对象 */ readonly tile: T; /** 当前朝向 */ readonly faceDirection: FaceDirection; /** 当前移动方向 */ readonly moveDirection: FaceDirection; /** 当前动画播放方向 */ readonly currAnimDir: ObjectAnimDirection; /** 当前移动速度,单位毫秒 */ readonly currentSpeed: number; /** * 追加若干个绝对方向步,并同步更新移动方向与朝向 * @param dir 移动方向 * @param count 追加次数,默认 1 */ step(dir: FaceDirection, count?: number): this; /** * 追加若干个绝对方向步,移动方向与朝向分别指定 * @param move 移动方向 * @param face 朝向方向 * @param count 追加次数,默认 1 */ stepFace(move: FaceDirection, face: FaceDirection, count?: number): this; /** * 追加若干个前进步,沿当前移动方向移动 * @param count 追加次数,默认 1 */ forward(count?: number): this; /** * 追加若干个后退步,沿当前移动方向的反方向移动 * @param count 追加次数,默认 1 */ backward(count?: number): this; /** * 设置后续移动速度 * @param value 每格耗时,单位为 ms */ speed(value: number): this; /** * 追加一个纯转向步骤 * @param dir 目标朝向 */ face(dir: FaceDirection): this; /** * 设置后续步骤的动画播放方向 * @param dir 动画播放方向 */ animDir(dir: ObjectAnimDirection): this; /** * 清空尚未执行的步骤队列 */ clear(): this; /** * 开始执行当前计划队列 * @returns 若当前已在移动,则返回 `null` */ start(): Readonly | null; } //#endregion //#region 移动基类 export abstract class ObjectMover extends Hookable> implements IObjectMover { abstract readonly tile: T; /** 尚未开始执行的移动步骤队列 */ protected readonly moveQueue: Readonly[] = []; /** 当前是否正在移动 */ moving: boolean = false; /** 当前朝向 */ faceDirection: FaceDirection = FaceDirection.Unknown; /** 当前移动方向 */ moveDirection: FaceDirection = FaceDirection.Unknown; /** 当前动画播放方向 */ currAnimDir: ObjectAnimDirection = ObjectAnimDirection.Forward; /** 当前移动速度,单位毫秒 */ currentSpeed: number = 100; /** 是否调用了 `IMoverController.stop` 接口 */ private shouldStop: boolean = false; /** 朝向处理 */ private readonly faceHandler: IFaceHandler; constructor(faceHandler: IFaceHandler) { super(); this.faceHandler = faceHandler; } protected createController( hook: Partial> ): IHookController> { return new HookController(this, hook); } /** * 当移动开始时执行 * @param tile 移动图块 * @param controller 移动控制器 */ protected abstract onMoveStart( tile: T, controller: Readonly ): Promise; /** * 当移动结束时触发 * @param tile 移动图块 * @param controller 移动控制器 */ protected abstract onMoveEnd( tile: T, controller: Readonly ): Promise; /** * 当单步移动开始时触发,返回移动代码,此移动代码将会传递至 {@link onStepEnd} * @param step 当前移动步对象 * @param tile 移动图块 * @param controller 移动控制器 */ protected abstract onStepStart( step: ObjectMoveStep, tile: T, controller: Readonly ): Promise; /** * 当单步移动结束时触发,返回坐标对象代表这一步移动结果 * @param code 移动代码,由 {@link onStepStart} 返回值传递而来 * @param step 当前移动步对象 * @param tile 移动图块 * @param controller 移动控制器 */ protected abstract onStepEnd( code: number, step: ObjectMoveStep, tile: T, controller: Readonly ): Promise; /** * 向计划队列末尾追加一个步骤 * @param step 要追加的步骤 */ protected pushStep(step: Readonly): void { this.moveQueue.push(step); } /** * 获取当前应当作为相对移动基准的方向 */ private getCurrentDirection(): FaceDirection { if (this.moveDirection !== FaceDirection.Unknown) { return this.moveDirection; } else { return this.faceDirection; } } /** * 根据步骤内容预先同步移动器内部状态 * @param step 当前步骤 */ private prepareStep(step: ObjectMoveStep): void { switch (step.type) { case ObjectMoveStepType.Dir: this.moveDirection = step.move; this.faceDirection = step.move; break; case ObjectMoveStepType.DirFace: this.moveDirection = step.move; this.faceDirection = step.face; break; case ObjectMoveStepType.Face: this.faceDirection = step.value; break; case ObjectMoveStepType.Special: { const dir = this.getCurrentDirection(); if (step.direction === ObjectSpecialStep.Backward) { const opposite = this.faceHandler.opposite(dir); this.moveDirection = opposite; this.faceDirection = opposite; } else { this.moveDirection = dir; this.faceDirection = dir; } break; } case ObjectMoveStepType.AnimDir: this.currAnimDir = step.dir; break; case ObjectMoveStepType.Speed: this.currentSpeed = step.value; break; } } step(dir: FaceDirection, count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ type: ObjectMoveStepType.Dir, move: dir }); } return this; } stepFace( move: FaceDirection, face: FaceDirection, count: number = 1 ): this { for (let i = 0; i < count; i++) { this.pushStep({ type: ObjectMoveStepType.DirFace, move, face }); } return this; } forward(count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ type: ObjectMoveStepType.Special, direction: ObjectSpecialStep.Forward }); } return this; } backward(count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ type: ObjectMoveStepType.Special, direction: ObjectSpecialStep.Backward }); } return this; } speed(value: number): this { this.pushStep({ type: ObjectMoveStepType.Speed, value }); return this; } face(dir: FaceDirection): this { this.pushStep({ type: ObjectMoveStepType.Face, value: dir }); return this; } animDir(dir: ObjectAnimDirection): this { this.pushStep({ type: ObjectMoveStepType.AnimDir, dir }); return this; } clear(): this { this.moveQueue.length = 0; return this; } /** * 开始移动流程 * @param queue 移动队列 */ private async moveProgress( queue: ObjectMoveStep[], controller: Readonly ) { // 移动开始 await this.onMoveStart(this.tile, controller); await Promise.all( this.forEachHook(hook => { return hook.onMoveStart?.(this.tile, this); }) ); // 移动流程 while (queue.length > 0) { const step = queue.shift(); if (!step || !this.moving || this.shouldStop) break; this.prepareStep(step); const code = await this.onStepStart(step, this.tile, controller); const stepStartHooks = this.forEachHook(hook => hook.onStepStart?.(code, step, this.tile, this) ); await Promise.all(stepStartHooks); const loc = await this.onStepEnd(code, step, this.tile, controller); this.tile.setPos(loc.x, loc.y); const stepEndHooks = this.forEachHook(hook => hook.onStepEnd?.(code, step, this.tile, this) ); await Promise.all(stepEndHooks); } // 移动结束 await this.onMoveEnd(this.tile, controller); const moveEndHooks = this.forEachHook(hook => hook.onMoveEnd?.(this.tile, this) ); await Promise.all(moveEndHooks); } start(): IMoverController | null { if (this.moving) return null; const queue = this.moveQueue.slice(); this.clear(); this.shouldStop = false; this.faceDirection = this.tile.getCurrentFaceDirection(); this.moveDirection = FaceDirection.Unknown; const { promise, resolve } = Promise.withResolvers(); const controller: IMoverController = { done: false, onEnd: promise, push: (...steps: ObjectMoveStep[]) => { if (!this.moving || this.shouldStop || controller.done) { return; } queue.push(...steps); }, insert: (...steps: ObjectMoveStep[]) => { if (!this.moving || this.shouldStop || controller.done) { return; } queue.unshift(...steps); }, stop: () => { this.shouldStop = true; return controller.onEnd; } }; this.moving = true; const moving = this.moveProgress(queue, controller); moving.then(() => { this.moving = false; controller.done = true; resolve(); }); return controller; } } //#endregion