refactor(map/hero): 重构英雄位置管理逻辑,移除lane相关字段

重构了英雄分路排位的旧实现,改用硬编码的点位数组管理英雄站位,移除了HeroAttrsComp中的lane和lane_index字段,简化了英雄位置分配、UI面板绑定的逻辑,提升代码可维护性。
This commit is contained in:
walkpan
2026-05-13 23:48:58 +08:00
parent e3a9d447ba
commit 3f47df2682
5 changed files with 105 additions and 265 deletions

View File

@@ -39,6 +39,7 @@ import { FacSet, FightSet, BoxSet } from "../common/config/GameSet";
import { oneCom } from "../skill/oncend";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
import { MoveComp } from "../hero/MoveComp";
const { ccclass } = _decorator;
/**
@@ -52,18 +53,18 @@ const { ccclass } = _decorator;
export class MissionHeroCompComp extends CCComp {
// ======================== 常量 ========================
/** 硬编码的6个英雄占位点 */
public static readonly HERO_POSITIONS: Vec3[] = [
v3(-200, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front
v3(-200, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front
v3(-200, BoxSet.GAME_LINE - 100, 0), // index 2 (node_index 3): Bot Front
v3(-300, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back
v3(-300, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back
v3(-300, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
];
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */
private static readonly HERO_DROP_HEIGHT = 260
/** 近战英雄起始出生 X 坐标 */
private static readonly HERO_SPAWN_START_MELEE_X = -320
/** 远程(含中程)英雄起始出生 X 坐标 */
private static readonly HERO_SPAWN_START_RANGED_X = -320
/** 三路高度偏移(上路, 中路, 下路) */
private static readonly HERO_LANE_Y_OFFSETS = [ BoxSet.GAME_LINE+90, BoxSet.GAME_LINE, BoxSet.GAME_LINE-90]
/** 每路前排容量 */
private static readonly HERO_LANE_CAP = 2
/** 同路内 X 间距 */
private static readonly HERO_GAP_X = 100
// ======================== 运行时属性 ========================
@@ -131,10 +132,7 @@ export class MissionHeroCompComp extends CCComp {
if (model && view) {
if (model.is_dead) {
view.alive();
const { lane, indexInLane } = this.pickLaneForHero(model.hero_uuid, [hero.eid]);
model.lane = lane;
model.lane_index = indexInLane;
const landingPos = this.resolveHeroLandingPos(model.hero_uuid, lane, indexInLane);
const landingPos = this.pickPositionForHero([hero.eid]);
// 不再直接设置位置,而是播放下落入场动画
// 计算出出生点(空中)
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
@@ -170,43 +168,39 @@ export class MissionHeroCompComp extends CCComp {
// ======================== 英雄生成 ========================
/**
* 动态分配英雄上场的路和排位(优先中路 -> 上路 -> 下路)
* 标记英雄的6个登录点
* @param uuid 英雄 UUID
* 动态分配英雄上场的位置
* @param excludeEids 排除计算的实体ID数组避免复活或合成时把自己算成占据的位置
*/
private pickLaneForHero(uuid: number, excludeEids: number[] = []): { lane: number; indexInLane: number } {
private pickPositionForHero(excludeEids: number[] = []): Vec3 {
const heroes = this.getAllHeroes().filter(h => {
const m = h.get(HeroAttrsComp);
return m && !m.is_dead && !excludeEids.includes(h.eid);
});
// 记录6个位置点的占用情况 [lane][indexInLane]
const occupied = [
[false, false], // 上路 0
[false, false], // 中路 1
[false, false] // 下路 2
];
const occupied = new Set<number>();
for (const h of heroes) {
const m = h.get(HeroAttrsComp);
if (m && m.lane >= 0 && m.lane <= 2 && m.lane_index >= 0 && m.lane_index <= 1) {
occupied[m.lane][m.lane_index] = true;
}
}
// 优先中路(1) -> 上路(0) -> 下路(2)
const priority = [1, 0, 2];
for (let indexInLane = 0; indexInLane < MissionHeroCompComp.HERO_LANE_CAP; indexInLane++) {
for (const lane of priority) {
if (!occupied[lane][indexInLane]) {
return { lane, indexInLane };
const move = h.get(MoveComp); // MoveComp 记录了英雄当前的目标位置
if (move) {
for (let i = 0; i < MissionHeroCompComp.HERO_POSITIONS.length; i++) {
const pos = MissionHeroCompComp.HERO_POSITIONS[i];
if (Math.abs(move.targetX - pos.x) < 2 && Math.abs(move.baseY - pos.y) < 2) {
occupied.add(i);
break;
}
}
}
}
// 溢出:仍放中路,沿 X 继续排
return { lane: 1, indexInLane: 2 };
// 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5)
const slotPriority = [1, 0, 2, 4, 3, 5];
for (const idx of slotPriority) {
if (!occupied.has(idx)) {
return MissionHeroCompComp.HERO_POSITIONS[idx];
}
}
// 溢出:默认中前
return MissionHeroCompComp.HERO_POSITIONS[1];
}
/**
@@ -223,16 +217,13 @@ export class MissionHeroCompComp extends CCComp {
console.log("addHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const { lane, indexInLane } = this.pickLaneForHero(uuid);
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
const landingPos = this.pickPositionForHero();
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv);
// 召唤完成后,派发事件以更新英雄面板
const model = hero.get(HeroAttrsComp);
if (model) {
model.lane = lane;
model.lane_index = indexInLane;
oops.message.dispatchEvent(GameEvent.MasterCalled, {
eid: hero.eid,
model: model
@@ -242,33 +233,7 @@ export class MissionHeroCompComp extends CCComp {
return hero;
}
/**
* 计算英雄落点位置。
* Y 坐标来自 HeroPos 配置X 坐标根据英雄类型(近战/远程)决定。
*
* @param uuid 英雄 UUID
* @param lane 分配到的路 (0: 上, 1: 中, 2: 下)
* @param indexInLane 该路排位
* @returns 落点 Vec3
*/
private resolveHeroLandingPos(uuid: number, lane: number, indexInLane: number): Vec3 {
const hero_pos = 0;
const baseY = HeroPos[hero_pos].pos.y + MissionHeroCompComp.HERO_LANE_Y_OFFSETS[lane];
const startX = this.resolveSpawnStartX(uuid);
return v3(startX + indexInLane * MissionHeroCompComp.HERO_GAP_X, baseY, 0);
}
/**
* 根据英雄类型决定出生 X 坐标。
* @param uuid 英雄 UUID
* @returns 近战 or 远程的起始 X
*/
private resolveSpawnStartX(uuid: number): number {
const heroType = HeroInfo[uuid]?.type;
return heroType === HType.Melee
? MissionHeroCompComp.HERO_SPAWN_START_MELEE_X
: MissionHeroCompComp.HERO_SPAWN_START_RANGED_X;
}
/**
* 生成合成后的高级英雄,并覆盖为聚合后的属性。
@@ -278,33 +243,21 @@ export class MissionHeroCompComp extends CCComp {
* @param pool_lv 卡池等级
* @param ap 聚合后攻击力
* @param hp_max 聚合后最大生命值
* @param targetLane 指定生成
* @param targetIndex 指定该路排位
* @param targetPos 指定生成位置
* @returns 实际生成的英雄等级
*/
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetLane?: number, targetIndex?: number): number {
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetPos?: Vec3): number {
console.log("addMergedHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
// 如果未指定路,则按普通添加英雄处理
let lane = targetLane;
let indexInLane = targetIndex;
if (lane === undefined || indexInLane === undefined) {
const res = this.pickLaneForHero(uuid);
lane = res.lane;
indexInLane = res.indexInLane;
}
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
const landingPos = targetPos || this.pickPositionForHero();
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv);
// 召唤完成后,派发事件以更新英雄面板
const model = hero.get(HeroAttrsComp);
if (model) {
model.lane = lane;
model.lane_index = indexInLane;
model.ap = Math.max(0, ap);
model.hp_max = Math.max(1, hp_max);
model.hp = model.hp_max;
@@ -540,14 +493,13 @@ export class MissionHeroCompComp extends CCComp {
}
// 计算目标出生点(提前排除素材英雄所占的位置)
const { lane, indexInLane } = this.pickLaneForHero(uuid, mergeEids);
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
const landingPos = this.pickPositionForHero(mergeEids);
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
// 汇聚 → 特效 → 生成
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
await this.playMergeBoomFx(spawnPos);
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, lane, indexInLane);
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, landingPos);
}
/**