refactor: 将 Layer 0 单独放到一个包

This commit is contained in:
AncTe committed 2026-05-17 21:32:23 +08:00
1 parent bf781c5ee8
commit 8f4c4b7df2
48 files changed
+159 -97

No files matched your search

@@ -0,0 +1,63 @@
import { logger } from '@motajs/common';
import { IFaceData, IRoleFaceBinder } from '../common';
import { isNil } from 'lodash-es';
import { FaceDirection } from '.';
interface FaceInfo {
/** 此图块的朝向 */
readonly face: FaceDirection;
/** 此图块对应的映射 */
readonly map: Map<FaceDirection, number>;
}
export class RoleFaceBinder implements IRoleFaceBinder {
/** 每个图块对应的朝向信息 */
private faceMap: Map<number, FaceInfo> = new Map();
/** 主要朝向映射 */
private mainMap: Map<number, FaceDirection> = new Map();
malloc(identifier: number, main: FaceDirection): void {
this.mainMap.set(identifier, main);
const map = new Map<FaceDirection, number>();
map.set(main, identifier);
const info: FaceInfo = { face: main, map };
this.faceMap.set(identifier, info);
}
bind(identifier: number, main: number, face: FaceDirection): void {
const mainFace = this.mainMap.get(main);
if (isNil(mainFace)) {
logger.error(43, main.toString());
return;
}
if (mainFace === face) {
logger.error(44, main.toString());
return;
}
const { map } = this.faceMap.get(main)!;
map.set(face, identifier);
const info: FaceInfo = { face, map };
this.faceMap.set(identifier, info);
this.mainMap.set(identifier, mainFace);
}
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null {
const info = this.faceMap.get(identifier);
if (!info) return null;
const target = info.map.get(face);
if (isNil(target)) return null;
const data: IFaceData = { identifier: target, face };
return data;
}
getFaceDirection(identifier: number): FaceDirection | undefined {
return this.faceMap.get(identifier)?.face;
}
getMainFace(identifier: number): IFaceData | null {
const face = this.mainMap.get(identifier);
if (isNil(face)) return null;
const data: IFaceData = { identifier, face };
return data;
}
}
@@ -0,0 +1,282 @@
import { FaceDirection } from './types';
//#region 接口与枚举
export const enum FaceGroup {
/** 四方向(上下左右) */
Dir4,
/** 八方向(上下左右+斜向) */
Dir8
}
export interface IFaceDescriptor {
/** 横坐标增量 */
readonly x: number;
/** 纵坐标增量 */
readonly y: number;
}
export interface IFaceHandler<T extends number> {
/**
* 将任意朝向值降级为本组支持的方向。
* 对于无法合理降级的方向(包括 `Unknown`),返回 `FaceDirection.Unknown`
* @param dir 任意朝向值
*/
degrade(dir: number): T;
/**
* 获取指定方向的单步坐标偏移量,输入先经过 `degrade`
* @param dir 朝向
*/
movement(dir: number): IFaceDescriptor;
/**
* 获取指定方向走 `count` 步的坐标偏移量,等价于 `movement * count`。
* `count` 允许为负数,表示反向位移,输入先经过 `degrade`
* @param dir 朝向
* @param count 步数,允许为负
*/
move(dir: number, count: number): IFaceDescriptor;
/**
* 获取本组内的反方向,输入先经过 `degrade`,`Unknown` 返回 `Unknown`
* @param dir 朝向
*/
opposite(dir: number): T;
/**
* 在本组方向集合内顺时针(默认)或逆时针旋转一步,输入先经过 `degrade`,
* `Unknown` 返回 `Unknown`
* @param dir 朝向
* @param anticlockwise 是否逆时针,默认顺时针
*/
next(dir: number, anticlockwise?: boolean): T;
/**
* 迭代本组支持的所有朝向,包含 `Unknown`
*/
mapDirection(): Iterable<T>;
/**
* 迭代本组所有朝向及其对应的坐标描述器,包含 `Unknown`(对应 `{ x: 0, y: 0 }`)
*/
mapMovement(): Iterable<[T, IFaceDescriptor]>;
}
export interface IFaceManager {
/**
* 以数字 key 注册一个 handler
* @param group 数字 key
* @param handler 朝向处理器
*/
register(group: number, handler: IFaceHandler<number>): void;
/**
* 以字符串 id 注册一个 handler
* @param id 字符串 id
* @param handler 朝向处理器
*/
registerById(id: string, handler: IFaceHandler<number>): void;
/**
* 按数字 key 查找 handler,未找到返回 `null`
* @param group 数字 key
*/
get<T extends number>(group: number): IFaceHandler<T> | null;
/**
* 按字符串 id 查找 handler,未找到返回 `null`
* @param id 字符串 id
*/
getById<T extends number>(id: string): IFaceHandler<T> | null;
}
//#endregion
//#region 内置 Handler
const ZERO_DESCRIPTOR: IFaceDescriptor = { x: 0, y: 0 };
const DIR8_MOVEMENTS: ReadonlyMap<FaceDirection, IFaceDescriptor> = new Map([
[FaceDirection.Unknown, ZERO_DESCRIPTOR],
[FaceDirection.Left, { x: -1, y: 0 }],
[FaceDirection.Up, { x: 0, y: -1 }],
[FaceDirection.Right, { x: 1, y: 0 }],
[FaceDirection.Down, { x: 0, y: 1 }],
[FaceDirection.LeftUp, { x: -1, y: -1 }],
[FaceDirection.RightUp, { x: 1, y: -1 }],
[FaceDirection.LeftDown, { x: -1, y: 1 }],
[FaceDirection.RightDown, { x: 1, y: 1 }]
]);
/** 顺时针旋转顺序(不含 Unknown) */
const DIR8_CW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.RightUp],
[FaceDirection.RightUp, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.RightDown],
[FaceDirection.RightDown, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.LeftDown],
[FaceDirection.LeftDown, FaceDirection.Left],
[FaceDirection.Left, FaceDirection.LeftUp],
[FaceDirection.LeftUp, FaceDirection.Up]
]);
/** 逆时针旋转顺序(不含 Unknown) */
const DIR8_CCW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.LeftUp],
[FaceDirection.LeftUp, FaceDirection.Left],
[FaceDirection.Left, FaceDirection.LeftDown],
[FaceDirection.LeftDown, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.RightDown],
[FaceDirection.RightDown, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.RightUp],
[FaceDirection.RightUp, FaceDirection.Up]
]);
const DIR8_OPPOSITE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.Up],
[FaceDirection.Left, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.Left],
[FaceDirection.LeftUp, FaceDirection.RightDown],
[FaceDirection.RightDown, FaceDirection.LeftUp],
[FaceDirection.RightUp, FaceDirection.LeftDown],
[FaceDirection.LeftDown, FaceDirection.RightUp]
]);
export class Dir8FaceHandler implements IFaceHandler<FaceDirection> {
degrade(dir: number): FaceDirection {
return dir as FaceDirection;
}
movement(dir: number): IFaceDescriptor {
return DIR8_MOVEMENTS.get(this.degrade(dir)) ?? ZERO_DESCRIPTOR;
}
move(dir: number, count: number): IFaceDescriptor {
const { x, y } = this.movement(dir);
return { x: x * count, y: y * count };
}
opposite(dir: number): FaceDirection {
const degraded = this.degrade(dir);
return DIR8_OPPOSITE.get(degraded) ?? FaceDirection.Unknown;
}
next(dir: number, anticlockwise: boolean = false): FaceDirection {
const degraded = this.degrade(dir);
if (degraded === FaceDirection.Unknown) return FaceDirection.Unknown;
const map = anticlockwise ? DIR8_CCW : DIR8_CW;
return map.get(degraded) ?? FaceDirection.Unknown;
}
mapDirection(): Iterable<FaceDirection> {
return DIR8_MOVEMENTS.keys();
}
mapMovement(): Iterable<[FaceDirection, IFaceDescriptor]> {
return DIR8_MOVEMENTS.entries();
}
}
const DIR4_DEGRADE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Left, FaceDirection.Left],
[FaceDirection.Up, FaceDirection.Up],
[FaceDirection.Right, FaceDirection.Right],
[FaceDirection.Down, FaceDirection.Down],
[FaceDirection.LeftUp, FaceDirection.Left],
[FaceDirection.LeftDown, FaceDirection.Left],
[FaceDirection.RightUp, FaceDirection.Right],
[FaceDirection.RightDown, FaceDirection.Right]
]);
const DIR4_MOVEMENTS: ReadonlyMap<FaceDirection, IFaceDescriptor> = new Map([
[FaceDirection.Unknown, ZERO_DESCRIPTOR],
[FaceDirection.Left, { x: -1, y: 0 }],
[FaceDirection.Up, { x: 0, y: -1 }],
[FaceDirection.Right, { x: 1, y: 0 }],
[FaceDirection.Down, { x: 0, y: 1 }]
]);
const DIR4_CW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.Left],
[FaceDirection.Left, FaceDirection.Up]
]);
const DIR4_CCW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.Left],
[FaceDirection.Left, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.Up]
]);
const DIR4_OPPOSITE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
[FaceDirection.Up, FaceDirection.Down],
[FaceDirection.Down, FaceDirection.Up],
[FaceDirection.Left, FaceDirection.Right],
[FaceDirection.Right, FaceDirection.Left]
]);
export class Dir4FaceHandler implements IFaceHandler<FaceDirection> {
degrade(dir: number): FaceDirection {
return DIR4_DEGRADE.get(dir as FaceDirection) ?? FaceDirection.Unknown;
}
movement(dir: number): IFaceDescriptor {
return DIR4_MOVEMENTS.get(this.degrade(dir)) ?? ZERO_DESCRIPTOR;
}
move(dir: number, count: number): IFaceDescriptor {
const { x, y } = this.movement(dir);
return { x: x * count, y: y * count };
}
opposite(dir: number): FaceDirection {
const degraded = this.degrade(dir);
return DIR4_OPPOSITE.get(degraded) ?? FaceDirection.Unknown;
}
next(dir: number, anticlockwise: boolean = false): FaceDirection {
const degraded = this.degrade(dir);
if (degraded === FaceDirection.Unknown) return FaceDirection.Unknown;
const map = anticlockwise ? DIR4_CCW : DIR4_CW;
return map.get(degraded) ?? FaceDirection.Unknown;
}
mapDirection(): Iterable<FaceDirection> {
return DIR4_MOVEMENTS.keys();
}
mapMovement(): Iterable<[FaceDirection, IFaceDescriptor]> {
return DIR4_MOVEMENTS.entries();
}
}
//#endregion
//#region FaceManager
export class FaceManager implements IFaceManager {
private readonly byGroup: Map<number, IFaceHandler<number>> = new Map();
private readonly byId: Map<string, IFaceHandler<number>> = new Map();
register(group: number, handler: IFaceHandler<number>): void {
this.byGroup.set(group, handler);
}
registerById(id: string, handler: IFaceHandler<number>): void {
this.byId.set(id, handler);
}
get<T extends number>(group: number): IFaceHandler<T> | null {
return (this.byGroup.get(group) as IFaceHandler<T>) ?? null;
}
getById<T extends number>(id: string): IFaceHandler<T> | null {
return (this.byId.get(id) as IFaceHandler<T>) ?? null;
}
}
//#endregion
@@ -0,0 +1,6 @@
export * from './face';
export * from './faceManager';
export * from './indexer';
export * from './mover';
export * from './types';
export * from './utils';
@@ -0,0 +1,61 @@
import { ITileLocator } from '@motajs/common';
//#region 接口定义
export interface ILocationHelper {
/**
* 坐标 -> 索引
* @param x 横坐标
* @param y 纵坐标
*/
locToIndex(x: number, y: number): number;
/**
* 定位符 -> 索引
* @param locator 定位符
*/
locaterToIndex(locator: ITileLocator): number;
/**
* 索引 -> 定位符
* @param index 索引
*/
indexToLocator(index: number): ITileLocator;
}
export interface ILocationIndexer extends ILocationHelper {
/**
* 设置地图宽度
* @param width 地图宽度
*/
setWidth(width: number): void;
}
//#endregion
//#region 默认实现
export class MapLocIndexer implements ILocationIndexer {
private width: number = 0;
setWidth(width: number): void {
this.width = width;
}
locToIndex(x: number, y: number): number {
return y * this.width + x;
}
locaterToIndex(locator: ITileLocator): number {
return locator.y * this.width + locator.x;
}
indexToLocator(index: number): ITileLocator {
return {
x: index % this.width,
y: Math.floor(index / this.width)
};
}
}
//#endregion
@@ -0,0 +1,560 @@
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
@@ -0,0 +1,82 @@
export const enum FaceDirection {
Unknown,
Left,
Up,
Right,
Down,
LeftUp,
RightUp,
LeftDown,
RightDown
}
export interface IFaceData {
/** 图块数字 */
readonly identifier: number;
/** 图块朝向 */
readonly face: FaceDirection;
}
export interface IRoleFaceBinder {
/**
* 给指定的图块分配朝向绑定
* @param identifier 图块数字
* @param main 主图块朝向,一般是朝下
*/
malloc(identifier: number, main: FaceDirection): void;
/**
* 将一个图块与另一个图块绑定朝向,需要注意要先调用 {@link malloc} 分配朝向信息
* @param identifier 当前图块数字
* @param main 主图块数字,即当前图块与目标图块属于主图块的另一个朝向
* @param face 当前图块的朝向方向
*/
bind(identifier: number, main: number, face: FaceDirection): void;
/**
* 获取一个图块指定朝向的图块数字
* @param identifier 图块数字,可以是任意朝向的图块数字
* @param face 要获取的朝向
*/
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null;
/**
* 获取指定图块数字是哪个朝向
* @param identifier 图块数字
*/
getFaceDirection(identifier: number): FaceDirection | undefined;
/**
* 获取指定图块数字绑定至的主朝向
* @param identifier 图块数字,可以是任意朝向的图块数字
*/
getMainFace(identifier: number): IFaceData | null;
}
//#region 功能接口
export const enum SaveCompression {
/** 不进行压缩,仅提取必要数据 */
NoCompression,
/** 进行小幅度压缩,以性能为主要考虑目标 */
LowCompression,
/** 进行大幅度压缩,以体积为主要考虑目标 */
HighCompression
}
export interface ISaveableContent<T> {
/**
* 保存对象状态,返回的对象应该经过深拷贝(即 `structuedClone`)
* @param compression 压缩级别
*/
saveState(compression: SaveCompression): T;
/**
* 读取对象状态
* @param state 状态对象
* @param compression 压缩级别
*/
loadState(state: T, compression: SaveCompression): void;
}
//#endregion
@@ -0,0 +1,182 @@
import { FaceDirection } from './types';
/**
* 获取指定朝向的坐标偏移量
* @param dir 朝向
*/
export function getFaceMovement(dir: FaceDirection): Loc {
switch (dir) {
case FaceDirection.Left:
return { x: -1, y: 0 };
case FaceDirection.Right:
return { x: 1, y: 0 };
case FaceDirection.Up:
return { x: 0, y: -1 };
case FaceDirection.Down:
return { x: 0, y: 1 };
case FaceDirection.LeftUp:
return { x: -1, y: -1 };
case FaceDirection.RightUp:
return { x: 1, y: -1 };
case FaceDirection.LeftDown:
return { x: -1, y: 1 };
case FaceDirection.RightDown:
return { x: 1, y: 1 };
case FaceDirection.Unknown:
return { x: 0, y: 0 };
}
}
/**
* 将八方向朝向降级为四方向朝向
* @param dir 朝向
* @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认值是未知
*/
export function degradeFace(
dir: FaceDirection,
unknown: FaceDirection = FaceDirection.Unknown
): FaceDirection {
switch (dir) {
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Unknown:
return unknown;
}
return dir;
}
/**
* 获取指定朝向旋转后的朝向
* @param dir 当前朝向
* @param anticlockwise 是否逆时针旋转,默认顺时针
* @param face8 是否使用八朝向。为 `false` 时,旋转为九十度旋转,即 上->右->下->左,左上->右上->右下->左下。
* 为 `true` 时,旋转为四十五度旋转,即 上->右上->右->右下->下->左下->左->左上。逆时针反过来旋转。
*/
export function nextFaceDirection(
dir: FaceDirection,
anticlockwise: boolean = false,
face8: boolean = false
): FaceDirection {
if (face8) {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.LeftUp;
case FaceDirection.LeftUp:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.Left;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
} else {
if (anticlockwise) {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
} else {
switch (dir) {
case FaceDirection.Left:
return FaceDirection.Up;
case FaceDirection.Up:
return FaceDirection.Right;
case FaceDirection.Right:
return FaceDirection.Down;
case FaceDirection.Down:
return FaceDirection.Left;
case FaceDirection.LeftUp:
return FaceDirection.RightUp;
case FaceDirection.RightUp:
return FaceDirection.RightDown;
case FaceDirection.RightDown:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.LeftUp;
case FaceDirection.Unknown:
return FaceDirection.Unknown;
}
}
}
}
/**
* 根据朝向字符串获取朝向枚举值
* @param dir 朝向字符串
*/
export function fromDirectionString(dir: Dir2): FaceDirection {
switch (dir) {
case 'left':
return FaceDirection.Left;
case 'right':
return FaceDirection.Right;
case 'up':
return FaceDirection.Up;
case 'down':
return FaceDirection.Down;
case 'leftup':
return FaceDirection.LeftUp;
case 'rightup':
return FaceDirection.RightUp;
case 'leftdown':
return FaceDirection.LeftDown;
case 'rightdown':
return FaceDirection.RightDown;
default:
return FaceDirection.Unknown;
}
}
+4
View File
@@ -0,0 +1,4 @@
export * from './common';
export * from './store';
export * from './types';
@@ -0,0 +1,2 @@
export * from './tileStore';
export * from './types';
@@ -0,0 +1,86 @@
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
import {
ITileLegacyConverter,
ITileRawData,
ITileStore,
TileType
} from './types';
export class TileStore<TLegacy = unknown> implements ITileStore<TLegacy> {
/** 以图块数字为键的原始图块定义表 */
private readonly dataMap: Map<number, ITileRawData> = new Map();
/** 由图块 id 反查图块数字的映射表 */
private readonly idMap: Map<string, number> = new Map();
/** 由图块数字反查图块 id 的映射表 */
private readonly numMap: Map<number, string> = new Map();
/** 当前挂载的旧样板图块转换器 */
private legacyConverter: ITileLegacyConverter<TLegacy> | null = null;
getData(num: number): ITileRawData | null {
return this.dataMap.get(num) ?? null;
}
getTrigger(num: number): number {
return this.dataMap.get(num)?.trigger ?? -1;
}
getType(num: number): TileType {
return this.dataMap.get(num)?.type ?? TileType.Unknown;
}
addTile(data: ITileRawData): void {
const oldData = this.dataMap.get(data.num);
const oldNum = this.idMap.get(data.id);
if (oldData) {
logger.warn(133, data.num.toString(), oldData.id);
this.deleteBy(oldData.num, oldData.id);
}
if (!isNil(oldNum) && oldNum !== data.num) {
logger.warn(134, data.id, oldNum.toString());
const oldIdData = this.dataMap.get(oldNum);
if (oldIdData) {
this.deleteBy(oldIdData.num, oldIdData.id);
} else {
this.idMap.delete(data.id);
this.numMap.delete(oldNum);
}
}
this.dataMap.set(data.num, data);
this.idMap.set(data.id, data.num);
this.numMap.set(data.num, data.id);
}
idToNumber(id: string): number | null {
return this.idMap.get(id) ?? null;
}
numberToId(num: number): string | null {
return this.numMap.get(num) ?? null;
}
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void {
this.legacyConverter = converter;
}
fromLegacy(num: number, legacy: TLegacy): ITileRawData {
const converter = this.legacyConverter;
if (!converter) {
logger.error(56);
throw new Error('Expected a tile legacy converter');
}
const data = converter.fromLegacy(num, legacy);
this.addTile(data);
return data;
}
/** 删除一组旧的图块定义及其双向索引 */
private deleteBy(num: number, id: string): void {
this.dataMap.delete(num);
this.idMap.delete(id);
this.numMap.delete(num);
}
}
@@ -0,0 +1,91 @@
export const enum TileType {
/** 未知或尚未归类的图块 */
Unknown,
/** 空白图块 */
None,
/** 地形类图块 */
Terrain,
/** 动画类图块 */
Animate,
/** 道具类图块 */
Item,
/** 怪物类图块 */
Enemy,
/** NPC 类图块 */
Npc,
/** 自动元件 */
Autotile,
/** Tileset 切片图块 */
Tileset
}
export interface ITileRawData {
/** 图块数字 */
readonly num: number;
/** 图块字符串 id */
readonly id: string;
/** 默认触发器类型 */
readonly trigger: number;
/** 图块逻辑类型 */
readonly type: TileType;
}
export interface ITileLegacyConverter<TLegacy> {
/**
* 将旧样板图块定义转换为新的图块原始数据
* @param num 图块数字
* @param legacy 旧样板图块定义
*/
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
}
export interface ITileStore<TLegacy = unknown> {
/**
* 获取指定图块数字对应的完整原始定义
* @param num 图块数字
*/
getData(num: number): ITileRawData | null;
/**
* 获取指定图块数字对应的默认触发器类型
* @param num 图块数字
*/
getTrigger(num: number): number;
/**
* 获取指定图块数字对应的图块类型
* @param num 图块数字
*/
getType(num: number): TileType;
/**
* 添加一个图块定义;若 `num` 或 `id` 冲突则警告并覆盖
* @param data 图块原始定义
*/
addTile(data: ITileRawData): void;
/**
* 根据图块 id 查询对应图块数字
* @param id 图块 id
*/
idToNumber(id: string): number | null;
/**
* 根据图块数字查询对应图块 id
* @param num 图块数字
*/
numberToId(num: number): string | null;
/**
* 挂载一个旧样板转换器
* @param converter 旧样板转换器
*/
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void;
/**
* 使用当前转换器转换并写入一个旧样板图块定义
* @param num 图块数字
* @param legacy 旧样板图块定义
*/
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
}
+16
View File
@@ -0,0 +1,16 @@
import { IFaceManager, IRoleFaceBinder } from './common';
import { ITileStore } from './store';
export interface IDataCommon {
/** 图块定义存储 */
readonly tileStore: ITileStore<MapDataOf<keyof NumberToId>>;
/** 朝向绑定 */
readonly roleFace: IRoleFaceBinder;
/** 朝向管理 */
readonly faceManager: IFaceManager;
}
export interface IDataCommonExtended {
/** 当前对象对应的基本数据端对象(Layer 0 对象) */
readonly state: IDataCommon;
}