diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index fc882f67..c02b2acd 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -64,8 +64,6 @@ export class HeroAttrsComp extends ecs.Comp { minSkillDistance: number = 0; // 最近技能攻击距离(缓存,不受MP影响,用于停止位置判断) // ==================== 阵型位置 ==================== - lane: number = -1; // 所在分路:0上路, 1中路, 2下路 - lane_index: number = -1; // 所在路中的排位:0前排, 1后排 // ==================== 标记状态 ==================== is_dead: boolean = false; @@ -312,8 +310,6 @@ export class HeroAttrsComp extends ecs.Comp { this.maxSkillDistance = 0; this.minSkillDistance = 0; - this.lane = -1; - this.lane_index = -1; this.is_dead = false; this.is_count_dead = false; diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index 95dcb6f7..dbd75f6a 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -46,6 +46,8 @@ interface MoveFacConfig { retreatBackX: number; } +import { MissionHeroCompComp } from "../map/MissionHeroComp"; + @ecs.register('MoveSystem') export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { private readonly heroFrontAnchorX = -200; @@ -249,20 +251,12 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const slotIndex = Math.max(0, allAllies.findIndex(entity => entity === self)); - const lanePriority = [1, 0, 2]; // 中路优先,其次上路,最后下路 - const laneOffsets = [100, 0, -100]; + // 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5) + const slotPriority = [1, 0, 2, 4, 3, 5]; + const posIndex = slotPriority[slotIndex % 6]; + const pos = MissionHeroCompComp.HERO_POSITIONS[posIndex]; - const col = Math.floor(slotIndex / 3); - const laneIdx = lanePriority[slotIndex % 3]; - - // 动态更新英雄位置数据,为战斗面板排序提供依据 - model.lane = laneIdx; - model.lane_index = col; - - const targetY = BoxSet.GAME_LINE + laneOffsets[laneIdx]; - const targetX = this.heroFrontAnchorX - col * this.heroAllySpacingX; - - return { targetX, targetY }; + return { targetX: pos.x, targetY: pos.y }; } private moveToSlot(view: HeroViewComp, move: MoveComp, model: HeroAttrsComp, targetX: number) { diff --git a/assets/script/game/map/HInfoComp.ts b/assets/script/game/map/HInfoComp.ts index a5ec3cfe..dd513708 100644 --- a/assets/script/game/map/HInfoComp.ts +++ b/assets/script/game/map/HInfoComp.ts @@ -32,6 +32,9 @@ import { GameEvent } from "../common/config/GameEvent"; import { oops } from "db://oops-framework/core/Oops"; import { UIID } from "../common/config/GameUIConfig"; import { mLogger } from "../common/Logger"; +import { MissionHeroCompComp } from "./MissionHeroComp"; +import { MoveComp } from "../hero/MoveComp"; +import { FacSet } from "../common/config/GameSet"; const {property, ccclass } = _decorator; @@ -98,6 +101,44 @@ export class HInfoComp extends CCComp { this.refresh(); } + /** + * 根据 node_index 获取对应的硬编码位置,并查找该位置的英雄 + */ + refreshByNodeIndex() { + if (this.node_index < 1 || this.node_index > 6) return; + const targetPos = MissionHeroCompComp.HERO_POSITIONS[this.node_index - 1]; + + let foundModel: HeroAttrsComp | null = null; + let foundEid: number = 0; + + // 遍历所有英雄,查找 targetX 和 targetY 匹配该位置的英雄 + ecs.query(ecs.allOf(HeroAttrsComp, MoveComp)).forEach((entity: ecs.Entity) => { + const model = entity.get(HeroAttrsComp); + const move = entity.get(MoveComp); + if (model && move && !model.is_dead && model.fac === FacSet.HERO) { + if (Math.abs(move.targetX - targetPos.x) < 2 && Math.abs(move.baseY - targetPos.y) < 2) { + foundModel = model; + foundEid = entity.eid; + } + } + }); + + if (foundModel) { + if (this.eid !== foundEid) { + this.bindData(foundEid, foundModel); + if (!this.node.active) this.node.active = true; + } else { + this.refresh(); + } + } else { + if (this.eid !== 0) { + this.eid = 0; + this.model = null; + if (this.node.active) this.node.active = false; + } + } + } + /** * 设置当前是否处于战斗阶段,控制出售按钮显示/隐藏 * @param isBattlePhase 是否处于战斗阶段 diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 0fcf58a7..9ca6daf4 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -141,15 +141,6 @@ export class MissionCardComp extends CCComp { private cardsHideScale: Vec3 = new Vec3(0, 0, 1); /** 卡牌原始定位点 */ private cardsPos = [-260,-75,108,260] - /** - * 英雄信息面板映射:EID → { node, model, comp } - * 用于追踪每个出战英雄的面板实例和数据引用 - */ - private heroInfoItems: Map = new Map(); /** 缓存预先放置的 6 个 HInfoComp */ private cachedHInfoComps: Map = new Map(); // ======================== 生命周期 ======================== @@ -226,11 +217,6 @@ export class MissionCardComp extends CCComp { missionData.hero_extend_max_num = FightSet.HERO_MAX_NUM + 1; } - // 确保 Map 被正确初始化 - if (!this.heroInfoItems) { - this.heroInfoItems = new Map(); - } - // 确保卡牌组件列表已被正确缓存 if (!this.cardComps || this.cardComps.length === 0) { this.cacheCardComps(); @@ -276,7 +262,13 @@ export class MissionCardComp extends CCComp { this.heroInfoSyncTimer += dt; if (this.heroInfoSyncTimer < 0.15) return; this.heroInfoSyncTimer = 0; - this.refreshHeroInfoPanels(); + + // 遍历所有预设的 HInfoComp,让其根据 node_index 自己刷新 + this.cachedHInfoComps.forEach(comp => { + if (comp && comp.isValid) { + comp.refreshByNodeIndex(); + } + }); } @@ -422,14 +414,12 @@ export class MissionCardComp extends CCComp { if (!eid || !model) return; const before = this.getAliveHeroCount(); - this.ensureHeroInfoPanel(eid, model); const after = this.getAliveHeroCount(); this.updateHeroNumUI(true, after > before); } /** 英雄死亡事件回调:刷新面板列表并更新英雄数量 UI */ private onHeroDead() { - this.refreshHeroInfoPanels(); this.updateHeroNumUI(true, false); } @@ -663,9 +653,9 @@ export class MissionCardComp extends CCComp { Tween.stopAllByTarget(this.cards_node); this.cards_node.setScale(this.cardsShowScale); - this.heroInfoItems.forEach(item => { - if (item.comp && item.comp.isValid) { - item.comp.setBattlePhase(false); + this.cachedHInfoComps.forEach(comp => { + if (comp && comp.isValid) { + comp.setBattlePhase(false); } }); } @@ -683,9 +673,9 @@ export class MissionCardComp extends CCComp { }) .start(); - this.heroInfoItems.forEach(item => { - if (item.comp && item.comp.isValid) { - item.comp.setBattlePhase(true); + this.cachedHInfoComps.forEach(comp => { + if (comp && comp.isValid) { + comp.setBattlePhase(true); } }); } @@ -895,132 +885,7 @@ export class MissionCardComp extends CCComp { return Math.floor(cost); } - private ensureHeroInfoPanel(eid: number, model: HeroAttrsComp) { - if (!this.hero_info_node) { - mLogger.error(this.debugMode, "MissionCardComp", "ensureHeroInfoPanel: missing hero_info_node"); - return; - } - - // MoveComp.ts 里的 assignment: - // lanePriority = [1, 0, 2]; // slotIndex 0->中路(1), 1->上路(0), 2->下路(2) - // laneIdx = lanePriority[slotIndex % 3]; // priority: 1(中), 0(上), 2(下) - // model.lane = laneIdx; - // - // 所以当: - // model.lane = 0 (上路), model.lane_index = 0 -> 对应 node_index = 1 - // model.lane = 1 (中路), model.lane_index = 0 -> 对应 node_index = 2 - // model.lane = 2 (下路), model.lane_index = 0 -> 对应 node_index = 3 - const expectedNodeIndex = model.lane_index * 3 + model.lane + 1; - - mLogger.log(this.debugMode, "MissionCardComp", `ensureHeroInfoPanel calculation: lane=${model.lane}, lane_index=${model.lane_index} -> expectedNodeIndex=${expectedNodeIndex}`); - - const comp = this.cachedHInfoComps.get(expectedNodeIndex); - - if (!comp) { - mLogger.error(this.debugMode, "MissionCardComp", `ensureHeroInfoPanel: missing pre-placed HInfoComp for index ${expectedNodeIndex}`); - return; - } - - const current = this.heroInfoItems.get(eid); - if (current) { - current.model = model; - current.comp = comp; - current.node = comp.node; - comp.node.active = true; - comp.bindData(eid, model); - this.updateHeroInfoPanel(current); - return; - } - - comp.node.active = true; - const item = { - node: comp.node, - model, - comp - }; - comp.bindData(eid, model); - comp.setBattlePhase(this.isBattlePhase); - this.heroInfoItems.set(eid, item); - this.updateHeroInfoPanel(item); - - mLogger.log(this.debugMode, "MissionCardComp", `ensureHeroInfoPanel: updated panel for eid ${eid} at node_index ${expectedNodeIndex}`); - } - - private refreshHeroInfoPanels() { - const removeKeys: number[] = []; - - // 1. 先将已死亡的英雄移除,释放占用的节点 - this.heroInfoItems.forEach((item, eid) => { - if (!item.node || !item.node.isValid) { - removeKeys.push(eid); - return; - } - // 使用 model.is_dead 增加判断条件,更加准确 - if (!item.comp.isModelAlive() || item.model.is_dead) { - if (item.node.isValid) item.node.active = false; - removeKeys.push(eid); - return; - } - }); - for (let i = 0; i < removeKeys.length; i++) { - this.heroInfoItems.delete(removeKeys[i]); - } - - // 2. 然后再处理所有存活英雄的位置转移和信息刷新 - // 如果有多个英雄在同一帧发生位置变动,我们需要统一处理 - const needTransfer: Array<{eid: number, expectedNodeIndex: number}> = []; - - this.heroInfoItems.forEach((item, eid) => { - // 检查英雄是否改变了位置 (lane 或 lane_index 发生了变化) - const expectedNodeIndex = item.model.lane_index * 3 + item.model.lane + 1; - - if (item.comp.node_index !== expectedNodeIndex) { - // 如果位置变了,需要转移到新的节点上 - const newComp = this.cachedHInfoComps.get(expectedNodeIndex); - if (newComp) { - needTransfer.push({eid, expectedNodeIndex}); - // 将原来的节点释放,以供其他可能换到这个位置的英雄使用 - item.node.active = false; - } - } else { - this.updateHeroInfoPanel(item); - } - }); - - // 执行位置转移 - for (const transfer of needTransfer) { - const item = this.heroInfoItems.get(transfer.eid); - if (!item) continue; - - const newComp = this.cachedHInfoComps.get(transfer.expectedNodeIndex); - if (newComp) { - // 转移到新节点 - item.comp = newComp; - item.node = newComp.node; - item.node.active = true; - item.comp.bindData(transfer.eid, item.model); - item.comp.setBattlePhase(this.isBattlePhase); - - this.updateHeroInfoPanel(item); - } - } - - this.updateHeroNumUI(false, false); - } - - private updateHeroInfoPanel(item: { - node: Node, - model: HeroAttrsComp, - comp: HInfoComp - }) { - item.comp.refresh(); - item.comp.setBattlePhase(this.isBattlePhase); - } - private clearHeroInfoPanels() { - if (this.heroInfoItems) { - this.heroInfoItems.clear(); - } if (this.cachedHInfoComps) { this.cachedHInfoComps.forEach(comp => { if (comp && comp.node && comp.node.isValid) { @@ -1028,13 +893,6 @@ export class MissionCardComp extends CCComp { } }); } - // 不再销毁子节点,因为它们是预先放置的 - // if (this.hero_info_node && this.hero_info_node.isValid) { - // for (let i = this.hero_info_node.children.length - 1; i >= 0; i--) { - // const child = this.hero_info_node.children[i]; - // if (child && child.isValid) child.destroy(); - // } - // } this.heroInfoSyncTimer = 0; this.syncMissionHeroData(0); this.updateHeroNumUI(false, false); @@ -1066,11 +924,11 @@ export class MissionCardComp extends CCComp { private getAliveHeroCount(): number { let count = 0; - this.heroInfoItems.forEach(item => { - if (!item?.node || !item.node.isValid) return; - if (!item.comp?.isModelAlive()) return; - if (item.model?.is_dead) return; - count += 1; + ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => { + const model = entity.get(HeroAttrsComp); + if (model && model.fac === FacSet.HERO && !model.is_dead) { + count++; + } }); return count; } @@ -1218,7 +1076,6 @@ export class MissionCardComp extends CCComp { // this.resetButtonScale(this.cards_up); // 关键:在 reset/销毁 时将 Map 置空,彻底切断引用 - this.heroInfoItems = null as any; this.cardComps = [] as any; if (this.node && this.node.isValid) { diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index 229ecd5c..de853522 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -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(); 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); 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); 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); } /**