feat: 勇士撞击行为

This commit is contained in:
AncTe committed 2026-06-29 19:57:46 +08:00
1 parent 0508dfb9e4
commit c4bcecdf8a
12 files changed
+380 -254

No files matched your search

+146 -96
View File
@@ -1,18 +1,21 @@
import { ITileLocator } from '@motajs/common';
import {
FaceDirection,
getFaceMovement,
IFaceHandler,
IMoverController,
ObjectMoveStep,
ObjectMover,
ObjectMoveStepType
ObjectMoveType,
IDataCommon
} from '@user/data-common';
import {
HeroMoveCode,
IHeroHitAction,
IHeroHitHandler,
IHeroLocation,
IHeroMover,
IHeroMoverConfig,
IPassCheckerHandler,
ITerrainPassChecker
} from './types';
import { isNil } from 'lodash-es';
@@ -21,20 +24,26 @@ export class HeroMover<T extends IHeroLocation>
extends ObjectMover<T>
implements IHeroMover<T>
{
readonly state: IDataCommon;
/** 本次移动是否不记录进路线系统 */
private noRoute: boolean = false;
/** 本次移动是否忽略地形碰撞检测 */
private ignoreTerrain: boolean = false;
/** 本次移动是否在特定时机触发自动存档 */
private autoSave: boolean = false;
/** 自定义地形通行判定器 */
/** 地形通行判定器 */
private terrainChecker: ITerrainPassChecker | null = null;
/** 撞击行为对象 */
private hitAction: IHeroHitAction | null = null;
constructor(
readonly tile: T,
faceHandler: IFaceHandler<FaceDirection>
) {
super(faceHandler);
this.state = tile.state;
}
config(config: Readonly<IHeroMoverConfig>): this {
@@ -62,38 +71,108 @@ export class HeroMover<T extends IHeroLocation>
this.terrainChecker = checker;
}
protected async onMoveStart(
_tile: IHeroLocation,
_controller: Readonly<IMoverController>
): Promise<void> {}
useHitAction(action: IHeroHitAction | null): void {
this.hitAction = action;
}
protected async onMoveEnd(
_tile: IHeroLocation,
_controller: Readonly<IMoverController>
): Promise<void> {}
/**
* 创建通行性检查信息对象
* @param curr 勇士所在位置
* @param dir 勇士移动方向
* @param floorId 当前楼层 id
*/
private createPassHandler(
curr: ITileLocator,
dir: FaceDirection,
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
};
}
/**
* 创建通行性检查信息对象
* @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> {}
protected async onMoveEnd(): Promise<void> {}
protected async onStepStart(
step: ObjectMoveStep,
tile: IHeroLocation,
controller: Readonly<IMoverController>
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation
): Promise<number> {
if (!this.terrainChecker) return HeroMoveCode.CannotMove;
const type = step.type;
if (
type !== ObjectMoveStepType.Dir &&
type !== ObjectMoveStepType.DirFace &&
type !== ObjectMoveStepType.Special
type === ObjectMoveType.Dir ||
type === ObjectMoveType.DirFace ||
type === ObjectMoveType.Special
) {
return HeroMoveCode.Step;
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);
if (canPass) {
const hit = this.terrainChecker.shouldHit(handler);
if (hit) return HeroMoveCode.Hit;
else return HeroMoveCode.Step;
} else {
return HeroMoveCode.CannotMove;
}
}
}
if (!this.ignoreTerrain) {
const result = this.checkTerrain(tile, controller);
if (result !== HeroMoveCode.Step) return result;
}
if (this.autoSave) {
// TODO: 检查当前步是否需要触发自动存档(如进入/离开地图伤害区域)
// 跳跃和瞬移仅需要进行边界判断
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)) {
return HeroMoveCode.Step;
} else {
return HeroMoveCode.Stop;
}
}
}
return HeroMoveCode.Step;
@@ -101,89 +180,60 @@ export class HeroMover<T extends IHeroLocation>
protected async onStepEnd(
code: number,
step: ObjectMoveStep,
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation,
controller: Readonly<IMoverController>
): Promise<ITileLocator> {
const type = step.type;
const isMovementStep =
type === ObjectMoveStepType.Dir ||
type === ObjectMoveStepType.DirFace ||
type === ObjectMoveStepType.Special;
if (
!isMovementStep &&
type !== ObjectMoveStepType.Teleport &&
type !== ObjectMoveStepType.Jump
) {
// 不能移动或停止
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Stop) {
// 这里不能 await,因为其 Promise 会在当前步结束后兑现,如果 await 就会卡住
controller.stop();
return { x: tile.x, y: tile.y };
}
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
void controller.stop();
// TODO: 处理撞击图块逻辑
// 撞击时也使用当前位置,同时处理撞击行为
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
controller.stop();
return { x: tile.x, y: tile.y };
}
if (code === HeroMoveCode.Stop) {
void controller.stop();
// TODO: 清除自动寻路
return { x: tile.x, y: tile.y };
// 正常移动
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 };
}
}
if (type === ObjectMoveStepType.Teleport) {
return this.calcPos(tile, step.x, step.y, step.rel);
}
if (type === ObjectMoveStepType.Jump) {
return this.calcPos(tile, step.x, step.y, step.rel);
}
const offset = getFaceMovement(this.moveDirection);
const nx = tile.x + offset.x;
const ny = tile.y + offset.y;
// TODO: 路线记录
// TODO: 中毒伤害处理
// TODO: 步数累计
return { x: nx, y: ny };
}
/**
* 计算传送 / 跳跃的目标坐标
* @param tile 当前所在图块
* @param x 目标横坐标或偏移量
* @param y 目标纵坐标或偏移量
* @param rel 是否使用相对模式
*/
private calcPos(
tile: IHeroLocation,
x: number,
y: number,
rel: boolean
): ITileLocator {
if (rel) {
return { x: tile.x + x, y: tile.y + y };
}
return { x, y };
}
/**
* 检测当前移动方向前方一格的地形是否可通行
*/
private checkTerrain(
tile: IHeroLocation,
_controller: Readonly<IMoverController>
): HeroMoveCode {
if (!this.terrainChecker) return HeroMoveCode.CannotMove;
const locator: ITileLocator = { x: tile.x, y: tile.y };
const pass = this.terrainChecker.canPass(
locator,
this.moveDirection,
tile.floorId
);
return pass ? HeroMoveCode.Step : HeroMoveCode.CannotMove;
return { x: tile.x, y: tile.y };
}
}
+52 -13
View File
@@ -7,6 +7,7 @@ import {
import {
FaceDirection,
IDataCommonExtended,
IFaceHandler,
IObjectMovable,
IObjectMover,
ISaveableContent
@@ -229,21 +230,52 @@ export const enum HeroMoveCode {
CannotMove
}
export interface IHeroMoveHandlerBase extends IDataCommonExtended {
/** 当前位置 */
readonly currLoc: ITileLocator;
/** 要移动至的位置 */
readonly nextLoc: ITileLocator;
/** 移动方向 */
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 {
/** 对应的勇士状态对象 */
readonly hero: IHeroState<unknown>;
/**
* 检查目标位置是否在地图范围内
* @param x 横坐标
* @param y 纵坐标
* @param floorId 楼层 id
*/
inBound(x: number, y: number, floorId: string | undefined): boolean;
/**
* 判断在指定楼层中,从指定坐标向指定方向移动一格是否可通行
* @param locator 当前位置
* @param direction 移动方向
* @param floorId 当前楼层 id,可能为 undefined
* @param handler 通行性检查对象
*/
canPass(
locator: ITileLocator,
direction: FaceDirection,
floorId: string | undefined
): boolean;
canPass(handler: IPassCheckerHandler): boolean;
/**
* 判断在指定楼层中,从指定坐标向指定方向移动时是否应该产生撞击,撞击将会触发目标位置的触发器
* @param handler 通行性检查对象
*/
shouldHit(handler: IPassCheckerHandler): boolean;
}
export interface IHeroHitAction {
/**
* 勇士撞击某一个图块时执行的内容,一般用于触发目标位置的触发器
* @param handler 撞击行为对象
*/
hit(handler: IHeroHitHandler): Promise<void>;
}
export interface IHeroMoverConfig {
@@ -255,7 +287,8 @@ export interface IHeroMoverConfig {
autoSave?: boolean;
}
export interface IHeroMover<T extends IHeroLocation> extends IObjectMover<T> {
export interface IHeroMover<T extends IHeroLocation>
extends IObjectMover<T>, IDataCommonExtended {
/**
* 配置本次移动的行为模式
* @param config 配置对象,未传入的字段保持当前值
@@ -268,10 +301,16 @@ export interface IHeroMover<T extends IHeroLocation> extends IObjectMover<T> {
getConfig(): Readonly<IHeroMoverConfig>;
/**
* 设置地形通行判定器,传入 null 移除判定器
* @param checker 地形判定器,null 表示移除
* 设置地形通行判定器,传入 `null` 移表示移除
* @param checker 地形判定器
*/
useTerrainChecker(checker: ITerrainPassChecker | null): void;
/**
* 设置勇士撞击行为的执行对象,传入 `null` 表示移除。
* @param action 撞击行为对象
*/
useHitAction(action: IHeroHitAction | null): void;
}
//#endregion
+7 -7
View File
@@ -3,7 +3,7 @@ import {
getFaceMovement,
ObjectMover,
ObjectMoveStep,
ObjectMoveStepType
ObjectMoveType
} from '@user/data-common';
import { IDynamicTile } from './types';
import { DYNAMIC_MOVER_FACE } from '../shared';
@@ -47,33 +47,33 @@ export class DynamicTileMover extends ObjectMover<IDynamicTile> {
y: tile.y
};
switch (step.type) {
case ObjectMoveStepType.Dir: {
case ObjectMoveType.Dir: {
const { x, y } = getFaceMovement(step.move);
tile.setFaceDirection(step.move);
locator.x += x;
locator.y += y;
break;
}
case ObjectMoveStepType.DirFace: {
case ObjectMoveType.DirFace: {
const { x, y } = getFaceMovement(step.move);
tile.setFaceDirection(step.face);
locator.x += x;
locator.y += y;
break;
}
case ObjectMoveStepType.Face: {
case ObjectMoveType.Face: {
tile.setFaceDirection(step.value);
break;
}
case ObjectMoveStepType.Special: {
case ObjectMoveType.Special: {
const { x, y } = getFaceMovement(this.moveDirection);
tile.setFaceDirection(this.faceDirection);
locator.x += x;
locator.y += y;
break;
}
case ObjectMoveStepType.Teleport:
case ObjectMoveStepType.Jump: {
case ObjectMoveType.Teleport:
case ObjectMoveType.Jump: {
const { x, y, rel } = step;
tile.setFaceDirection(this.faceDirection);
if (rel) {
+1 -1
View File
@@ -40,7 +40,7 @@ export interface IStateBase extends IDataCommon {
getSaveableContent<T>(id: string): ISaveableContent<T> | null;
}
export interface IStateBaseExtended {
export interface IDataBaseExtended {
/** 当前对象对应的数据层对象(Layer 1 对象) */
readonly state: IStateBase;
}