refactor(map): 统一卡牌购买后的全池刷新逻辑

1.  为装备、物品、技能卡牌组件统一添加CardUsed事件派发
2.  重构MissionCardComp的卡槽管理,改为槽位复用模式
3.  拆分并统一装备、物品、技能卡池的刷新逻辑
4.  移除冗余的UseSkillCard事件监听与处理
5.  整理并缓存各类卡槽组件引用
This commit is contained in:
panFD
2026-07-26 19:19:30 +08:00
parent 24eb72ef99
commit 1593c7ffbf
5 changed files with 918 additions and 4976 deletions

View File

@@ -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() {