diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index 060ec10f..7144fabd 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -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; + + const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self)); - 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 lanePriority = [1, 0, 2]; // 中路优先,其次上路,最后下路 + const laneOffsets = [100, 0, -100]; - const targetX = frontAnchorX - forwardDir * totalSpacing; - return Math.max(moveMinX, Math.min(moveMaxX, targetX)); + 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();