From c7afe5d73f1623a93594d6311eee9d69560714db Mon Sep 17 00:00:00 2001 From: panFD Date: Sun, 26 Jul 2026 20:08:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(map,=20mission):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=A7=BD=E4=BD=8D=E5=B8=83=E5=B1=80=E4=B8=8E=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E5=BC=82=E5=B8=B8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 为英雄组件clearBySystem增加位置重置判断,避免未初始化时将槽位重置到原点 2. 重构任务面板槽位布局逻辑,统一使用三等分水平布局 3. 新增布局辅助方法,处理预制体Widget组件冲突问题 4. 禁用多个冗余的任务预制体节点,清理无效引用 --- assets/resources/gui/element/mission.prefab | 10 +-- assets/script/game/map/CHeroComp.ts | 7 +- assets/script/game/map/MissionCardComp.ts | 77 +++++++++++++++++++-- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/assets/resources/gui/element/mission.prefab b/assets/resources/gui/element/mission.prefab index c888d973..8da9b0b7 100644 --- a/assets/resources/gui/element/mission.prefab +++ b/assets/resources/gui/element/mission.prefab @@ -6029,7 +6029,7 @@ "node": { "__id__": 348 }, - "_enabled": true, + "_enabled": false, "__prefab": { "__id__": 354 }, @@ -7599,7 +7599,7 @@ "node": { "__id__": 456 }, - "_enabled": true, + "_enabled": false, "__prefab": { "__id__": 462 }, @@ -8571,7 +8571,7 @@ "node": { "__id__": 522 }, - "_enabled": true, + "_enabled": false, "__prefab": { "__id__": 528 }, @@ -9562,7 +9562,7 @@ "node": { "__id__": 590 }, - "_enabled": true, + "_enabled": false, "__prefab": { "__id__": 596 }, @@ -14349,7 +14349,7 @@ "__id__": 530 }, "equip_refresh": { - "__id__": 530 + "__id__": 464 }, "shop_refresh": { "__id__": 598 diff --git a/assets/script/game/map/CHeroComp.ts b/assets/script/game/map/CHeroComp.ts index 39cb2594..9bf48344 100644 --- a/assets/script/game/map/CHeroComp.ts +++ b/assets/script/game/map/CHeroComp.ts @@ -578,11 +578,16 @@ export class CHeroComp extends CCComp { /** * 系统清槽:任务开始/结束时强制重置。 * 清空数据与 UI 并隐藏节点。 + * + * 仅在 setSlotPosition 初始化过静止位置后才恢复位置, + * 否则 restPosition 为默认 (0,0,0),会把所有槽位重置到中心。 */ clearBySystem() { this.cardData = null; this.isPurchased = false; - this.node.setPosition(this.restPosition); + if (this.hasFixedBasePosition) { + this.node.setPosition(this.restPosition); + } this.applyEmptyUI(); this.node.active = false; } diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 06ce6c15..1b71391d 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -35,7 +35,7 @@ * - smc.vmdata.mission_data —— 局内数据(coin / hero_num / hero_max_num) */ import { mLogger } from "../common/Logger"; -import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, Tween, tween, Vec3, UITransform } from "cc"; +import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, Tween, tween, Vec3, UITransform, Widget } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { GameEvent } from "../common/config/GameEvent"; @@ -155,10 +155,13 @@ export class MissionCardComp extends CCComp { /** 英雄数量显示节点(含 icon + num 子节点) */ @property(Node) hero_num_node: Node = null! - /**技能刷新按钮节点 */ + /** 技能刷新按钮节点 */ @property(Node) skill_refresh: Node = null! + /** 槽位水平坐标(屏幕 720 三等分,卡片 230,列宽 240) */ + private readonly slotPosX: number[] = [-240, 0, 240]; + /** 装备刷新按钮节点 */ @property(Node) equip_refresh: Node = null!; @@ -221,7 +224,6 @@ export class MissionCardComp extends CCComp { onLoad() { this.bindEvents(); this.cacheCardComps(); - this.layoutCardSlots(); this.initCardsPanelPos(); this.initPanelLayout(); mLogger.log(this.debugMode, "MissionCardComp", "onLoad init", { @@ -542,6 +544,9 @@ export class MissionCardComp extends CCComp { // 根据物品数量设置列表面板高度 this.resizeListBox(this.shopBoxNode, targetCount); + // 三等分水平布局 + this.layoutSlotsHorizontal(this.itemCardComps); + // 默认显示商店面板 if (this.shopPanNode) { this.shopPanNode.active = true; @@ -556,6 +561,7 @@ export class MissionCardComp extends CCComp { /** * 确保药品槽位数量足够。 * 优先复用现有槽位,不足时从 itemPrefab 实例化补充。 + * 实例化后立即按 slotPosX 定位,避免堆叠在 (0,0)。 */ private ensureItemSlots(targetCount: number) { if (!this.shopBoxNode || !this.itemPrefab) return; @@ -570,6 +576,7 @@ export class MissionCardComp extends CCComp { const comp = node.getComponent(ItemListComp) || node.addComponent(ItemListComp); this.itemCardComps.push(comp); } + this.layoutSlotsHorizontal(this.itemCardComps); } /** 清空商品列表(复用槽位,仅清空数据) */ @@ -1015,6 +1022,9 @@ export class MissionCardComp extends CCComp { // 根据物品数量设置列表面板高度 this.resizeListBox(this.equipsBoxNode, targetCount); + // 三等分水平布局 + this.layoutSlotsHorizontal(this.equipCardComps); + // 默认显示装备面板 if (this.equipsPanNode) { this.equipsPanNode.active = true; @@ -1029,6 +1039,7 @@ export class MissionCardComp extends CCComp { /** * 确保装备槽位数量足够。 * 优先复用现有槽位,不足时从 equipPrefab 实例化补充。 + * 实例化后立即按 slotPosX 定位,避免堆叠在 (0,0)。 */ private ensureEquipSlots(targetCount: number) { if (!this.equipsBoxNode || !this.equipPrefab) return; @@ -1043,6 +1054,7 @@ export class MissionCardComp extends CCComp { const comp = node.getComponent(EquipListComp) || node.addComponent(EquipListComp); this.equipCardComps.push(comp); } + this.layoutSlotsHorizontal(this.equipCardComps); } /** 清空装备列表(复用槽位,仅清空数据) */ @@ -1088,6 +1100,9 @@ export class MissionCardComp extends CCComp { // 根据技能数量设置列表面板高度 this.resizeListBox(this.skillBoxNode, targetCount); + // 三等分水平布局 + this.layoutSlotsHorizontal(this.skillCardComps); + // 默认显示技能面板 if (this.skillPanNode) { this.skillPanNode.active = true; @@ -1102,6 +1117,7 @@ export class MissionCardComp extends CCComp { /** * 确保技能槽位数量足够。 * 优先复用现有槽位,不足时从 sCardPrefab 实例化补充。 + * 实例化后立即按 slotPosX 定位,避免堆叠在 (0,0)。 */ private ensureSkillSlots(targetCount: number) { if (!this.skillBoxNode || !this.sCardPrefab) return; @@ -1112,10 +1128,15 @@ export class MissionCardComp extends CCComp { // 补充实例化不足的槽位 while (this.skillCardComps.length < targetCount) { const node = instantiate(this.sCardPrefab); + // 先禁用根节点 Widget 再挂到父节点: + // 否则 addChild 激活当帧 lateUpdate 时 Widget 会对齐覆盖 position + const widget = node.getComponent(Widget); + if (widget) widget.enabled = false; this.skillBoxNode.addChild(node); const comp = node.getComponent(SCardComp) || node.addComponent(SCardComp); this.skillCardComps.push(comp); } + this.layoutSlotsHorizontal(this.skillCardComps); } /** 清空技能卡槽(复用槽位,仅清空数据) */ @@ -1232,6 +1253,10 @@ export class MissionCardComp extends CCComp { if (this.cardPrefab && this.cards_node && this.cards_node.isValid) { for (let i = 0; i < 3; i++) { const node = instantiate(this.cardPrefab); + // 先禁用根节点 Widget 再挂到父节点: + // 否则 addChild 激活当帧 lateUpdate 时 Widget 会对齐覆盖 position + const widget = node.getComponent(Widget); + if (widget) widget.enabled = false; this.cards_node.addChild(node); const comp = node.getComponent(CHeroComp) || node.addComponent(CHeroComp); this.cardComps.push(comp); @@ -1246,6 +1271,8 @@ export class MissionCardComp extends CCComp { cardPrefabValid: this.cardPrefab.isValid }); } + // 实例化后立即按 slotPosX 定位,避免堆叠在 (0,0) + this.layoutCardSlots(); } else { mLogger.log(this.debugMode, "MissionCardComp", "cacheCardComps failed", { cardPrefabValid: this.cardPrefab?.isValid, @@ -1503,7 +1530,49 @@ export class MissionCardComp extends CCComp { } private layoutCardSlots() { - // 布局由 cHero.prefab 根节点 Widget 约束控制,代码不再覆盖位置 + // 走 CHeroComp.setSlotPosition,同步更新其 restPosition, + // 避免 clearBySystem 恢复位置时与布局结果不一致 + for (let i = 0; i < this.cardComps.length && i < this.slotPosX.length; i++) { + const comp = this.cardComps[i]; + if (comp && comp.node && comp.node.isValid) { + const widget = comp.node.getComponent(Widget); + if (widget) widget.enabled = false; + comp.setSlotPosition(this.slotPosX[i]); + } + } + } + + /** + * 三等分水平布局: + * 按固定坐标数组 slotPosX 排列槽位,超出数量的槽位不处理。 + * + * Why: 各卡池根节点的 Layout 组件已移除,槽位水平位置改由代码统一控制。 + * 部分预制体根节点残留 Widget 组件(对齐父节点中心), + * 启用时会在 lateUpdate 覆盖代码设置的 position,需先禁用。 + * + * @param comps 槽位组件数组(CHeroComp / EquipListComp / SCardComp / ItemListComp) + */ + private layoutSlotsHorizontal(comps: Array) { + for (let i = 0; i < comps.length && i < this.slotPosX.length; i++) { + const node = comps[i]?.node; + if (!node || !node.isValid) continue; + // 禁用根节点 Widget,防止其在对齐刷新时覆盖 position + const widget = node.getComponent(Widget); + if (widget) widget.enabled = false; + const pos = node.getPosition(); + node.setPosition(this.slotPosX[i], pos.y, pos.z); + } + } + + /** + * 延迟一帧执行布局: + * 首次实例化的嵌套预制体在首帧会先恢复到原始位置, + * 等下一帧预制体初始化完成后再定位,避免首帧堆叠在 (0,0)。 + */ + private layoutSlotsHorizontalDeferred(comps: Array) { + this.scheduleOnce(() => { + this.layoutSlotsHorizontal(comps); + }, 0); } private playButtonPressAnim(node: Node | null) {