refactor: 移动顶层实施对象

This commit is contained in:
unanmed 2026-06-29 23:58:52 +08:00
parent 327bdc840b
commit 3d72954bb2
7 changed files with 157 additions and 186 deletions

View File

@ -1,4 +1,4 @@
import { ITileLocator } from '@motajs/common';
import { ITileLocator, logger } from '@motajs/common';
import {
FaceDirection,
IFaceHandler,
@ -10,13 +10,11 @@ import {
} from '@user/data-common';
import {
HeroMoveCode,
IHeroHitAction,
IHeroHitHandler,
IHeroLocation,
IHeroMover,
IHeroMoverConfig,
IPassCheckerHandler,
ITerrainPassChecker
IHeroMoveTopHandler,
IHeroMoveTopImpl
} from './types';
import { isNil } from 'lodash-es';
@ -26,17 +24,17 @@ export class HeroMover<T extends IHeroLocation>
{
readonly state: IDataCommon;
/** 本次移动是否不记录进路线系统 */
/** 是否不记录进路线系统 */
private noRoute: boolean = false;
/** 本次移动是否忽略地形碰撞检测 */
/** 是否忽略地形碰撞检测 */
private ignoreTerrain: boolean = false;
/** 本次移动是否在特定时机触发自动存档 */
/** 是否在特定时机触发自动存档 */
private autoSave: boolean = false;
/** 是否允许到达地图外 */
private allowOutBound: boolean = false;
/** 地形通行判定器 */
private terrainChecker: ITerrainPassChecker | null = null;
/** 撞击行为对象 */
private hitAction: IHeroHitAction | null = null;
/** 勇士移动顶层实现对象 */
private topImpl: IHeroMoveTopImpl | null = null;
constructor(
readonly tile: T,
@ -46,7 +44,7 @@ export class HeroMover<T extends IHeroLocation>
this.state = tile.state;
}
config(config: Readonly<IHeroMoverConfig>): this {
config(config: Partial<IHeroMoverConfig>): this {
if (!isNil(config.noRoute)) {
this.noRoute = config.noRoute;
}
@ -56,6 +54,9 @@ export class HeroMover<T extends IHeroLocation>
if (!isNil(config.autoSave)) {
this.autoSave = config.autoSave;
}
if (!isNil(config.allowOutBound)) {
this.allowOutBound = config.allowOutBound;
}
return this;
}
@ -63,95 +64,104 @@ export class HeroMover<T extends IHeroLocation>
return {
noRoute: this.noRoute,
ignoreTerrain: this.ignoreTerrain,
autoSave: this.autoSave
autoSave: this.autoSave,
allowOutBound: this.allowOutBound
};
}
useTerrainChecker(checker: ITerrainPassChecker | null): void {
this.terrainChecker = checker;
}
useHitAction(action: IHeroHitAction | null): void {
this.hitAction = action;
useTopImplementation(impl: IHeroMoveTopImpl | null): void {
this.topImpl = impl;
}
/**
*
*
* @param curr
* @param dir
* @param floorId id
*/
private createPassHandler(
private createHandler(
curr: ITileLocator,
dir: FaceDirection,
step: Readonly<ObjectMoveStep>,
floorId: string | undefined
): IPassCheckerHandler {
const { x, y } = this.faceHandler.movement(dir);
const nx = curr.x + x;
const ny = curr.y + y;
return {
currLoc: curr,
nextLoc: { x: nx, y: ny },
direction: dir,
floorId,
face: this.faceHandler,
state: this.state
};
): IHeroMoveTopHandler {
switch (step.type) {
case ObjectMoveType.Dir:
case ObjectMoveType.DirFace:
case ObjectMoveType.Special: {
const { x, y } = this.faceHandler.movement(this.moveDirection);
const nx = curr.x + x;
const ny = curr.y + y;
return {
currLoc: { x: curr.x, y: curr.y },
nextLoc: { x: nx, y: ny },
direction: this.moveDirection,
floorId,
face: this.faceHandler,
state: this.state
};
}
case ObjectMoveType.Jump:
case ObjectMoveType.Teleport: {
const nx = step.rel ? curr.x + step.x : step.x;
const ny = step.rel ? curr.y + step.y : step.y;
return {
currLoc: { x: curr.x, y: curr.y },
nextLoc: { x: nx, y: ny },
direction: this.moveDirection,
floorId,
face: this.faceHandler,
state: this.state
};
}
case ObjectMoveType.AnimDir:
case ObjectMoveType.Face:
case ObjectMoveType.Speed: {
return {
currLoc: { x: curr.x, y: curr.y },
nextLoc: { x: curr.x, y: curr.y },
direction: this.moveDirection,
floorId,
face: this.faceHandler,
state: this.state
};
}
}
}
/**
*
* @param curr
* @param dir
* @param floorId id
*/
private createHitHandler(
curr: ITileLocator,
dir: FaceDirection,
floorId: string | undefined
): IHeroHitHandler {
const { x, y } = this.faceHandler.movement(dir);
const nx = curr.x + x;
const ny = curr.y + y;
return {
currLoc: curr,
nextLoc: { x: nx, y: ny },
direction: dir,
floorId,
state: this.state
};
protected async onMoveStart(): Promise<void> {
if (!this.topImpl) {
logger.warn(144);
}
}
protected async onMoveStart(): Promise<void> {}
protected async onMoveEnd(): Promise<void> {}
protected async onStepStart(
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation
): Promise<number> {
if (!this.terrainChecker) return HeroMoveCode.CannotMove;
if (!this.topImpl) return HeroMoveCode.Stop;
const type = step.type;
const handler = this.createHandler(tile, step, tile.floorId);
// 边界判断
const { x: nx, y: ny } = handler.nextLoc;
const inBound = this.topImpl.inBound(nx, ny, handler.floorId);
if (!this.allowOutBound && !inBound) return HeroMoveCode.CannotMove;
// 移动步判断
if (
type === ObjectMoveType.Dir ||
type === ObjectMoveType.DirFace ||
type === ObjectMoveType.Special
) {
const dir = this.moveDirection;
const handler = this.createPassHandler(tile, dir, tile.floorId);
const { x: nx, y: ny } = handler.nextLoc;
const inBound = this.terrainChecker.inBound(nx, ny, tile.floorId);
// 目标点不在地图内
if (!inBound) return HeroMoveCode.CannotMove;
if (this.ignoreTerrain) {
return HeroMoveCode.Step;
} else {
const canPass = this.terrainChecker.canPass(handler);
const canPass = this.topImpl.canPass(handler);
if (canPass) {
const hit = this.terrainChecker.shouldHit(handler);
const hit = this.topImpl.shouldHit(handler);
if (hit) return HeroMoveCode.Hit;
else return HeroMoveCode.Step;
} else {
@ -160,14 +170,12 @@ export class HeroMover<T extends IHeroLocation>
}
}
// 跳跃和瞬移仅需要进行边界判断
// 跳跃和瞬移
if (type === ObjectMoveType.Jump || type === ObjectMoveType.Teleport) {
const nx = step.rel ? tile.x + step.x : step.x;
const ny = step.rel ? tile.y + step.y : step.y;
if (this.ignoreTerrain) {
return HeroMoveCode.Step;
} else {
if (this.terrainChecker.inBound(nx, ny, tile.floorId)) {
if (inBound) {
return HeroMoveCode.Step;
} else {
return HeroMoveCode.Stop;
@ -175,6 +183,7 @@ export class HeroMover<T extends IHeroLocation>
}
}
// 其他的一律可以直接执行
return HeroMoveCode.Step;
}
@ -184,7 +193,9 @@ export class HeroMover<T extends IHeroLocation>
tile: IHeroLocation,
controller: Readonly<IMoverController>
): Promise<ITileLocator> {
const type = step.type;
if (!this.topImpl) return { x: tile.x, y: tile.y };
const handler = this.createHandler(tile, step, tile.floorId);
// 不能移动或停止
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Stop) {
@ -195,12 +206,7 @@ export class HeroMover<T extends IHeroLocation>
// 撞击时也使用当前位置,同时处理撞击行为
if (code === HeroMoveCode.Hit) {
// 执行撞击行为
if (this.hitAction) {
const dir = this.moveDirection;
const handler = this.createHitHandler(tile, dir, tile.floorId);
await this.hitAction.hit(handler);
}
await this.topImpl.hit(handler);
// 这里同样不能 await
controller.stop();
return { x: tile.x, y: tile.y };
@ -208,30 +214,7 @@ export class HeroMover<T extends IHeroLocation>
// 正常移动
if (code === HeroMoveCode.Step) {
if (
type === ObjectMoveType.Teleport ||
type === ObjectMoveType.Jump
) {
// 瞬移行为
if (step.rel) {
return { x: tile.x + step.x, y: tile.y + step.y };
} else {
return { x: step.x, y: step.y };
}
} else if (
type === ObjectMoveType.Dir ||
type === ObjectMoveType.DirFace ||
type === ObjectMoveType.Special
) {
// 移动行为
const { x, y } = this.faceHandler.movement(this.moveDirection);
const nx = tile.x + x;
const ny = tile.y + y;
return { x: nx, y: ny };
} else {
// 其他行为
return { x: tile.x, y: tile.y };
}
return handler.nextLoc;
}
return { x: tile.x, y: tile.y };

View File

@ -230,7 +230,7 @@ export const enum HeroMoveCode {
CannotMove
}
export interface IHeroMoveHandlerBase extends IDataCommonExtended {
export interface IHeroMoveTopHandler extends IDataCommonExtended {
/** 当前位置 */
readonly currLoc: ITileLocator;
/** 要移动至的位置 */
@ -239,16 +239,11 @@ export interface IHeroMoveHandlerBase extends IDataCommonExtended {
readonly direction: FaceDirection;
/** 当前楼层 id */
readonly floorId: string | undefined;
}
export interface IPassCheckerHandler extends IHeroMoveHandlerBase {
/** 朝向管理对象 */
readonly face: IFaceHandler<FaceDirection>;
}
export interface IHeroHitHandler extends IHeroMoveHandlerBase {}
export interface ITerrainPassChecker {
export interface IHeroMoveTopImpl {
/**
*
* @param x
@ -261,30 +256,30 @@ export interface ITerrainPassChecker {
*
* @param handler
*/
canPass(handler: IPassCheckerHandler): boolean;
canPass(handler: IHeroMoveTopHandler): boolean;
/**
*
* @param handler
*/
shouldHit(handler: IPassCheckerHandler): boolean;
}
shouldHit(handler: IHeroMoveTopHandler): boolean;
export interface IHeroHitAction {
/**
*
* @param handler
*/
hit(handler: IHeroHitHandler): Promise<void>;
hit(handler: IHeroMoveTopHandler): Promise<void>;
}
export interface IHeroMoverConfig {
/** 本次移动是否不记录进路线系统 */
noRoute?: boolean;
/** 本次移动是否忽略地形碰撞检测 */
ignoreTerrain?: boolean;
/** 本次移动是否在特定时机触发自动存档 */
autoSave?: boolean;
/** 是否不记录进路线系统 */
noRoute: boolean;
/** 是否忽略地形碰撞检测 */
ignoreTerrain: boolean;
/** 是否在特定时机触发自动存档 */
autoSave: boolean;
/** 是否允许到达地图外 */
allowOutBound: boolean;
}
export interface IHeroMover<T extends IHeroLocation>
@ -293,7 +288,7 @@ export interface IHeroMover<T extends IHeroLocation>
*
* @param config
*/
config(config: Readonly<IHeroMoverConfig>): this;
config(config: Partial<IHeroMoverConfig>): this;
/**
*
@ -301,16 +296,10 @@ export interface IHeroMover<T extends IHeroLocation>
getConfig(): Readonly<IHeroMoverConfig>;
/**
* `null`
* @param checker
*
* @param impl
*/
useTerrainChecker(checker: ITerrainPassChecker | null): void;
/**
* `null`
* @param action
*/
useHitAction(action: IHeroHitAction | null): void;
useTopImplementation(impl: IHeroMoveTopImpl | null): void;
}
//#endregion

View File

@ -66,8 +66,7 @@ import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
import { ISaveSystem, SaveSystem } from './save';
import { DefaultPassChecker } from './hero';
import { DefaultHitAction } from './hero/hitAction';
import { DefaultHeroMoveTopImpl } from './hero';
export class CoreState implements ICoreState {
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
@ -211,14 +210,11 @@ export class CoreState implements ICoreState {
});
// 勇士顶层初始化
const passChecker = new DefaultPassChecker(this.maps);
const hitAction = new DefaultHitAction(
this.maps,
this.triggerCollector,
this
const heroMoveTopImpl = new DefaultHeroMoveTopImpl(
this,
this.triggerCollector
);
this.hero.location.mover.useTerrainChecker(passChecker);
this.hero.location.mover.useHitAction(hitAction);
this.hero.location.mover.useTopImplementation(heroMoveTopImpl);
//#endregion
}

View File

@ -1,37 +0,0 @@
import {
IHeroHitAction,
IHeroHitHandler,
IMapStore,
IStateBase
} from '@user/data-base';
import { ITriggerCollector, ITriggerHandler } from '@user/data-system';
import { isNil } from 'lodash-es';
export class DefaultHitAction implements IHeroHitAction {
constructor(
readonly maps: IMapStore,
readonly collector: ITriggerCollector,
readonly state: IStateBase
) {}
async hit(handler: IHeroHitHandler): Promise<void> {
if (isNil(handler.floorId)) return;
const map = this.maps.getLayerState(handler.floorId);
if (!map) return;
const event = map.eventLayer;
if (!event) return;
const { x, y } = handler.nextLoc;
const triggers = this.collector.collect(x, y, event);
const triggerHandler: ITriggerHandler = {
state: this.state,
layer: map,
mapLayer: event,
locator: handler.nextLoc
};
return triggers.trigger(triggerHandler);
}
}

View File

@ -1,2 +1,2 @@
export * from './passChecker';
export * from './moverImpl';
export * from './types';

View File

@ -1,13 +1,25 @@
import { FaceDirection, PassBit } from '@user/data-common';
import {
IHeroMoveTopHandler,
IHeroMoveTopImpl,
IMapStore,
IPassCheckerHandler,
ITerrainPassChecker
IStateBase
} from '@user/data-base';
import { FaceDirection, PassBit } from '@user/data-common';
import { ITriggerCollector, ITriggerHandler } from '@user/data-system';
import { isNil } from 'lodash-es';
export class DefaultPassChecker implements ITerrainPassChecker {
constructor(readonly maps: IMapStore) {}
export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
/** 地图存储对象 */
private readonly maps: IMapStore;
constructor(
private readonly state: IStateBase,
private readonly collector: ITriggerCollector
) {
this.maps = state.maps;
}
//#region 通行性判断
/**
*
@ -36,7 +48,7 @@ export class DefaultPassChecker implements ITerrainPassChecker {
return x >= 0 && y >= 0 && x < width && y < height;
}
canPass(handler: IPassCheckerHandler): boolean {
canPass(handler: IHeroMoveTopHandler): boolean {
const { currLoc, nextLoc, direction, floorId, face } = handler;
if (isNil(floorId)) return false;
@ -96,7 +108,7 @@ export class DefaultPassChecker implements ITerrainPassChecker {
return true;
}
shouldHit(handler: IPassCheckerHandler): boolean {
shouldHit(handler: IHeroMoveTopHandler): boolean {
const { nextLoc, floorId } = handler;
if (isNil(floorId)) return false;
const layerState = this.maps.getLayerState(floorId);
@ -110,4 +122,31 @@ export class DefaultPassChecker implements ITerrainPassChecker {
if (!next || !next.raw) return false;
return !next.raw.eventPass;
}
//#endregion
//#region 触发器行为
async hit(handler: IHeroMoveTopHandler): Promise<void> {
if (isNil(handler.floorId)) return;
const map = this.maps.getLayerState(handler.floorId);
if (!map) return;
const event = map.eventLayer;
if (!event) return;
const { x, y } = handler.nextLoc;
const triggers = this.collector.collect(x, y, event);
const triggerHandler: ITriggerHandler = {
state: this.state,
layer: map,
mapLayer: event,
locator: handler.nextLoc
};
return triggers.trigger(triggerHandler);
}
//#endregion
}

View File

@ -200,6 +200,7 @@
"140": "Different priority of combat script is expected, but got a same one.",
"141": "Some issue may occur in binded damage system on combat flow, please check the console.",
"142": "Expected a specific tile number after convert id '$1' to number, but got null.",
"143": "Cannot create dynamic tile for $1 since its raw data is not found."
"143": "Cannot create dynamic tile for $1 since its raw data is not found.",
"144": "A hero move top implementation object binding is required for hero moving."
}
}