3 Commits

Author SHA1 Message Date
panw
8f65282af7 fix(map): 修复英雄位置变更时的节点绑定逻辑
根据英雄的lane和lane_index重新计算期望的节点索引,当实际节点不匹配时将英雄卡片转移到正确的节点上,隐藏旧节点并激活新节点绑定最新数据
2026-05-13 17:21:07 +08:00
panw
254b7f3e9e fix(map): correct hero position calculation and loop order
修复了MissionHeroComp的遍历顺序,同时修正了MissionCardComp中的节点索引计算逻辑,匹配实际的视觉排布和MoveComp中的赋值规则,添加了调试日志方便排查位置问题。
2026-05-13 17:13:55 +08:00
panw
626d27e676 refactor(map): 优化英雄信息面板的缓存与复用逻辑
1. 新增缓存预先放置的HInfoComp组件,避免运行时实例化预制体
2. 移除动态创建面板逻辑,改为复用预先摆放的节点
3. 简化ensureHeroInfoPanel逻辑,通过node_index直接获取目标组件
4. 销毁时改为隐藏缓存节点而非直接销毁,保留复用基础
5. 移除冗余的relayoutHeroInfoPanels方法和相关逻辑
2026-05-13 17:13:41 +08:00
3 changed files with 1843 additions and 1798 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -150,6 +150,8 @@ export class MissionCardComp extends CCComp {
model: HeroAttrsComp, model: HeroAttrsComp,
comp: HInfoComp comp: HInfoComp
}> = new Map(); }> = new Map();
/** 缓存预先放置的 6 个 HInfoComp */
private cachedHInfoComps: Map<number, HInfoComp> = new Map();
// ======================== 生命周期 ======================== // ======================== 生命周期 ========================
/** /**
@@ -161,6 +163,7 @@ export class MissionCardComp extends CCComp {
* 5. 触发首次任务开始流程。 * 5. 触发首次任务开始流程。
*/ */
onLoad() { onLoad() {
this.cacheHInfoComps();
this.bindEvents(); this.bindEvents();
this.cacheCardComps(); this.cacheCardComps();
this.layoutCardSlots(); this.layoutCardSlots();
@@ -171,6 +174,19 @@ export class MissionCardComp extends CCComp {
poolLv: this.poolLv poolLv: this.poolLv
}); });
} }
private cacheHInfoComps() {
this.cachedHInfoComps.clear();
if (!this.hero_info_node) return;
for (let i = 0; i < this.hero_info_node.children.length; i++) {
const child = this.hero_info_node.children[i];
const comp = (child.getComponent(HInfoComp) || child.getComponent("HInfoComp")) as HInfoComp;
if (comp && comp.node_index > 0) {
this.cachedHInfoComps.set(comp.node_index, comp);
child.active = false;
}
}
}
/** 组件销毁时解绑所有事件并清理英雄信息面板 */ /** 组件销毁时解绑所有事件并清理英雄信息面板 */
onDestroy() { onDestroy() {
@@ -880,48 +896,61 @@ export class MissionCardComp extends CCComp {
} }
private ensureHeroInfoPanel(eid: number, model: HeroAttrsComp) { private ensureHeroInfoPanel(eid: number, model: HeroAttrsComp) {
if (!this.hero_info_node || !this.hero_info_prefab) { if (!this.hero_info_node) {
mLogger.error(this.debugMode, "MissionCardComp", "ensureHeroInfoPanel: missing hero_info_node or hero_info_prefab"); mLogger.error(this.debugMode, "MissionCardComp", "ensureHeroInfoPanel: missing hero_info_node");
return; return;
} }
this.hero_info_node.active = true;
// MoveComp.ts 里的 assignment:
// lanePriority = [1, 0, 2]; // slotIndex 0->中路(1), 1->上路(0), 2->下路(2)
// laneIdx = lanePriority[slotIndex % 3];
// col = Math.floor(slotIndex / 3);
// model.lane = laneIdx;
// model.lane_index = col;
//
// 节点顺序预期:
// node_index=1 对应 1排上路 (lane=0, lane_index=0)
// node_index=2 对应 1排中路 (lane=1, lane_index=0)
// node_index=3 对应 1排下路 (lane=2, lane_index=0)
// node_index=4 对应 2排上路 (lane=0, lane_index=1)
// node_index=5 对应 2排中路 (lane=1, lane_index=1)
// node_index=6 对应 2排下路 (lane=2, lane_index=1)
// 因为 1排中路 在视觉上是最前面的,实际按路排(上/中/下):
const laneOrder = model.lane === 0 ? 0 : (model.lane === 1 ? 1 : 2);
const expectedNodeIndex = model.lane_index * 3 + laneOrder + 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); const current = this.heroInfoItems.get(eid);
if (current) { if (current) {
current.model = model; current.model = model;
current.comp.bindData(eid, model); current.comp = comp;
current.node = comp.node;
comp.node.active = true;
comp.bindData(eid, model);
this.updateHeroInfoPanel(current); this.updateHeroInfoPanel(current);
return; return;
} }
mLogger.log(this.debugMode, "MissionCardComp", "ensureHeroInfoPanel: creating new panel for eid", eid); comp.node.active = true;
const node = instantiate(this.hero_info_prefab);
node.parent = this.hero_info_node;
node.active = true;
// 尝试两种方式获取组件,并输出日志
let comp = node.getComponent(HInfoComp) as any;
if (!comp) {
comp = node.getComponent("HInfoComp") as any;
}
if (!comp) {
mLogger.error(this.debugMode, "MissionCardComp", "ensureHeroInfoPanel: Failed to get HInfoComp from prefab!");
node.destroy();
return;
}
const item = { const item = {
node, node: comp.node,
model, model,
comp comp
}; };
comp.bindData(eid, model); comp.bindData(eid, model);
comp.setBattlePhase(this.isBattlePhase); comp.setBattlePhase(this.isBattlePhase);
this.heroInfoItems.set(eid, item); this.heroInfoItems.set(eid, item);
this.relayoutHeroInfoPanels();
this.updateHeroInfoPanel(item); this.updateHeroInfoPanel(item);
mLogger.log(this.debugMode, "MissionCardComp", `ensureHeroInfoPanel: new panel created for eid ${eid}, final position:`, item.node.position); mLogger.log(this.debugMode, "MissionCardComp", `ensureHeroInfoPanel: updated panel for eid ${eid} at node_index ${expectedNodeIndex}`);
} }
private refreshHeroInfoPanels() { private refreshHeroInfoPanels() {
@@ -932,16 +961,36 @@ export class MissionCardComp extends CCComp {
return; return;
} }
if (!item.comp.isModelAlive()) { if (!item.comp.isModelAlive()) {
if (item.node.isValid) item.node.destroy(); if (item.node.isValid) item.node.active = false;
removeKeys.push(eid); removeKeys.push(eid);
return; return;
} }
// 检查英雄是否改变了位置 (lane 或 lane_index 发生了变化)
const laneOrder = item.model.lane === 0 ? 0 : (item.model.lane === 1 ? 1 : 2);
const expectedNodeIndex = item.model.lane_index * 3 + laneOrder + 1;
if (item.comp.node_index !== expectedNodeIndex) {
// 如果位置变了,需要转移到新的节点上
const newComp = this.cachedHInfoComps.get(expectedNodeIndex);
if (newComp) {
// 隐藏旧节点
item.node.active = false;
// 转移到新节点
item.comp = newComp;
item.node = newComp.node;
item.node.active = true;
item.comp.bindData(eid, item.model);
item.comp.setBattlePhase(this.isBattlePhase);
}
}
this.updateHeroInfoPanel(item); this.updateHeroInfoPanel(item);
}); });
for (let i = 0; i < removeKeys.length; i++) { for (let i = 0; i < removeKeys.length; i++) {
this.heroInfoItems.delete(removeKeys[i]); this.heroInfoItems.delete(removeKeys[i]);
} }
this.relayoutHeroInfoPanels();
this.updateHeroNumUI(false, false); this.updateHeroNumUI(false, false);
} }
@@ -954,54 +1003,24 @@ export class MissionCardComp extends CCComp {
item.comp.setBattlePhase(this.isBattlePhase); item.comp.setBattlePhase(this.isBattlePhase);
} }
private relayoutHeroInfoPanels() {
const sortedItems = [...this.heroInfoItems.values()].sort((a, b) => {
const aEnt = (a.model as any)?.ent as ecs.Entity | undefined;
const bEnt = (b.model as any)?.ent as ecs.Entity | undefined;
const aView = aEnt?.get(HeroViewComp);
const bView = bEnt?.get(HeroViewComp);
const aMove = aEnt?.get(MoveComp);
const bMove = bEnt?.get(MoveComp);
// 排序逻辑反转:适应 cc.Layout 的节点渲染顺序(先渲染/index小的在左边
// 1. x 坐标越小越靠后排index 应该越小
const aFrontScore = aView?.node?.position?.x ?? -999999;
const bFrontScore = bView?.node?.position?.x ?? -999999;
if (aFrontScore !== bFrontScore) return aFrontScore - bFrontScore;
const aSpawnOrder = aMove?.spawnOrder ?? 0;
const bSpawnOrder = bMove?.spawnOrder ?? 0;
if (aSpawnOrder !== bSpawnOrder) return aSpawnOrder - bSpawnOrder;
const aEid = aEnt?.eid ?? 0;
const bEid = bEnt?.eid ?? 0;
return aEid - bEid;
});
for (let index = 0; index < sortedItems.length; index++) {
const item = sortedItems[index];
if (!item.node || !item.node.isValid) continue;
// 既然使用了 cc.Layout 进行自动排版,我们只需设置渲染顺序
// Layout 会自动根据 siblingIndex 对所有子节点重新排位
item.node.setSiblingIndex(index);
}
}
private clearHeroInfoPanels() { private clearHeroInfoPanels() {
if (this.heroInfoItems) { if (this.heroInfoItems) {
this.heroInfoItems.forEach(item => {
if (item && item.node && item.node.isValid) {
item.node.destroy();
}
});
this.heroInfoItems.clear(); this.heroInfoItems.clear();
} }
if (this.hero_info_node && this.hero_info_node.isValid) { if (this.cachedHInfoComps) {
for (let i = this.hero_info_node.children.length - 1; i >= 0; i--) { this.cachedHInfoComps.forEach(comp => {
const child = this.hero_info_node.children[i]; if (comp && comp.node && comp.node.isValid) {
if (child && child.isValid) child.destroy(); comp.node.active = false;
} }
});
} }
// 不再销毁子节点,因为它们是预先放置的
// 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.heroInfoSyncTimer = 0;
this.syncMissionHeroData(0); this.syncMissionHeroData(0);
this.updateHeroNumUI(false, false); this.updateHeroNumUI(false, false);

View File

@@ -197,8 +197,8 @@ export class MissionHeroCompComp extends CCComp {
// 优先中路(1) -> 上路(0) -> 下路(2) // 优先中路(1) -> 上路(0) -> 下路(2)
const priority = [1, 0, 2]; const priority = [1, 0, 2];
for (const lane of priority) { for (let indexInLane = 0; indexInLane < MissionHeroCompComp.HERO_LANE_CAP; indexInLane++) {
for (let indexInLane = 0; indexInLane < MissionHeroCompComp.HERO_LANE_CAP; indexInLane++) { for (const lane of priority) {
if (!occupied[lane][indexInLane]) { if (!occupied[lane][indexInLane]) {
return { lane, indexInLane }; return { lane, indexInLane };
} }