feat(移动系统): 实现多线站位和防重叠机制

- 将移动边界配置从 min/max 改为 front/back 以支持双向移动逻辑
- 增加四条站位线(LINE1~LINE4)实现纵向分层站位
- 添加防重叠检测,防止同阵营单位位置冲突
- 调整游戏地平线高度和地图元素位置以适配新站位系统
- 禁用地图中多余的游戏对象以优化性能
This commit is contained in:
walkpan
2026-03-13 16:04:53 +08:00
parent b12b421823
commit 8d4ebcfdd8
3 changed files with 80 additions and 24 deletions

View File

@@ -18,7 +18,7 @@ export enum BoxSet {
LETF_END = -420,
RIGHT_END = 420,
//游戏地平线
GAME_LINE = -215,
GAME_LINE = -165
//攻击距离
}

View File

@@ -28,26 +28,35 @@ export class MoveComp extends ecs.Comp {
}
interface MoveFacConfig {
moveMinX: number;
moveMaxX: number;
retreatMinX: number;
retreatMaxX: number;
moveFrontX: number;
moveBackX: number;
retreatFrontX: number;
retreatBackX: number;
}
@ecs.register('MoveSystem')
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
private readonly ySlots = [
{ offset: 0, lineName: "LINE2" },
{ offset: 5, lineName: "LINE1" },
{ offset: -5, lineName: "LINE3" },
{ offset: -10, lineName: "LINE4" },
];
private readonly samePointXThreshold = 12;
private readonly samePointYThreshold = 3;
private readonly facConfigs: Record<number, MoveFacConfig> = {
[FacSet.HERO]: {
moveMinX: -320,
moveMaxX: 320,
retreatMinX: -300,
retreatMaxX: 300,
moveFrontX: 320,
moveBackX: -320,
retreatFrontX: 300,
retreatBackX: -300,
},
[FacSet.MON]: {
moveMinX: -320,
moveMaxX: 320,
retreatMinX: -300,
retreatMaxX: 300,
moveFrontX: -320,
moveBackX: 320,
retreatFrontX: -300,
retreatBackX: 300,
}
};
@@ -72,6 +81,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return;
}
this.applyLineAndY(e, view, move, view.node.position.x);
this.updateRenderOrder(e);
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) {
@@ -177,8 +187,10 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
private performRetreat(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, currentX: number) {
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
const retreatMinX = Math.min(cfg.retreatBackX, cfg.retreatFrontX);
const retreatMaxX = Math.max(cfg.retreatBackX, cfg.retreatFrontX);
const safeRetreatX = currentX - move.direction * 50;
if (safeRetreatX >= cfg.retreatMinX && safeRetreatX <= cfg.retreatMaxX) {
if (safeRetreatX >= retreatMinX && safeRetreatX <= retreatMaxX) {
const retreatSpeed = (model.speed / 3) * 0.8;
this.moveEntity(view, -move.direction, retreatSpeed);
model.is_atking = false;
@@ -233,23 +245,67 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const move = view.ent.get(MoveComp);
if (!model || !move) return;
const cfg = this.facConfigs[model.fac] || this.facConfigs[FacSet.HERO];
const moveMinX = Math.min(cfg.moveBackX, cfg.moveFrontX);
const moveMaxX = Math.max(cfg.moveBackX, cfg.moveFrontX);
const currentX = view.node.position.x;
const delta = speed * this.dt * direction;
let newX = view.node.position.x + delta;
if (currentX < cfg.moveMinX && direction < 0) {
if (currentX < moveMinX && direction < 0) {
view.status_change("idle");
return;
}
if (currentX > cfg.moveMaxX && direction > 0) {
if (currentX > moveMaxX && direction > 0) {
view.status_change("idle");
return;
}
newX = Math.max(cfg.moveMinX, Math.min(cfg.moveMaxX, newX));
const newY = view.node.position.y;
newX = Math.max(moveMinX, Math.min(moveMaxX, newX));
const newY = this.applyLineAndY(view.ent, view, move, newX);
view.node.setPosition(newX, newY, 0);
view.status_change("move");
}
private applyLineAndY(entity: ecs.Entity, view: HeroViewComp, move: MoveComp, x: number): number {
if (!view.node) return 0;
const baseY = move.baseY;
for (const slot of this.ySlots) {
const y = baseY + slot.offset;
if (!this.hasTeammateAtPoint(entity, x, y)) {
const lineNode = this.getLineNode(slot.lineName);
if (lineNode && view.node.parent !== lineNode) {
view.node.parent = lineNode;
}
return y;
}
}
const fallback = this.ySlots[0];
const fallbackNode = this.getLineNode(fallback.lineName);
if (fallbackNode && view.node.parent !== fallbackNode) {
view.node.parent = fallbackNode;
}
return baseY + fallback.offset;
}
private getLineNode(lineName: string) {
const scene = smc.map?.MapView?.scene;
const layerRoot = scene?.entityLayer?.node;
if (!layerRoot) return null;
return layerRoot.getChildByName(lineName);
}
private hasTeammateAtPoint(self: ecs.Entity, x: number, y: number): boolean {
const myAttrs = self.get(HeroAttrsComp);
if (!myAttrs) return false;
return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
if (e === self) return false;
const attrs = e.get(HeroAttrsComp);
if (!attrs || attrs.fac !== myAttrs.fac || attrs.is_dead) return false;
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
return Math.abs(view.node.position.x - x) <= this.samePointXThreshold
&& Math.abs(view.node.position.y - y) <= this.samePointYThreshold;
});
}
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
const minRange = model.getCachedMinSkillDistance();
const maxRange = model.getCachedMaxSkillDistance();