mirror of
https://github.com/motajs/template.git
synced 2026-11-03 07:24:30 +08:00
feat: 地形通行性判断
This commit is contained in:
1 parent
eb34d63e96
commit
0508dfb9e4
9 files changed
+288
-20
No files matched your search
@@ -6,6 +6,7 @@ import { IHeroLocation, IHeroLocationSave, IHeroMover } from './types';
|
||||
export class HeroLocation implements IHeroLocation {
|
||||
x: number;
|
||||
y: number;
|
||||
floorId: string | undefined = undefined;
|
||||
|
||||
readonly state: IDataCommon;
|
||||
readonly mover: IHeroMover<this>;
|
||||
@@ -36,13 +37,15 @@ export class HeroLocation implements IHeroLocation {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
direction: this.mover.faceDirection
|
||||
direction: this.mover.faceDirection,
|
||||
floorId: this.floorId
|
||||
};
|
||||
}
|
||||
|
||||
loadState(state: IHeroLocationSave): void {
|
||||
this.x = state.x;
|
||||
this.y = state.y;
|
||||
this.floorId = state.floorId;
|
||||
this.mover.setFaceDir(state.direction);
|
||||
}
|
||||
}
|
||||
@@ -12,35 +12,39 @@ import {
|
||||
HeroMoveCode,
|
||||
IHeroLocation,
|
||||
IHeroMover,
|
||||
IHeroMoverConfig
|
||||
IHeroMoverConfig,
|
||||
ITerrainPassChecker
|
||||
} from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
export class HeroMover<T extends IHeroLocation>
|
||||
extends ObjectMover<T>
|
||||
implements IHeroMover<T>
|
||||
{
|
||||
readonly tile: T;
|
||||
|
||||
/** 本次移动是否不记录进路线系统 */
|
||||
private noRoute: boolean = false;
|
||||
/** 本次移动是否忽略地形碰撞检测 */
|
||||
private ignoreTerrain: boolean = false;
|
||||
/** 本次移动是否在特定时机触发自动存档 */
|
||||
private autoSave: boolean = false;
|
||||
/** 自定义地形通行判定器 */
|
||||
private terrainChecker: ITerrainPassChecker | null = null;
|
||||
|
||||
constructor(tile: T, faceHandler: IFaceHandler<FaceDirection>) {
|
||||
constructor(
|
||||
readonly tile: T,
|
||||
faceHandler: IFaceHandler<FaceDirection>
|
||||
) {
|
||||
super(faceHandler);
|
||||
this.tile = tile;
|
||||
}
|
||||
|
||||
config(config: Readonly<IHeroMoverConfig>): this {
|
||||
if (config.noRoute !== undefined) {
|
||||
if (!isNil(config.noRoute)) {
|
||||
this.noRoute = config.noRoute;
|
||||
}
|
||||
if (config.ignoreTerrain !== undefined) {
|
||||
if (!isNil(config.ignoreTerrain)) {
|
||||
this.ignoreTerrain = config.ignoreTerrain;
|
||||
}
|
||||
if (config.autoSave !== undefined) {
|
||||
if (!isNil(config.autoSave)) {
|
||||
this.autoSave = config.autoSave;
|
||||
}
|
||||
return this;
|
||||
@@ -54,6 +58,10 @@ export class HeroMover<T extends IHeroLocation>
|
||||
};
|
||||
}
|
||||
|
||||
useTerrainChecker(checker: ITerrainPassChecker | null): void {
|
||||
this.terrainChecker = checker;
|
||||
}
|
||||
|
||||
protected async onMoveStart(
|
||||
_tile: IHeroLocation,
|
||||
_controller: Readonly<IMoverController>
|
||||
@@ -81,9 +89,7 @@ export class HeroMover<T extends IHeroLocation>
|
||||
|
||||
if (!this.ignoreTerrain) {
|
||||
const result = this.checkTerrain(tile, controller);
|
||||
if (result !== HeroMoveCode.Step) {
|
||||
return result;
|
||||
}
|
||||
if (result !== HeroMoveCode.Step) return result;
|
||||
}
|
||||
|
||||
if (this.autoSave) {
|
||||
@@ -168,10 +174,16 @@ export class HeroMover<T extends IHeroLocation>
|
||||
* 检测当前移动方向前方一格的地形是否可通行
|
||||
*/
|
||||
private checkTerrain(
|
||||
_tile: IHeroLocation,
|
||||
tile: IHeroLocation,
|
||||
_controller: Readonly<IMoverController>
|
||||
): HeroMoveCode {
|
||||
// TODO: 需要地形检测接口(替代 core.noPass / core.canMoveHero)
|
||||
return HeroMoveCode.Step;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import { IFacedTileLocator, IHookBase, IHookable } from '@motajs/common';
|
||||
import {
|
||||
IFacedTileLocator,
|
||||
IHookBase,
|
||||
IHookable,
|
||||
ITileLocator
|
||||
} from '@motajs/common';
|
||||
import {
|
||||
FaceDirection,
|
||||
IDataCommonExtended,
|
||||
IObjectMovable,
|
||||
IObjectMover,
|
||||
@@ -186,13 +192,24 @@ export interface IHeroAttribute<THero> extends IReadonlyHeroAttribute<THero> {
|
||||
|
||||
//#region 勇士位置
|
||||
|
||||
export interface IHeroLocationSave extends Readonly<IFacedTileLocator> {}
|
||||
export interface IHeroLocationSave {
|
||||
/** 当前横坐标 */
|
||||
readonly x: number;
|
||||
/** 当前纵坐标 */
|
||||
readonly y: number;
|
||||
/** 当前朝向 */
|
||||
readonly direction: FaceDirection;
|
||||
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
||||
readonly floorId: string | undefined;
|
||||
}
|
||||
|
||||
export interface IHeroLocation
|
||||
extends
|
||||
ISaveableContent<IHeroLocationSave>,
|
||||
IObjectMovable,
|
||||
IDataCommonExtended {
|
||||
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
||||
readonly floorId: string | undefined;
|
||||
/** 勇士的移动对象 */
|
||||
readonly mover: IObjectMover<this>;
|
||||
}
|
||||
@@ -212,6 +229,23 @@ export const enum HeroMoveCode {
|
||||
CannotMove
|
||||
}
|
||||
|
||||
export interface ITerrainPassChecker {
|
||||
/** 对应的勇士状态对象 */
|
||||
readonly hero: IHeroState<unknown>;
|
||||
|
||||
/**
|
||||
* 判断在指定楼层中,从指定坐标向指定方向移动一格是否可通行
|
||||
* @param locator 当前位置
|
||||
* @param direction 移动方向
|
||||
* @param floorId 当前楼层 id,可能为 undefined
|
||||
*/
|
||||
canPass(
|
||||
locator: ITileLocator,
|
||||
direction: FaceDirection,
|
||||
floorId: string | undefined
|
||||
): boolean;
|
||||
}
|
||||
|
||||
export interface IHeroMoverConfig {
|
||||
/** 本次移动是否不记录进路线系统 */
|
||||
noRoute?: boolean;
|
||||
@@ -232,6 +266,12 @@ export interface IHeroMover<T extends IHeroLocation> extends IObjectMover<T> {
|
||||
* 获取当前移动配置的只读快照
|
||||
*/
|
||||
getConfig(): Readonly<IHeroMoverConfig>;
|
||||
|
||||
/**
|
||||
* 设置地形通行判定器,传入 null 移除判定器
|
||||
* @param checker 地形判定器,null 表示移除
|
||||
*/
|
||||
useTerrainChecker(checker: ITerrainPassChecker | null): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -247,6 +247,8 @@ export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
||||
readonly currAnimDir: ObjectAnimDirection;
|
||||
/** 当前移动速度,单位毫秒 */
|
||||
readonly currentSpeed: number;
|
||||
/** 朝向信息对象 */
|
||||
readonly faceHandler: IFaceHandler<FaceDirection>;
|
||||
|
||||
/**
|
||||
* 立刻设置移动对象的位置
|
||||
@@ -367,8 +369,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
||||
/** 是否调用了 `IMoverController.stop` 接口 */
|
||||
private shouldStop: boolean = false;
|
||||
|
||||
/** 朝向处理 */
|
||||
private readonly faceHandler: IFaceHandler<FaceDirection>;
|
||||
readonly faceHandler: IFaceHandler<FaceDirection>;
|
||||
|
||||
constructor(faceHandler: IFaceHandler<FaceDirection>) {
|
||||
super();
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './passChecker';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ITileLocator } from '@motajs/common';
|
||||
import { FaceDirection, PassBit } from '@user/data-common';
|
||||
import { IHeroState, IStateBase, ITerrainPassChecker } from '@user/data-base';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
export class DefaultPassChecker implements ITerrainPassChecker {
|
||||
constructor(
|
||||
readonly state: IStateBase,
|
||||
readonly hero: IHeroState<unknown>
|
||||
) {}
|
||||
|
||||
canPass(
|
||||
locator: ITileLocator,
|
||||
direction: FaceDirection,
|
||||
floorId: string | undefined
|
||||
): boolean {
|
||||
if (isNil(floorId)) return false;
|
||||
if (
|
||||
direction === FaceDirection.LeftDown ||
|
||||
direction === FaceDirection.LeftUp ||
|
||||
direction === FaceDirection.RightDown ||
|
||||
direction === FaceDirection.RightUp
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const layerState = this.state.maps.getLayerState(floorId);
|
||||
if (!layerState) return false;
|
||||
const eventLayer = layerState.eventLayer;
|
||||
if (!eventLayer) return false;
|
||||
|
||||
const { x, y } = locator;
|
||||
const face = this.hero.location.mover.faceHandler;
|
||||
const { x: dx, y: dy } = face.movement(direction);
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
const opposite = face.opposite(direction);
|
||||
const leaveMask = this.directionToPassBit(direction);
|
||||
const enterMask = this.directionToPassBit(opposite);
|
||||
|
||||
let canLeave = true;
|
||||
let canEnter = true;
|
||||
|
||||
const eventCurr = eventLayer.getLocationData(x, y);
|
||||
const eventNext = eventLayer.getLocationData(nx, ny);
|
||||
if (eventCurr && eventCurr.raw) {
|
||||
canLeave = !!(leaveMask & eventCurr.raw.pass.outPass);
|
||||
}
|
||||
if (eventNext && eventNext.raw) {
|
||||
canEnter = !!(enterMask & eventNext.raw.pass.inPass);
|
||||
}
|
||||
|
||||
if (!canLeave || !canEnter) return false;
|
||||
|
||||
for (const layer of layerState.layerList) {
|
||||
if (layer === eventLayer) continue;
|
||||
const curr = layer.getLocationData(x, y);
|
||||
const next = layer.getLocationData(nx, ny);
|
||||
if (curr && curr.raw && curr.raw.pass.onlyEvents) {
|
||||
canLeave = !!(leaveMask & curr.raw.pass.outPass);
|
||||
}
|
||||
if (next && next.raw && next.raw.pass.onlyEvents) {
|
||||
canEnter = !!(enterMask & next.raw.pass.inPass);
|
||||
}
|
||||
if (!canLeave || !canEnter) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将朝向转换为对应的通行性位掩码。
|
||||
* @param dir 朝向
|
||||
*/
|
||||
private directionToPassBit(dir: FaceDirection): number {
|
||||
switch (dir) {
|
||||
case FaceDirection.Up:
|
||||
return PassBit.Up;
|
||||
case FaceDirection.Right:
|
||||
return PassBit.Right;
|
||||
case FaceDirection.Down:
|
||||
return PassBit.Down;
|
||||
case FaceDirection.Left:
|
||||
return PassBit.Left;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user