mirror of
https://github.com/motajs/template.git
synced 2026-05-20 00:51:11 +08:00
561 lines
15 KiB
TypeScript
561 lines
15 KiB
TypeScript
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<void>;
|
|
|
|
/**
|
|
* 向当前移动队列末尾追加步骤
|
|
* @param steps 要追加的步骤列表
|
|
*/
|
|
push(...steps: ObjectMoveStep[]): void;
|
|
|
|
/**
|
|
* 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序
|
|
* @param steps 要插入的步骤列表
|
|
*/
|
|
insert(...steps: ObjectMoveStep[]): void;
|
|
|
|
/**
|
|
* 停止当前移动,在当前步骤完成后兑现
|
|
*/
|
|
stop(): Promise<void>;
|
|
}
|
|
|
|
export interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
|
|
/**
|
|
* 当移动开始时触发
|
|
* @param tile 移动器绑定的图块
|
|
* @param mover 当前移动器
|
|
*/
|
|
onMoveStart?(tile: T, mover: IObjectMover<T>): Promise<void>;
|
|
|
|
/**
|
|
* 当移动结束时触发
|
|
* @param tile 移动器绑定的图块
|
|
* @param mover 当前移动器
|
|
*/
|
|
onMoveEnd?(tile: T, mover: IObjectMover<T>): Promise<void>;
|
|
|
|
/**
|
|
* 当单步移动开始前触发,此时对象坐标仍为移动前坐标
|
|
* @param code 当前步移动代码
|
|
* @param step 当前步骤
|
|
* @param tile 移动器绑定的图块
|
|
* @param mover 当前移动器
|
|
*/
|
|
onStepStart?(
|
|
code: number,
|
|
step: ObjectMoveStep,
|
|
tile: T,
|
|
mover: IObjectMover<T>
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* 当单步移动结束后触发,此时对象坐标已更新为移动后坐标
|
|
* @param code 当前步移动代码
|
|
* @param step 当前步骤
|
|
* @param tile 移动器绑定的图块
|
|
* @param mover 当前移动器
|
|
*/
|
|
onStepEnd?(
|
|
code: number,
|
|
step: ObjectMoveStep,
|
|
tile: T,
|
|
mover: IObjectMover<T>
|
|
): Promise<void>;
|
|
}
|
|
|
|
export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
|
IObjectMoverHooks<T>
|
|
> {
|
|
/** 当前是否正在移动 */
|
|
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<IMoverController> | null;
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region 移动基类
|
|
|
|
export abstract class ObjectMover<T extends IObjectMovable>
|
|
extends Hookable<IObjectMoverHooks<T>>
|
|
implements IObjectMover<T>
|
|
{
|
|
abstract readonly tile: T;
|
|
|
|
/** 尚未开始执行的移动步骤队列 */
|
|
protected readonly moveQueue: Readonly<ObjectMoveStep>[] = [];
|
|
|
|
/** 当前是否正在移动 */
|
|
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<FaceDirection>;
|
|
|
|
constructor(faceHandler: IFaceHandler<FaceDirection>) {
|
|
super();
|
|
this.faceHandler = faceHandler;
|
|
}
|
|
|
|
protected createController(
|
|
hook: Partial<IObjectMoverHooks<T>>
|
|
): IHookController<IObjectMoverHooks<T>> {
|
|
return new HookController(this, hook);
|
|
}
|
|
|
|
/**
|
|
* 当移动开始时执行
|
|
* @param tile 移动图块
|
|
* @param controller 移动控制器
|
|
*/
|
|
protected abstract onMoveStart(
|
|
tile: T,
|
|
controller: Readonly<IMoverController>
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* 当移动结束时触发
|
|
* @param tile 移动图块
|
|
* @param controller 移动控制器
|
|
*/
|
|
protected abstract onMoveEnd(
|
|
tile: T,
|
|
controller: Readonly<IMoverController>
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* 当单步移动开始时触发,返回移动代码,此移动代码将会传递至 {@link onStepEnd}
|
|
* @param step 当前移动步对象
|
|
* @param tile 移动图块
|
|
* @param controller 移动控制器
|
|
*/
|
|
protected abstract onStepStart(
|
|
step: ObjectMoveStep,
|
|
tile: T,
|
|
controller: Readonly<IMoverController>
|
|
): Promise<number>;
|
|
|
|
/**
|
|
* 当单步移动结束时触发,返回坐标对象代表这一步移动结果
|
|
* @param code 移动代码,由 {@link onStepStart} 返回值传递而来
|
|
* @param step 当前移动步对象
|
|
* @param tile 移动图块
|
|
* @param controller 移动控制器
|
|
*/
|
|
protected abstract onStepEnd(
|
|
code: number,
|
|
step: ObjectMoveStep,
|
|
tile: T,
|
|
controller: Readonly<IMoverController>
|
|
): Promise<ITileLocator>;
|
|
|
|
/**
|
|
* 向计划队列末尾追加一个步骤
|
|
* @param step 要追加的步骤
|
|
*/
|
|
protected pushStep(step: Readonly<ObjectMoveStep>): 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<IMoverController>
|
|
) {
|
|
// 移动开始
|
|
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<void>();
|
|
|
|
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
|