refactor(battle): 重构战斗目标查找与位置管理逻辑

新增全局位置网格系统,用于按索引存储敌我单位实体ID:
-  在SingletonModuleComp添加heroGrid与monGrid数组
-  为HeroAttrsComp新增posIndex字段记录位置索引并初始化

优化战斗核心流程:
-  重构MissionHeroComp的位置选择逻辑,拆分方法返回位置索引而非直接坐标,优化位置占用检测
-  重构SCastSystem的目标查找与收集逻辑,改用网格遍历替代全量实体查询,大幅提升性能
-  统一三路单位的查找优先级,简化代码提升可维护性
-  完善Hero与Monster的创建销毁流程,同步更新网格的单位注册与注销信息
This commit is contained in:
pan
2026-06-17 09:45:46 +08:00
parent 06a47842dd
commit b6b2dff986
7 changed files with 144 additions and 142 deletions

View File

@@ -100,12 +100,17 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
let highestAp = baseAp;
// 2. 获取场上最高攻击力的英雄,保证后期奶量/增益绝对够用
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
const attr = e.get(HeroAttrsComp);
if (attr && attr.fac === FacSet.HERO && !attr.is_dead && attr.ap > highestAp) {
highestAp = attr.ap;
for (const eid of smc.mission.heroGrid) {
if (eid >= 0) {
const entity = ecs.getEntityByEid(eid);
if (entity) {
const attr = entity.get(HeroAttrsComp);
if (attr && !attr.is_dead && attr.ap > highestAp) {
highestAp = attr.ap;
}
}
}
});
}
mockAttrs.ap = highestAp;
mockAttrs.critical = 0;
@@ -562,16 +567,21 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
*/
private collectFriendlyTargetEids(fac: number, selfEid: number | undefined, includeSelf: boolean): number[] {
const eids: number[] = [];
ecs.query(this.getHeroMatcher()).forEach(entity => {
const model = entity.get(HeroAttrsComp);
const view = entity.get(HeroViewComp);
if (!model || !view?.node || !view.ent) return;
if (model.fac !== fac) return;
if (model.is_dead || model.is_reviving) return;
const eid = view.ent.eid;
if (!includeSelf && typeof selfEid === "number" && eid === selfEid) return;
eids.push(eid);
});
const grid = fac === FacSet.HERO ? smc.mission.heroGrid : smc.mission.monGrid;
for (const eid of grid) {
if (eid >= 0) {
if (!includeSelf && typeof selfEid === "number" && eid === selfEid) continue;
const entity = ecs.getEntityByEid(eid);
if (entity) {
const model = entity.get(HeroAttrsComp);
if (model && !model.is_dead && !model.is_reviving) {
eids.push(eid);
}
}
}
}
return eids;
}
@@ -592,119 +602,85 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return group === TGroup.Self;
}
/**
* 根据网格快速查找最近的敌人
* 英雄查找怪物按列从前往后列0 -> 列3列内优先同排
* 怪物查找英雄按列从前往后列0 -> 列1列内优先中路或同排
*/
private findNearestEnemyByGrid(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
if (!heroView.node) return null;
const isHero = heroAttrs.fac === FacSet.HERO;
const myPosIndex = heroAttrs.posIndex;
let myRow = myPosIndex >= 0 ? myPosIndex % 3 : 1; // 默认中路
const currentX = heroView.node.position.x;
let targetView: HeroViewComp | null = null;
let minCol = -1;
if (isHero) {
// 英雄找怪物
for (let col = 0; col < 4; col++) {
// 列内顺序:优先同排,其次中路,再次其他
const rowOrder = myRow === 1 ? [1, 0, 2] : [myRow, 1, myRow === 0 ? 2 : 0];
for (const row of rowOrder) {
const idx = col * 3 + row;
const eid = smc.mission.monGrid[idx];
if (eid >= 0) {
const target = ecs.getEntityByEid(eid);
if (target) {
const tModel = target.get(HeroAttrsComp);
const tView = target.get(HeroViewComp);
if (tModel && !tModel.is_dead && !tModel.is_reviving && tView && tView.node) {
const dist = Math.abs(currentX - tView.node.position.x);
if (dist <= maxRange) {
return tView; // 找到列内最优且在射程内的目标,直接返回
}
}
}
}
}
}
} else {
// 怪物找英雄
for (let col = 0; col < 2; col++) {
// 列内顺序:怪物配置优先中路
const rowOrder = [1, myRow, myRow === 0 ? 2 : 0];
for (const row of rowOrder) {
const idx = col * 3 + row;
const eid = smc.mission.heroGrid[idx];
if (eid >= 0) {
const target = ecs.getEntityByEid(eid);
if (target) {
const tModel = target.get(HeroAttrsComp);
const tView = target.get(HeroViewComp);
if (tModel && !tModel.is_dead && !tModel.is_reviving && tView && tView.node) {
const dist = Math.abs(currentX - tView.node.position.x);
if (dist <= maxRange) {
return tView;
}
}
}
}
}
}
}
return null;
}
/**
* 在施法距离内查找最近敌人。
* 用于单体技能与基础目标参考
* 考虑三路设计同路Y差较小优先如果同路没有目标再考虑跨路
* 替换为网格化查找,大幅提升性能并解决同排优先问题
*/
private findNearestEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
if (!heroView.node) return null;
const currentX = heroView.node.position.x;
const currentY = heroView.node.position.y;
let nearest: HeroViewComp | null = null;
let minDist = Infinity;
let foundPreferredLane = false;
const isHero = heroAttrs.fac === FacSet.HERO;
ecs.query(this.getHeroMatcher()).forEach(entity => {
const attrs = entity.get(HeroAttrsComp);
const view = entity.get(HeroViewComp);
if (!attrs || !view?.node) return;
if (attrs.fac === heroAttrs.fac) return;
if (attrs.is_dead || attrs.is_reviving) return;
if (this.isOutOfBattleBounds(view.node.position.x)) return;
const distX = Math.abs(currentX - view.node.position.x);
if (distX > maxRange) return;
if (isHero) {
// 英雄单纯找X轴最近无视路线
if (distX < minDist) {
minDist = distX;
nearest = view;
}
} else {
// 怪物:优先找中路目标
const isMidLane = Math.abs(view.node.position.y - BoxSet.GAME_LINE) < 30; // BoxSet.GAME_LINE(100) 是中路
if (foundPreferredLane && !isMidLane) return;
if (isMidLane && !foundPreferredLane) {
foundPreferredLane = true;
minDist = distX;
nearest = view;
return;
}
if (distX >= minDist) return;
minDist = distX;
nearest = view;
}
});
return nearest;
return this.findNearestEnemyByGrid(heroAttrs, heroView, maxRange);
}
/**
* 在施法距离内查找“最前排”敌人。
* 依据施法者面向方向选择 x 轴上更前的目标
* 考虑三路设计:英雄找最前排,怪物优先找中路最前排
* 依据网格排布列0天然是最前排因此直接复用网格查找即可
*/
private findFrontEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number, nearestEnemy: HeroViewComp): HeroViewComp | null {
if (!heroView.node || !nearestEnemy.node) return null;
const currentX = heroView.node.position.x;
const currentY = heroView.node.position.y;
const direction = nearestEnemy.node.position.x >= currentX ? 1 : -1;
let frontEnemy: HeroViewComp | null = null;
let edgeX = direction > 0 ? Infinity : -Infinity;
let foundPreferredLane = false;
const isHero = heroAttrs.fac === FacSet.HERO;
ecs.query(this.getHeroMatcher()).forEach(entity => {
const attrs = entity.get(HeroAttrsComp);
const view = entity.get(HeroViewComp);
if (!attrs || !view?.node) return;
if (attrs.fac === heroAttrs.fac) return;
if (attrs.is_dead || attrs.is_reviving) return;
const enemyX = view.node.position.x;
if (this.isOutOfBattleBounds(enemyX)) return;
const dist = Math.abs(currentX - enemyX);
if (dist > maxRange) return;
if (isHero) {
// 英雄无视路线找X轴最前的
if (direction > 0) {
if (enemyX >= edgeX) return;
edgeX = enemyX;
} else {
if (enemyX <= edgeX) return;
edgeX = enemyX;
}
frontEnemy = view;
} else {
// 怪物:优先找中路最前排的
const isMidLane = Math.abs(view.node.position.y - BoxSet.GAME_LINE) < 30; // BoxSet.GAME_LINE(100) 是中路
if (foundPreferredLane && !isMidLane) return;
if (isMidLane && !foundPreferredLane) {
foundPreferredLane = true;
edgeX = enemyX;
frontEnemy = view;
return;
}
if (direction > 0) {
if (enemyX >= edgeX) return;
edgeX = enemyX;
} else {
if (enemyX <= edgeX) return;
edgeX = enemyX;
}
frontEnemy = view;
}
});
return frontEnemy;
return this.findNearestEnemyByGrid(heroAttrs, heroView, maxRange);
}
/**