refactor(game): 简化阵型移动逻辑并移除复杂碰撞处理

- 移除 `clampXByAllies` 及相关辅助方法,简化同排单位间的移动阻挡计算
- 重构 `getFormationSlotX` 方法,使用统一的战斗优先级和等级排序,不再按角色类型分层
- 新增 `moveToSlot` 方法集中处理向阵型位置移动的逻辑
- 引入 `isFormationParticipant` 方法,通过检查碰撞体启用状态更准确地筛选阵型参与者
- 删除多个未使用的移动调整参数,如 `meleeMinEnemyDistanceX`、`meleeOvertakeSpeedGap` 等
This commit is contained in:
panw
2026-03-31 15:00:07 +08:00
parent 67a59c9779
commit aa2bf8d6f6

View File

@@ -4,7 +4,7 @@ import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType, resolveFormationTargetX } from "../common/config/heroSet";
import { Node } from "cc";
import { BoxCollider2D, Node } from "cc";
@ecs.register('MoveComp')
export class MoveComp extends ecs.Comp {
@@ -49,20 +49,8 @@ interface MoveFacConfig {
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
/** 近战判定射程(来自 heroSet */
private readonly meleeAttackRange = HeroDisVal[HType.Melee];
/** 近战贴脸最小距离,避免完全重叠 */
private readonly meleeMinEnemyDistanceX = 60;
/** 同优先级近战允许“超车”时,至少要快这么多 */
private readonly meleeOvertakeSpeedGap = 20;
/** 常规同阵营横向最小间距 */
private readonly allySpacingX = 60;
/** 允许临时压缩站位时的最小间距 */
private readonly allyOverlapSpacingX = 14;
/** 友军偏离其目标点超过该值,可放宽让路 */
private readonly displacementReleaseX = 10;
/** 即将进入攻击位的锁定阈值 */
private readonly attackReadyLockX = 10;
/** 目标距离足够远才触发“借道前压” */
private readonly attackPassThresholdX = 60;
/** 纵向判定为同排的最大 Y 差 */
private readonly minSpacingY = 30;
/** 渲染层级重排节流,避免每帧排序 */
@@ -212,56 +200,16 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
private processFormationCombat(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
const currentX = view.node.position.x;
const targetX = this.getFormationSlotX(e, model, move.baseY);
move.targetX = targetX;
const needMoveToFormation = Math.abs(currentX - targetX) > 5;
if (needMoveToFormation) {
const dir = targetX > currentX ? 1 : -1;
move.direction = dir;
const speed = model.speed / 3;
this.moveEntity(view, dir, speed);
model.is_atking = true;
} else {
view.status_change("idle");
model.is_atking = true;
}
}
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 >= retreatMinX && safeRetreatX <= retreatMaxX) {
const retreatSpeed = (model.speed / 3) * 0.8;
this.moveEntity(view, -move.direction, retreatSpeed);
model.is_atking = false;
} else {
view.status_change("idle");
model.is_atking = true;
}
this.moveToSlot(view, move, model, targetX);
model.is_atking = true;
}
private processReturnFormation(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp) {
const currentX = view.node.position.x;
const targetX = this.getFormationSlotX(e, model, move.baseY);
move.targetX = targetX;
if (Math.abs(currentX - targetX) > 5) {
const dir = targetX > currentX ? 1 : -1;
const speed = model.speed / 3;
move.direction = dir;
this.moveEntity(view, dir, speed);
const newX = view.node.position.x;
if ((dir === 1 && newX > targetX) || (dir === -1 && newX < targetX)) {
if (!this.hasAnyActorTooClose(e, targetX, view.node.position.y)) {
view.node.setPosition(targetX, view.node.position.y, 0);
}
}
} else {
view.status_change("idle");
}
this.moveToSlot(view, move, model, targetX);
}
private getFormationSlotX(self: ecs.Entity, model: HeroAttrsComp, baseY: number): number {
@@ -269,24 +217,20 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
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 role = model.type as HType;
const sameRoleAllies: ecs.Entity[] = [];
let hasMeleeAlly = false;
let hasMidAlly = false;
const laneAllies: ecs.Entity[] = [];
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
const attrs = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!attrs || !view?.node) return;
if (attrs.is_dead || attrs.is_reviving) return;
if (attrs.fac !== model.fac) return;
if (Math.abs(view.node.position.y - baseY) >= this.minSpacingY) return;
const allyRole = attrs.type as HType;
if (allyRole === HType.Melee) hasMeleeAlly = true;
if (allyRole === HType.Mid) hasMidAlly = true;
if ((attrs.type as HType) !== role) return;
sameRoleAllies.push(e);
if (!this.isFormationParticipant(e, model.fac, baseY)) return;
laneAllies.push(e);
});
sameRoleAllies.sort((a, b) => {
laneAllies.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
const priorityA = attrsA ? this.getCombatPriority(attrsA) : 0;
const priorityB = attrsB ? this.getCombatPriority(attrsB) : 0;
if (priorityA !== priorityB) return priorityB - priorityA;
const lvA = attrsA?.lv ?? 1;
const lvB = attrsB?.lv ?? 1;
if (lvA !== lvB) return lvB - lvA;
const moveA = a.get(MoveComp);
const moveB = b.get(MoveComp);
const orderA = moveA?.spawnOrder ?? 0;
@@ -294,18 +238,25 @@ 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, sameRoleAllies.findIndex(entity => entity === self));
let roleDepth = 0;
if (role === HType.Mid) {
roleDepth = hasMeleeAlly ? 1 : 0;
} else if (role === HType.Long) {
roleDepth = (hasMeleeAlly ? 1 : 0) + (hasMidAlly ? 1 : 0);
}
const slotIndex = Math.max(0, laneAllies.findIndex(entity => entity === self));
const frontAnchorX = resolveFormationTargetX(model.fac, HType.Melee);
const targetX = frontAnchorX - forwardDir * (roleDepth + slotIndex) * this.allySpacingX;
const targetX = frontAnchorX - forwardDir * slotIndex * this.allySpacingX;
return Math.max(moveMinX, Math.min(moveMaxX, targetX));
}
private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) {
const currentX = view.node.position.x;
if (Math.abs(currentX - targetX) <= 2) {
view.node.setPosition(targetX, move.baseY, 0);
view.status_change("idle");
return;
}
const dir = targetX > currentX ? 1 : -1;
move.direction = dir;
const speed = model.speed / 3;
this.moveEntity(view, dir, speed, targetX);
}
private moveEntity(view: HeroViewComp, direction: number, speed: number, stopAtX?: number) {
const model = view.ent.get(HeroAttrsComp);
const move = view.ent.get(MoveComp);
@@ -330,13 +281,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
/** 指定停止点时,限制不越过 stopAtX */
newX = direction > 0 ? Math.min(newX, stopAtX) : Math.max(newX, stopAtX);
}
/** 结合同排友军占位,做“让位/防重叠”裁剪 */
newX = this.clampXByAllies(view.ent, model.fac, move.baseY, currentX, newX, direction);
if (direction > 0) {
newX = Math.max(currentX, newX);
} else {
newX = Math.min(currentX, newX);
}
if (Math.abs(newX - currentX) < 0.01) {
view.status_change("idle");
return;
@@ -345,100 +289,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
view.status_change("move");
}
private clampXByAllies(self: ecs.Entity, fac: number, baseY: number, currentX: number, proposedX: number, direction: number): number {
const selfAttrs = self.get(HeroAttrsComp);
const selfMove = self.get(MoveComp);
const selfPriority = selfAttrs ? this.getCombatPriority(selfAttrs) : 0;
let clampedX = proposedX;
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
if (e === self) return;
const attrs = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
const allyMove = e.get(MoveComp);
/** 只处理同阵营且同排Y 接近)的友军碰撞约束 */
if (!attrs || !view?.node || attrs.is_dead) return;
if (attrs.fac !== fac) return;
if (Math.abs(view.node.position.y - baseY) >= this.minSpacingY) return;
const allyPriority = this.getCombatPriority(attrs);
const facForwardDir = fac === FacSet.MON ? -1 : 1;
const isRetreating = direction !== facForwardDir;
if (isRetreating && selfPriority < allyPriority) return;
if (allyPriority < selfPriority) return;
const x = view.node.position.x;
/** 近战同优先级在满足条件时可超车,不做阻挡 */
if (this.shouldAllowMeleeOvertake(selfAttrs, selfMove, attrs, allyMove, currentX, x, direction, allyPriority, selfPriority)) return;
const spacing = this.resolveAllySpacing(selfAttrs, selfMove, currentX, direction, allyMove, x, allyPriority, selfPriority);
if (direction > 0 && x > currentX) {
clampedX = Math.min(clampedX, x - spacing);
}
if (direction < 0 && x < currentX) {
clampedX = Math.max(clampedX, x + spacing);
}
});
return clampedX;
}
private shouldAllowMeleeOvertake(
selfAttrs: HeroAttrsComp | null,
selfMove: MoveComp | null,
allyAttrs: HeroAttrsComp,
allyMove: MoveComp | null,
currentX: number,
allyX: number,
direction: number,
allyPriority: number,
selfPriority: number
): boolean {
if (!selfAttrs || !selfMove || !allyMove) return false;
/** 仅近战对近战、且同优先级才进入超车判定 */
if ((selfAttrs.type as HType) !== HType.Melee || (allyAttrs.type as HType) !== HType.Melee) return false;
if (allyPriority !== selfPriority) return false;
/** 我方更快,且双方都在前压且友军尚未到可攻击位,允许穿插 */
if (selfAttrs.speed <= allyAttrs.speed + this.meleeOvertakeSpeedGap) return false;
if (direction > 0 && allyX <= currentX) return false;
if (direction < 0 && allyX >= currentX) return false;
const selfTargetX = selfMove.targetX;
const allyTargetX = allyMove.targetX;
if (Math.abs(selfTargetX) <= 0.01 || Math.abs(allyTargetX) <= 0.01) return false;
const selfNeedAdvance = direction > 0 ? selfTargetX > currentX + 2 : selfTargetX < currentX - 2;
if (!selfNeedAdvance) return false;
const allyCanAttackNow = allyAttrs.enemy_in_cast_range || Math.abs(allyTargetX - allyX) <= this.attackReadyLockX;
if (allyCanAttackNow) return false;
const allyStillAdvancing = direction > 0 ? allyTargetX > allyX + 2 : allyTargetX < allyX - 2;
if (!allyStillAdvancing) return false;
return true;
}
private resolveAllySpacing(
selfAttrs: HeroAttrsComp | null,
selfMove: MoveComp | null,
currentX: number,
direction: number,
allyMove: MoveComp | null,
allyX: number,
allyPriority: number,
selfPriority: number
): number {
/** 默认保持标准间距,仅在“需要抢位输出”时放宽 */
if (!selfAttrs || !selfMove || !allyMove) return this.allySpacingX;
if ((selfAttrs.type as HType) !== HType.Melee) return this.allySpacingX;
if (allyPriority !== selfPriority) return this.allySpacingX;
const selfTargetX = selfMove.targetX;
const allyTargetX = allyMove.targetX;
const selfHasTarget = Math.abs(selfTargetX) > 0.01;
const allyHasTarget = Math.abs(allyTargetX) > 0.01;
if (!selfHasTarget || !allyHasTarget) return this.allySpacingX;
const selfDistToAttack = Math.abs(selfTargetX - currentX);
const canAttackNow = selfAttrs.enemy_in_cast_range || selfDistToAttack <= this.attackReadyLockX;
if (canAttackNow) return this.allySpacingX;
const targetTooFar = selfDistToAttack >= this.attackPassThresholdX;
if (!targetTooFar) return this.allySpacingX;
const allyDisplaced = Math.abs(allyX - allyTargetX) >= this.displacementReleaseX;
const selfNeedAdvance = direction > 0 ? selfTargetX > currentX + 2 : selfTargetX < currentX - 2;
if (allyDisplaced && selfNeedAdvance) return this.allyOverlapSpacingX;
return this.allySpacingX;
}
private getCombatPriority(model: HeroAttrsComp): number {
/** 数值越大越靠前:近战 > 中程 > 远程 */
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
@@ -447,19 +297,18 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return 1;
}
private hasAnyActorTooClose(self: ecs.Entity, x: number, y: number): boolean {
const myAttrs = self.get(HeroAttrsComp);
if (!myAttrs) return false;
return ecs.query(this.getHeroViewMatcher()).some(e => {
if (e === self) return false;
const attrs = e.get(HeroAttrsComp);
if (!attrs || attrs.is_dead) return false;
if (attrs.fac !== myAttrs.fac) return false;
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
return Math.abs(view.node.position.x - x) < this.allySpacingX
&& Math.abs(view.node.position.y - y) < this.minSpacingY;
});
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] {