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 { HeroViewComp } from "./HeroViewComp";
|
||||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
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 { HeroDisVal, HType } from "../common/config/heroSet";
|
||||||
import { BoxCollider2D, Node } from "cc";
|
import { BoxCollider2D, Node } from "cc";
|
||||||
import { MonMoveComp } from "./MonMoveComp";
|
import { MonMoveComp } from "./MonMoveComp";
|
||||||
@@ -118,12 +118,25 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 所有移动都锁定在 baseY,避免出现“漂移” */
|
// 1. 获取全局排位目标
|
||||||
if (view.node.position.y !== move.baseY) {
|
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);
|
view.node.setPosition(view.node.position.x, move.baseY, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染层级重排放在独立的系统或这里统筹,这里我们让它处理所有英雄和怪物的排序
|
// 渲染层级重排
|
||||||
this.updateRenderOrder();
|
this.updateRenderOrder();
|
||||||
|
|
||||||
const nearestEnemy = this.findNearestEnemy(e);
|
const nearestEnemy = this.findNearestEnemy(e);
|
||||||
@@ -134,10 +147,14 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
} else {
|
} else {
|
||||||
/** 无敌人:清目标并回归编队站位 */
|
/** 无敌人:清目标并回归编队站位 */
|
||||||
this.clearCombatTarget(model);
|
this.clearCombatTarget(model);
|
||||||
move.targetY = 0;
|
this.moveToSlot(view, move, model, move.targetX);
|
||||||
this.processReturnFormation(e, move, view, model);
|
|
||||||
model.is_atking = false;
|
model.is_atking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果只在 Y 轴移动,也要播放 move 动画
|
||||||
|
if (isChangingLane && view.status !== "move" && view.status !== "atk") {
|
||||||
|
view.status_change("move");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearCombatTarget(model: HeroAttrsComp): void {
|
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) {
|
private processFormationCombat(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
|
||||||
const targetX = this.getFormationSlotX(e, model, move.baseY);
|
this.moveToSlot(view, move, model, move.targetX);
|
||||||
move.targetX = targetX;
|
|
||||||
this.moveToSlot(view, move, model, targetX);
|
|
||||||
model.is_atking = true;
|
model.is_atking = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private processReturnFormation(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
|
private getGlobalFormationSlot(self: ecs.Entity, model: HeroAttrsComp): { targetX: number, targetY: number } {
|
||||||
const targetX = this.getFormationSlotX(e, model, move.baseY);
|
const allAllies: ecs.Entity[] = [];
|
||||||
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[] = [];
|
|
||||||
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
|
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
|
||||||
if (!this.isFormationParticipant(e, model.fac, baseY)) return;
|
const attrs = e.get(HeroAttrsComp);
|
||||||
laneAllies.push(e);
|
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 attrsA = a.get(HeroAttrsComp);
|
||||||
const attrsB = b.get(HeroAttrsComp);
|
const attrsB = b.get(HeroAttrsComp);
|
||||||
const priorityA = attrsA ? this.getCombatPriority(attrsA) : 0;
|
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;
|
if (orderA !== orderB) return orderA - orderB;
|
||||||
return a.eid - b.eid;
|
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;
|
const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self));
|
||||||
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 targetX = frontAnchorX - forwardDir * totalSpacing;
|
const lanePriority = [1, 0, 2]; // 中路优先,其次上路,最后下路
|
||||||
return Math.max(moveMinX, Math.min(moveMaxX, targetX));
|
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) {
|
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;
|
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] {
|
private resolveCombatRange(model: HeroAttrsComp, defaultMin: number, defaultMax: number): [number, number] {
|
||||||
const minRange = model.getCachedMinSkillDistance();
|
const minRange = model.getCachedMinSkillDistance();
|
||||||
const maxRange = model.getCachedMaxSkillDistance();
|
const maxRange = model.getCachedMaxSkillDistance();
|
||||||
|
|||||||
Reference in New Issue
Block a user