feat(hero movement): 优化编队站位逻辑并添加平滑换路功能
重构英雄编队站位计算逻辑,支持多线路部署;新增平滑换路移动逻辑并控制换路速度;新增换路时触发移动动画的逻辑;移除过时方法,引入BoxSet统一配置游戏线路坐标。
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user