refactor(map): 统一卡牌购买后的全池刷新逻辑
1. 为装备、物品、技能卡牌组件统一添加CardUsed事件派发 2. 重构MissionCardComp的卡槽管理,改为槽位复用模式 3. 拆分并统一装备、物品、技能卡池的刷新逻辑 4. 移除冗余的UseSkillCard事件监听与处理 5. 整理并缓存各类卡槽组件引用
This commit is contained in:
@@ -40,12 +40,12 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType } from "../common/config/CardSet";
|
||||
import { drawSkillCards } from "../common/config/SCardSet";
|
||||
import { drawEquipCards } from "../common/config/EquipSet";
|
||||
import { drawItemCards } from "../common/config/ICardSet";
|
||||
import { drawSkillCards } from "../common/config/SCardSet";
|
||||
import { CHeroComp } from "./CHeroComp";
|
||||
import { SCardComp } from "./SCardComp";
|
||||
import { EquipListComp } from "./EquipListComp";
|
||||
import { SCardComp } from "./SCardComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
@@ -131,7 +131,7 @@ export class MissionCardComp extends CCComp {
|
||||
@property(Node)
|
||||
skillBoxNode: Node = null!
|
||||
@property(Prefab)
|
||||
skillPrefab: Prefab = null!
|
||||
sCardPrefab: Prefab = null!
|
||||
|
||||
/** 药品面板显示按钮 */
|
||||
@property(Node)
|
||||
@@ -155,27 +155,9 @@ export class MissionCardComp extends CCComp {
|
||||
/** 英雄数量显示节点(含 icon + num 子节点) */
|
||||
@property(Node)
|
||||
hero_num_node: Node = null!
|
||||
/** 技能卡牌三选一弹窗节点 */
|
||||
@property(Node)
|
||||
skill_card_node: Node = null!
|
||||
/**技能卡槽1节点 */
|
||||
@property(Node)
|
||||
skill_card1: Node = null!
|
||||
/**技能卡槽2节点 */
|
||||
@property(Node)
|
||||
skill_card2: Node = null!
|
||||
/**技能卡槽3节点 */
|
||||
@property(Node)
|
||||
skill_card3: Node = null!
|
||||
/**技能刷新按钮节点 */
|
||||
@property(Node)
|
||||
skill_refresh: Node = null!
|
||||
/**技能广告刷新按钮节点 */
|
||||
@property(Node)
|
||||
skill_ad_refresh: Node = null!
|
||||
/**可用刷新数显示节点 */
|
||||
@property(Node)
|
||||
skill_refresh_num_node: Node = null!
|
||||
|
||||
/** 装备刷新按钮节点 */
|
||||
@property(Node)
|
||||
@@ -188,8 +170,12 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/** 三个槽位对应的 CHeroComp 控制器缓存(有序数组) */
|
||||
private cardComps: CHeroComp[] = [];
|
||||
/** 装备卡槽控制器缓存 */
|
||||
private equipCardComps: EquipListComp[] = [];
|
||||
/** 技能卡槽控制器缓存 */
|
||||
private skillCardComps: SCardComp[] = [];
|
||||
/** 药品卡槽控制器缓存 */
|
||||
private itemCardComps: ItemListComp[] = [];
|
||||
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
|
||||
private hasCalledHero: boolean = false;
|
||||
/** 已购买装备的 UUID 集合(跨波次保持,防止重复购买) */
|
||||
@@ -319,6 +305,9 @@ export class MissionCardComp extends CCComp {
|
||||
this.purchasedEquipUuids.clear();
|
||||
this.populateEquipments();
|
||||
|
||||
// 填充技能卡槽(英雄三槽模式:sCardPrefab 实例化 3 个槽位)
|
||||
this.populateSkillCards();
|
||||
|
||||
// 重置商品购买记录并填充商品商店
|
||||
this.purchasedItemUuids.clear();
|
||||
this.populateShopItems();
|
||||
@@ -327,16 +316,14 @@ export class MissionCardComp extends CCComp {
|
||||
this.hasCalledHero = false;
|
||||
this.updatePanelButtonsInteractable();
|
||||
|
||||
// 首次进入准备阶段自动抽取一次技能卡,后续刷新只能通过技能刷新按钮触发
|
||||
this.initSkillCardsOnce();
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
|
||||
}
|
||||
|
||||
/** 任务结束:清空 3 槽 + 装备商店 + 英雄面板并隐藏整个节点 */
|
||||
/** 任务结束:清空 3 槽 + 装备商店 + 技能卡槽 + 英雄面板并隐藏整个节点 */
|
||||
onMissionEnd() {
|
||||
this.clearAllCards();
|
||||
this.clearEquipments();
|
||||
this.clearSkillCards();
|
||||
this.clearShopItems();
|
||||
if (this.node && this.node.isValid) {
|
||||
this.node.active = false;
|
||||
@@ -380,7 +367,6 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.on(GameEvent.HeroDead, this.onHeroDead, this);
|
||||
oops.message.on(GameEvent.HeroSell, this.onHeroSell, this);
|
||||
oops.message.on(GameEvent.UseHeroCard, this.onUseHeroCard, this);
|
||||
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
// 监听装备购买事件,追踪已购记录
|
||||
oops.message.on(GameEvent.UseEquipCard, this.onUseEquipCard, this);
|
||||
// 监听商品购买事件,追踪已购记录
|
||||
@@ -411,19 +397,16 @@ export class MissionCardComp extends CCComp {
|
||||
/** 药品面板显示按钮 */
|
||||
this.showShop?.on(NodeEventType.TOUCH_END, this.onShowShopClick, this);
|
||||
|
||||
/** 技能卡刷新按钮 */
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onSkillDrawTouchCancel, this);
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_START, this.onSkillAdDrawTouchStart, this);
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_END, this.onSkillAdDrawTouchEnd, this);
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onSkillAdDrawTouchCancel, this);
|
||||
|
||||
/** 装备刷新按钮 */
|
||||
this.equip_refresh?.on(NodeEventType.TOUCH_START, this.onEquipDrawTouchStart, this);
|
||||
this.equip_refresh?.on(NodeEventType.TOUCH_END, this.onEquipDrawTouchEnd, this);
|
||||
this.equip_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onEquipDrawTouchCancel, this);
|
||||
|
||||
/** 技能刷新按钮 */
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
this.skill_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onSkillDrawTouchCancel, this);
|
||||
|
||||
/** 药品刷新按钮 */
|
||||
this.shop_refresh?.on(NodeEventType.TOUCH_START, this.onShopDrawTouchStart, this);
|
||||
this.shop_refresh?.on(NodeEventType.TOUCH_END, this.onShopDrawTouchEnd, this);
|
||||
@@ -502,54 +485,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.updateHeroNumUI(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 技能卡入场动画:每个卡槽从场景基准位置下方 80px 处升起,
|
||||
* 终止位置 = 场景编辑器位置(保持初始不做位移的约束),
|
||||
* 多张依次错开 staggerDelay 形成"依次飞入"效果。
|
||||
*/
|
||||
private readonly skillEnterOffsetY: number = -80;
|
||||
private readonly skillEnterDuration: number = 0.25;
|
||||
private readonly skillEnterStagger: number = 0.08;
|
||||
|
||||
private playSkillCardEnterAnim() {
|
||||
if (!this.skillCardComps || this.skillCardComps.length === 0) return;
|
||||
for (let i = 0; i < this.skillCardComps.length; i++) {
|
||||
const comp = this.skillCardComps[i];
|
||||
if (!comp) continue;
|
||||
const node = comp.node;
|
||||
if (!node || !node.isValid) continue;
|
||||
// 缓存场景编辑器位置作为动画终止点(初始不做位移)
|
||||
const basePos = node.getPosition();
|
||||
// 起始位置 = 基准位置下移 80px
|
||||
node.setPosition(basePos.x, basePos.y + this.skillEnterOffsetY, basePos.z);
|
||||
Tween.stopAllByTarget(node);
|
||||
tween(node)
|
||||
.delay(i * this.skillEnterStagger)
|
||||
.to(this.skillEnterDuration, { position: basePos }, { easing: 'quadOut' })
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 首次进入准备阶段时自动抽取一次技能卡:
|
||||
* - 仅在任务开始时调用一次。
|
||||
* - 后续抽卡只能通过技能刷新按钮触发。
|
||||
* - 面板轮播模式下技能面板始终 active,仅通过滑动切换可见性。
|
||||
*/
|
||||
private initSkillCardsOnce() {
|
||||
if (this.skillCardComps.length === 0) {
|
||||
this.cacheCardComps();
|
||||
}
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
this.playSkillCardEnterAnim();
|
||||
|
||||
// 首次弹出技能三选一的时候弹出guide2
|
||||
if (!smc.finish_guides.includes(2)) {
|
||||
oops.gui.open(UIID.Guide2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 技能面板按钮回调:滑动到技能面板(index=2)。
|
||||
*/
|
||||
@@ -569,9 +504,9 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充商品列表:
|
||||
* 填充商品列表(槽位复用模式):
|
||||
* 从 ICardSet 按权重抽取 3 张商品卡,
|
||||
* 实例化 itemPrefab 到 shopBoxNode,
|
||||
* 优先复用现有槽位(shopBoxNode 子节点),不足再实例化新槽位,
|
||||
* 每个商品项由 ItemListComp 渲染并处理购买。
|
||||
*
|
||||
* 商品为一次性 buff 技能(Instant 触发,t_times=1),
|
||||
@@ -580,25 +515,32 @@ export class MissionCardComp extends CCComp {
|
||||
private populateShopItems() {
|
||||
if (!this.shopBoxNode || !this.itemPrefab) return;
|
||||
|
||||
// 清空旧列表
|
||||
this.shopBoxNode.removeAllChildren();
|
||||
|
||||
// 从商品卡池按权重抽取 3 张
|
||||
const items = drawItemCards(3);
|
||||
for (const item of items) {
|
||||
const node = instantiate(this.itemPrefab);
|
||||
this.shopBoxNode.addChild(node);
|
||||
const comp = node.getComponent(ItemListComp) || node.addComponent(ItemListComp);
|
||||
comp.applyCardData(item);
|
||||
const targetCount = items.length;
|
||||
|
||||
// 已购买的商品标记为已购
|
||||
if (this.purchasedItemUuids.has(item.uuid)) {
|
||||
comp.setPurchased();
|
||||
// 确保槽位数量足够(复用 + 补充实例化)
|
||||
this.ensureItemSlots(targetCount);
|
||||
|
||||
// 分发数据到槽位
|
||||
for (let i = 0; i < this.itemCardComps.length; i++) {
|
||||
const comp = this.itemCardComps[i];
|
||||
if (!comp) continue;
|
||||
|
||||
if (i < targetCount) {
|
||||
comp.applyCardData(items[i]);
|
||||
// 已购买的商品标记为已购
|
||||
if (this.purchasedItemUuids.has(items[i].uuid)) {
|
||||
comp.setPurchased();
|
||||
}
|
||||
} else {
|
||||
// 多余槽位清空
|
||||
comp.applyCardData(null as unknown as CardConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据物品数量设置列表面板高度
|
||||
this.resizeListBox(this.shopBoxNode, items.length);
|
||||
this.resizeListBox(this.shopBoxNode, targetCount);
|
||||
|
||||
// 默认显示商店面板
|
||||
if (this.shopPanNode) {
|
||||
@@ -606,45 +548,39 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "populate shop items", {
|
||||
count: items.length
|
||||
count: items.length,
|
||||
slotCount: this.itemCardComps.length
|
||||
});
|
||||
}
|
||||
|
||||
/** 清空商品列表 */
|
||||
private clearShopItems() {
|
||||
if (this.shopBoxNode && this.shopBoxNode.isValid) {
|
||||
this.shopBoxNode.removeAllChildren();
|
||||
/**
|
||||
* 确保药品槽位数量足够。
|
||||
* 优先复用现有槽位,不足时从 itemPrefab 实例化补充。
|
||||
*/
|
||||
private ensureItemSlots(targetCount: number) {
|
||||
if (!this.shopBoxNode || !this.itemPrefab) return;
|
||||
|
||||
// 刷新缓存
|
||||
this.cacheItemCardComps();
|
||||
|
||||
// 补充实例化不足的槽位
|
||||
while (this.itemCardComps.length < targetCount) {
|
||||
const node = instantiate(this.itemPrefab);
|
||||
this.shopBoxNode.addChild(node);
|
||||
const comp = node.getComponent(ItemListComp) || node.addComponent(ItemListComp);
|
||||
this.itemCardComps.push(comp);
|
||||
}
|
||||
}
|
||||
|
||||
private dispatchCardsToSkillSlots(cards: CardConfig[]) {
|
||||
if (!this.skillCardComps) return;
|
||||
for (let i = 0; i < this.skillCardComps.length; i++) {
|
||||
if (this.skillCardComps[i]) {
|
||||
this.skillCardComps[i].applyCardData(cards[i] ?? null);
|
||||
/** 清空商品列表(复用槽位,仅清空数据) */
|
||||
private clearShopItems() {
|
||||
for (const comp of this.itemCardComps) {
|
||||
if (comp && comp.node && comp.node.isValid) {
|
||||
comp.applyCardData(null as unknown as CardConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onUseSkillCard(event: string, args: any) {
|
||||
// 购买技能卡后不再关闭弹窗:技能卡池与英雄卡池保持一致,
|
||||
// 由 onCardUsed 立即向被使用的槽位补充一张新技能卡。
|
||||
// 弹窗由外部(按钮/阶段切换)控制显隐。
|
||||
|
||||
// 修复:独立判断 guide2 的关闭 和 guide3 的开启
|
||||
if (!smc.finish_guides.includes(2)) {
|
||||
smc.finish_guides.push(2);
|
||||
oops.gui.remove(UIID.Guide2);
|
||||
}
|
||||
|
||||
if (!smc.finish_guides.includes(3)) {
|
||||
oops.gui.open(UIID.Guide3);
|
||||
}
|
||||
|
||||
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
|
||||
this.scheduleOnce(() => this.updateCoinAndCostUI(), 0);
|
||||
}
|
||||
|
||||
/** 装备购买事件回调:记录已购 UUID,防止重复购买 */
|
||||
private onUseEquipCard(event: string, args: any) {
|
||||
const usedCard = args as CardConfig;
|
||||
@@ -668,7 +604,6 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.off(GameEvent.HeroDead, this.onHeroDead, this);
|
||||
oops.message.off(GameEvent.HeroSell, this.onHeroSell, this);
|
||||
oops.message.off(GameEvent.UseHeroCard, this.onUseHeroCard, this);
|
||||
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
oops.message.off(GameEvent.UseEquipCard, this.onUseEquipCard, this);
|
||||
oops.message.off(GameEvent.UseItemCard, this.onUseItemCard, this);
|
||||
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
|
||||
@@ -691,16 +626,6 @@ export class MissionCardComp extends CCComp {
|
||||
if (this.showEquips && this.showEquips.isValid) {
|
||||
this.showEquips.off(NodeEventType.TOUCH_END, this.onShowEquipsClick, this);
|
||||
}
|
||||
if (this.skill_refresh && this.skill_refresh.isValid) {
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_CANCEL, this.onSkillDrawTouchCancel, this);
|
||||
}
|
||||
if (this.skill_ad_refresh && this.skill_ad_refresh.isValid) {
|
||||
this.skill_ad_refresh.off(NodeEventType.TOUCH_START, this.onSkillAdDrawTouchStart, this);
|
||||
this.skill_ad_refresh.off(NodeEventType.TOUCH_END, this.onSkillAdDrawTouchEnd, this);
|
||||
this.skill_ad_refresh.off(NodeEventType.TOUCH_CANCEL, this.onSkillAdDrawTouchCancel, this);
|
||||
}
|
||||
if (this.showShop && this.showShop.isValid) {
|
||||
this.showShop.off(NodeEventType.TOUCH_END, this.onShowShopClick, this);
|
||||
}
|
||||
@@ -709,6 +634,11 @@ export class MissionCardComp extends CCComp {
|
||||
this.equip_refresh.off(NodeEventType.TOUCH_END, this.onEquipDrawTouchEnd, this);
|
||||
this.equip_refresh.off(NodeEventType.TOUCH_CANCEL, this.onEquipDrawTouchCancel, this);
|
||||
}
|
||||
if (this.skill_refresh && this.skill_refresh.isValid) {
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_START, this.onSkillDrawTouchStart, this);
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_END, this.onSkillDrawTouchEnd, this);
|
||||
this.skill_refresh.off(NodeEventType.TOUCH_CANCEL, this.onSkillDrawTouchCancel, this);
|
||||
}
|
||||
if (this.shop_refresh && this.shop_refresh.isValid) {
|
||||
this.shop_refresh.off(NodeEventType.TOUCH_START, this.onShopDrawTouchStart, this);
|
||||
this.shop_refresh.off(NodeEventType.TOUCH_END, this.onShopDrawTouchEnd, this);
|
||||
@@ -811,13 +741,14 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 单张卡牌被成功使用后的整池刷新回调:
|
||||
* - payload 为该卡槽组件实例(CHeroComp 或 SCardComp)。
|
||||
* - payload 为该卡槽组件实例(CHeroComp / EquipListComp / ItemListComp)。
|
||||
* - 命中英雄卡槽 → 重新构建 3 张英雄池卡牌并分发到所有英雄槽。
|
||||
* - 命中技能卡槽 → 重新构建 3 张技能卡并分发到所有技能槽。
|
||||
* - 命中装备卡槽 → 重新抽取装备并分发到所有装备槽。
|
||||
* - 命中药品卡槽 → 重新抽取药品并分发到所有药品槽。
|
||||
* - 未命中任何槽位 → 忽略。
|
||||
*
|
||||
* Why: 购买一张卡后立即刷新整个卡池(不扣金币),让玩家持续从新池中挑选;
|
||||
* 英雄卡池与技能卡池遵循同一逻辑。
|
||||
* 所有卡池遵循同一逻辑。
|
||||
* SpecialRefresh 自身效果(onUseSpecialCard 中的 tryRefreshHeroCards)
|
||||
* 已是整池刷新,因此 CHeroComp 对其不派发 CardUsed,避免双重刷新。
|
||||
*/
|
||||
@@ -835,15 +766,32 @@ export class MissionCardComp extends CCComp {
|
||||
return;
|
||||
}
|
||||
|
||||
const equipIdx = this.equipCardComps.findIndex(c => c === source);
|
||||
if (equipIdx >= 0) {
|
||||
this.populateEquipments();
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "refresh equip pool after buy", {
|
||||
triggerSlot: equipIdx
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const skillIdx = this.skillCardComps.findIndex(c => c === source);
|
||||
if (skillIdx >= 0) {
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
this.populateSkillCards();
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "refresh skill pool after buy", {
|
||||
triggerSlot: skillIdx
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const itemIdx = this.itemCardComps.findIndex(c => c === source);
|
||||
if (itemIdx >= 0) {
|
||||
this.populateShopItems();
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "refresh item pool after buy", {
|
||||
triggerSlot: itemIdx
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 按钮触控回调 ========================
|
||||
@@ -1030,9 +978,9 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充装备列表:
|
||||
* 填充装备列表(槽位复用模式):
|
||||
* 从 EquipPoolList 按权重抽取 3 张装备卡,
|
||||
* 实例化 equipPrefab 到 equipsBoxNode,
|
||||
* 优先复用现有槽位(EquipsBoxNode 子节点),不足再实例化新槽位,
|
||||
* 每个装备项由 EquipListComp 渲染并处理购买。
|
||||
*
|
||||
* 已购买的装备会标记为已购状态(隐藏购买按钮)。
|
||||
@@ -1040,25 +988,32 @@ export class MissionCardComp extends CCComp {
|
||||
private populateEquipments() {
|
||||
if (!this.equipsBoxNode || !this.equipPrefab) return;
|
||||
|
||||
// 清空旧列表
|
||||
this.equipsBoxNode.removeAllChildren();
|
||||
|
||||
// 从装备卡池按权重抽取 3 张
|
||||
const equips = drawEquipCards(3);
|
||||
for (const equip of equips) {
|
||||
const node = instantiate(this.equipPrefab);
|
||||
this.equipsBoxNode.addChild(node);
|
||||
const comp = node.getComponent(EquipListComp) || node.addComponent(EquipListComp);
|
||||
comp.applyCardData(equip);
|
||||
const targetCount = equips.length;
|
||||
|
||||
// 已购买的装备标记为已购
|
||||
if (this.purchasedEquipUuids.has(equip.uuid)) {
|
||||
comp.setPurchased();
|
||||
// 确保槽位数量足够(复用 + 补充实例化)
|
||||
this.ensureEquipSlots(targetCount);
|
||||
|
||||
// 分发数据到槽位
|
||||
for (let i = 0; i < this.equipCardComps.length; i++) {
|
||||
const comp = this.equipCardComps[i];
|
||||
if (!comp) continue;
|
||||
|
||||
if (i < targetCount) {
|
||||
comp.applyCardData(equips[i]);
|
||||
// 已购买的装备标记为已购
|
||||
if (this.purchasedEquipUuids.has(equips[i].uuid)) {
|
||||
comp.setPurchased();
|
||||
}
|
||||
} else {
|
||||
// 多余槽位清空
|
||||
comp.applyCardData(null as unknown as CardConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据物品数量设置列表面板高度
|
||||
this.resizeListBox(this.equipsBoxNode, equips.length);
|
||||
this.resizeListBox(this.equipsBoxNode, targetCount);
|
||||
|
||||
// 默认显示装备面板
|
||||
if (this.equipsPanNode) {
|
||||
@@ -1066,55 +1021,125 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "populate equipments", {
|
||||
count: equips.length
|
||||
count: equips.length,
|
||||
slotCount: this.equipCardComps.length
|
||||
});
|
||||
}
|
||||
|
||||
/** 清空装备列表 */
|
||||
private clearEquipments() {
|
||||
if (this.equipsBoxNode && this.equipsBoxNode.isValid) {
|
||||
this.equipsBoxNode.removeAllChildren();
|
||||
/**
|
||||
* 确保装备槽位数量足够。
|
||||
* 优先复用现有槽位,不足时从 equipPrefab 实例化补充。
|
||||
*/
|
||||
private ensureEquipSlots(targetCount: number) {
|
||||
if (!this.equipsBoxNode || !this.equipPrefab) return;
|
||||
|
||||
// 刷新缓存
|
||||
this.cacheEquipCardComps();
|
||||
|
||||
// 补充实例化不足的槽位
|
||||
while (this.equipCardComps.length < targetCount) {
|
||||
const node = instantiate(this.equipPrefab);
|
||||
this.equipsBoxNode.addChild(node);
|
||||
const comp = node.getComponent(EquipListComp) || node.addComponent(EquipListComp);
|
||||
this.equipCardComps.push(comp);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 技能抽卡按钮回调 ========================
|
||||
private onSkillDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.skill_refresh);
|
||||
}
|
||||
private onSkillDrawTouchEnd() {
|
||||
oops.audio.playEffect("music/button");
|
||||
this.playButtonClickAnim(this.skill_refresh, () => this.onClickSkillRefresh());
|
||||
}
|
||||
private onSkillDrawTouchCancel() {
|
||||
this.playButtonResetAnim(this.skill_refresh);
|
||||
/** 清空装备列表(复用槽位,仅清空数据) */
|
||||
private clearEquipments() {
|
||||
for (const comp of this.equipCardComps) {
|
||||
if (comp && comp.node && comp.node.isValid) {
|
||||
comp.applyCardData(null as unknown as CardConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onSkillAdDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.skill_ad_refresh);
|
||||
}
|
||||
private onSkillAdDrawTouchEnd() {
|
||||
oops.audio.playEffect("music/button");
|
||||
this.playButtonClickAnim(this.skill_ad_refresh, () => this.onClickSkillAdRefresh());
|
||||
}
|
||||
private onSkillAdDrawTouchCancel() {
|
||||
this.playButtonResetAnim(this.skill_ad_refresh);
|
||||
// ======================== 技能抽卡(英雄三槽模式) ========================
|
||||
|
||||
/**
|
||||
* 填充技能卡槽:
|
||||
* 从 SkillCardList 按权重抽取 3 张技能卡,
|
||||
* 优先复用现有槽位(skillBoxNode 子节点),不足再实例化新槽位,
|
||||
* 每个技能卡项由 SCardComp 渲染并处理购买。
|
||||
*/
|
||||
private populateSkillCards() {
|
||||
if (!this.skillBoxNode || !this.sCardPrefab) return;
|
||||
|
||||
// 从技能卡池按权重抽取 3 张
|
||||
const skills = drawSkillCards(3);
|
||||
const targetCount = skills.length;
|
||||
|
||||
// 确保槽位数量足够(复用 + 补充实例化)
|
||||
this.ensureSkillSlots(targetCount);
|
||||
|
||||
// 分发数据到槽位
|
||||
for (let i = 0; i < this.skillCardComps.length; i++) {
|
||||
const comp = this.skillCardComps[i];
|
||||
if (!comp) continue;
|
||||
|
||||
if (i < targetCount) {
|
||||
comp.applyDrawCard(skills[i]);
|
||||
} else {
|
||||
// 多余槽位清空
|
||||
comp.applyDrawCard(null);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据技能数量设置列表面板高度
|
||||
this.resizeListBox(this.skillBoxNode, targetCount);
|
||||
|
||||
// 默认显示技能面板
|
||||
if (this.skillPanNode) {
|
||||
this.skillPanNode.active = true;
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "populate skill cards", {
|
||||
count: skills.length,
|
||||
slotCount: this.skillCardComps.length
|
||||
});
|
||||
}
|
||||
|
||||
private onClickSkillRefresh() {
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
const success = MissionEconomy.executeRefresh(this.refreshCost);
|
||||
if (!success) {
|
||||
this.showSmallTip("refresh_coin");
|
||||
/**
|
||||
* 确保技能槽位数量足够。
|
||||
* 优先复用现有槽位,不足时从 sCardPrefab 实例化补充。
|
||||
*/
|
||||
private ensureSkillSlots(targetCount: number) {
|
||||
if (!this.skillBoxNode || !this.sCardPrefab) return;
|
||||
|
||||
// 刷新缓存
|
||||
this.cacheSkillCardComps();
|
||||
|
||||
// 补充实例化不足的槽位
|
||||
while (this.skillCardComps.length < targetCount) {
|
||||
const node = instantiate(this.sCardPrefab);
|
||||
this.skillBoxNode.addChild(node);
|
||||
const comp = node.getComponent(SCardComp) || node.addComponent(SCardComp);
|
||||
this.skillCardComps.push(comp);
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空技能卡槽(复用槽位,仅清空数据) */
|
||||
private clearSkillCards() {
|
||||
for (const comp of this.skillCardComps) {
|
||||
if (comp && comp.node && comp.node.isValid) {
|
||||
comp.clearBySystem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存技能卡槽组件引用。
|
||||
* 技能卡槽在编辑器中通过 sCardPrefab 实例化为 skillBoxNode 的子节点,
|
||||
* 每个子节点根节点挂载 SCardComp。
|
||||
*/
|
||||
private cacheSkillCardComps() {
|
||||
if (!this.skillBoxNode || !this.skillBoxNode.isValid) {
|
||||
this.skillCardComps = [];
|
||||
return;
|
||||
}
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
}
|
||||
|
||||
private onClickSkillAdRefresh() {
|
||||
// TODO: 接入广告 SDK 逻辑,目前先直接刷新
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
this.skillCardComps = this.skillBoxNode.children
|
||||
.map(node => node.getComponent(SCardComp))
|
||||
.filter((comp): comp is SCardComp => !!comp);
|
||||
}
|
||||
|
||||
// ======================== 装备/药品刷新按钮回调 ========================
|
||||
@@ -1141,6 +1166,17 @@ export class MissionCardComp extends CCComp {
|
||||
this.playButtonResetAnim(this.shop_refresh);
|
||||
}
|
||||
|
||||
private onSkillDrawTouchStart() {
|
||||
this.playButtonPressAnim(this.skill_refresh);
|
||||
}
|
||||
private onSkillDrawTouchEnd() {
|
||||
oops.audio.playEffect("music/button");
|
||||
this.playButtonClickAnim(this.skill_refresh, () => this.onClickSkillRefresh());
|
||||
}
|
||||
private onSkillDrawTouchCancel() {
|
||||
this.playButtonResetAnim(this.skill_refresh);
|
||||
}
|
||||
|
||||
/** 装备刷新:扣费后重新抽取 3 张装备展示 */
|
||||
private onClickEquipRefresh() {
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
@@ -1163,16 +1199,24 @@ export class MissionCardComp extends CCComp {
|
||||
this.populateShopItems();
|
||||
}
|
||||
|
||||
/** 技能刷新:扣费后重新抽取 3 张技能卡展示 */
|
||||
private onClickSkillRefresh() {
|
||||
const success = MissionEconomy.executeRefresh(this.refreshCost);
|
||||
if (!success) {
|
||||
this.showSmallTip("refresh_coin");
|
||||
return;
|
||||
}
|
||||
this.populateSkillCards();
|
||||
}
|
||||
|
||||
/** 将 cardPrefab 实例化为 3 个卡槽并映射为 CHeroComp,形成固定顺序控制数组(幂等:已缓存则不重复实例化) */
|
||||
private cacheCardComps() {
|
||||
// 幂等保护:卡槽已存在且节点有效时,仅刷新 skillCardComps 缓存,不重复实例化英雄卡槽
|
||||
// 幂等保护:卡槽已存在且节点有效时,仅刷新其他缓存,不重复实例化英雄卡槽
|
||||
const hasValidHeroComps = this.cardComps.length === 3
|
||||
&& this.cardComps.every(c => c && c.node && c.node.isValid);
|
||||
if (hasValidHeroComps) {
|
||||
const skillNodes = [this.skill_card1, this.skill_card2, this.skill_card3];
|
||||
this.skillCardComps = skillNodes
|
||||
.map(node => node?.getComponent(SCardComp))
|
||||
.filter((comp): comp is SCardComp => !!comp);
|
||||
this.cacheEquipCardComps();
|
||||
this.cacheItemCardComps();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1210,10 +1254,38 @@ export class MissionCardComp extends CCComp {
|
||||
});
|
||||
}
|
||||
|
||||
const skillNodes = [this.skill_card1, this.skill_card2, this.skill_card3];
|
||||
this.skillCardComps = skillNodes
|
||||
.map(node => node?.getComponent(SCardComp))
|
||||
.filter((comp): comp is SCardComp => !!comp);
|
||||
this.cacheEquipCardComps();
|
||||
this.cacheItemCardComps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存装备卡槽组件引用。
|
||||
* 装备卡槽在编辑器中通过 equipPrefab 实例化为 equipsBoxNode 的子节点,
|
||||
* 每个子节点根节点挂载 EquipListComp。
|
||||
*/
|
||||
private cacheEquipCardComps() {
|
||||
if (!this.equipsBoxNode || !this.equipsBoxNode.isValid) {
|
||||
this.equipCardComps = [];
|
||||
return;
|
||||
}
|
||||
this.equipCardComps = this.equipsBoxNode.children
|
||||
.map(node => node.getComponent(EquipListComp))
|
||||
.filter((comp): comp is EquipListComp => !!comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存药品卡槽组件引用。
|
||||
* 药品卡槽在编辑器中通过 itemPrefab 实例化为 shopBoxNode 的子节点,
|
||||
* 每个子节点根节点挂载 ItemListComp。
|
||||
*/
|
||||
private cacheItemCardComps() {
|
||||
if (!this.shopBoxNode || !this.shopBoxNode.isValid) {
|
||||
this.itemCardComps = [];
|
||||
return;
|
||||
}
|
||||
this.itemCardComps = this.shopBoxNode.children
|
||||
.map(node => node.getComponent(ItemListComp))
|
||||
.filter((comp): comp is ItemListComp => !!comp);
|
||||
}
|
||||
|
||||
// ======================== 核心业务:抽卡 ========================
|
||||
@@ -1373,16 +1445,6 @@ export class MissionCardComp extends CCComp {
|
||||
return cards[cards.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建技能卡抽卡结果,返回 3 张。
|
||||
*
|
||||
* 技能卡池长期存在,不再按波次过滤,
|
||||
* 直接从 SCardSet 的 drawSkillCards 按权重抽取。
|
||||
*/
|
||||
private buildSkillDrawCards(): CardConfig[] {
|
||||
return drawSkillCards(3);
|
||||
}
|
||||
|
||||
private tryRefreshHeroCards(heroType?: HType): boolean {
|
||||
const cards = drawCardsByRule(1, {
|
||||
count: 3,
|
||||
@@ -1438,11 +1500,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.cardComps.forEach(comp => {
|
||||
if (comp) comp.clearBySystem();
|
||||
});
|
||||
if (this.skillCardComps) {
|
||||
this.skillCardComps.forEach(comp => {
|
||||
if (comp) comp.applyCardData(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private layoutCardSlots() {
|
||||
@@ -1489,18 +1546,6 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.skill_refresh) {
|
||||
const nobg = this.skill_refresh.getChildByName("nobg");
|
||||
if (nobg) {
|
||||
nobg.active = !this.canDrawCards();
|
||||
}
|
||||
const coinNode = this.skill_refresh.getChildByName("coin");
|
||||
const numLabel = coinNode?.getChildByName("num")?.getComponent(Label);
|
||||
if (numLabel) {
|
||||
numLabel.string = `${MissionEconomy.getRefreshCost(this.refreshCost)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.equip_refresh) {
|
||||
const nobg = this.equip_refresh.getChildByName("nobg");
|
||||
if (nobg) {
|
||||
@@ -1524,6 +1569,18 @@ export class MissionCardComp extends CCComp {
|
||||
numLabel.string = `${MissionEconomy.getRefreshCost(this.refreshCost)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.skill_refresh) {
|
||||
const nobg = this.skill_refresh.getChildByName("nobg");
|
||||
if (nobg) {
|
||||
nobg.active = !this.canDrawCards();
|
||||
}
|
||||
const coinNode = this.skill_refresh.getChildByName("coin");
|
||||
const numLabel = coinNode?.getChildByName("num")?.getComponent(Label);
|
||||
if (numLabel) {
|
||||
numLabel.string = `${MissionEconomy.getRefreshCost(this.refreshCost)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateCoinAndCostUI() {
|
||||
|
||||
Reference in New Issue
Block a user