feat(hero movement): 优化编队站位逻辑并添加平滑换路功能

重构英雄编队站位计算逻辑,支持多线路部署;新增平滑换路移动逻辑并控制换路速度;新增换路时触发移动动画的逻辑;移除过时方法,引入BoxSet统一配置游戏线路坐标。
This commit is contained in:
panw
2026-05-12 19:31:15 +08:00
parent e98ea80a6d
commit 46a62e298c

View File

@@ -2,7 +2,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { FacSet } from "../common/config/GameSet";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType } from "../common/config/heroSet";
import { BoxCollider2D, Node } from "cc";
import { MonMoveComp } from "./MonMoveComp";
@@ -118,12 +118,25 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return;
}
/** 所有移动都锁定在 baseY避免出现“漂移” */
if (view.node.position.y !== move.baseY) {
// 1. 获取全局排位目标
const slot = this.getGlobalFormationSlot(e, model);
move.baseY = slot.targetY;
move.targetX = slot.targetX;
// 2. 平滑 Y 轴换路
let isChangingLane = false;
if (Math.abs(view.node.position.y - move.baseY) > 2) {
const currentY = view.node.position.y;
const deltaY = move.baseY - currentY;
const step = 400 * this.dt; // 换路速度
const newY = currentY + Math.sign(deltaY) * Math.min(Math.abs(deltaY), step);
view.node.setPosition(view.node.position.x, newY, 0);
isChangingLane = true;
} else {
view.node.setPosition(view.node.position.x, move.baseY, 0);
}
// 渲染层级重排放在独立的系统或这里统筹,这里我们让它处理所有英雄和怪物的排序
// 渲染层级重排
this.updateRenderOrder();
const nearestEnemy = this.findNearestEnemy(e);
@@ -134,10 +147,14 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} else {
/** 无敌人:清目标并回归编队站位 */
this.clearCombatTarget(model);
move.targetY = 0;
this.processReturnFormation(e, move, view, model);
this.moveToSlot(view, move, model, move.targetX);
model.is_atking = false;
}
// 如果只在 Y 轴移动,也要播放 move 动画
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
view.status_change("move");
}
}
private clearCombatTarget(model: HeroAttrsComp): void {
@@ -197,29 +214,23 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
private processFormationCombat(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
const targetX = this.getFormationSlotX(e, model, move.baseY);
move.targetX = targetX;
this.moveToSlot(view, move, model, targetX);
this.moveToSlot(view, move, model, move.targetX);
model.is_atking = true;
}
private processReturnFormation(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
const targetX = this.getFormationSlotX(e, model, move.baseY);
move.targetX = targetX;
this.moveToSlot(view, move, model, targetX);
}
private getFormationSlotX(self: ecs.Entity, model: HeroAttrsComp, baseY: number): number {
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 forwardDir = model.fac === FacSet.MON ? -1 : 1;
const laneAllies: ecs.Entity[] = [];
private getGlobalFormationSlot(self: ecs.Entity, model: HeroAttrsComp): { targetX: number, targetY: number } {
const allAllies: ecs.Entity[] = [];
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
if (!this.isFormationParticipant(e, model.fac, baseY)) return;
laneAllies.push(e);
const attrs = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
const move = e.get(MoveComp);
if (!attrs || !view?.node || !move) return;
if (attrs.is_dead || attrs.is_reviving) return;
if (attrs.fac !== model.fac) return;
allAllies.push(e);
});
laneAllies.sort((a, b) => {
allAllies.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
const priorityA = attrsA ? this.getCombatPriority(attrsA) : 0;
@@ -235,22 +246,19 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (orderA !== orderB) return orderA - orderB;
return a.eid - b.eid;
});
const slotIndex = Math.max(0, laneAllies.findIndex(entity => entity === self));
const frontAnchorX = model.fac === FacSet.MON ? this.monFrontAnchorX : this.heroFrontAnchorX;
let totalSpacing = 0;
for (let i = 1; i <= slotIndex; i++) {
const prevAttrs = laneAllies[i - 1].get(HeroAttrsComp);
const currAttrs = laneAllies[i].get(HeroAttrsComp);
const isPrevBoss = prevAttrs?.is_boss;
const isCurrBoss = currAttrs?.is_boss;
const baseSpacing = model.fac === FacSet.MON ? this.monAllySpacingX : this.heroAllySpacingX;
const spacing = (isPrevBoss || isCurrBoss) ? 100 : baseSpacing;
totalSpacing += spacing;
}
const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self));
const targetX = frontAnchorX - forwardDir * totalSpacing;
return Math.max(moveMinX, Math.min(moveMaxX, targetX));
const lanePriority = [1, 0, 2]; // 中路优先,其次上路,最后下路
const laneOffsets = [100, 0, -100];
const col = Math.floor(slotIndex / 3);
const laneIdx = lanePriority[slotIndex % 3];
const targetY = BoxSet.GAME_LINE + laneOffsets[laneIdx];
const targetX = this.heroFrontAnchorX - col * this.heroAllySpacingX;
return { targetX, targetY };
}
private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) {
@@ -306,20 +314,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return 1;
}
private isFormationParticipant(entity: ecs.Entity, fac: number, baseY: number): boolean {
const attrs = entity.get(HeroAttrsComp);
const view = entity.get(HeroViewComp);
const move = entity.get(MoveComp);
if (!attrs || !view?.node || !move) return false;
if (attrs.is_dead || attrs.is_reviving) return false;
if (attrs.fac !== fac) return false;
if (!move.moving) return false;
if (Math.abs(view.node.position.y - baseY) >= this.minSpacingY) return false;
const collider = view.node.getComponent(BoxCollider2D);
if (collider && !collider.enabled) return false;
return true;
}
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
const minRange = model.getCachedMinSkillDistance();
const maxRange = model.getCachedMaxSkillDistance();