refactor(mission): 重构卡牌系统为英雄卡槽组件

1. 新增CHeroComp作为英雄卡槽专用组件,替换原CardComp
2. 调整mission.prefab布局与预制体引用,适配新卡槽组件
3. 优化MissionCardComp的卡槽缓存与布局逻辑
4. 更新所有相关注释与类型引用,统一代码语义
This commit is contained in:
panFD
2026-07-26 12:52:58 +08:00
parent 399e25c3a6
commit 16996b9805
6 changed files with 5298 additions and 42 deletions

View File

@@ -3,7 +3,7 @@
* @description 卡牌系统核心控制器(战斗 UI 层 + 业务逻辑层)
*
* 职责:
* 1. **卡牌分发管理** —— 从卡池抽取 3 张卡,分发到 3 个 CardComp 槽位。
* 1. **卡牌分发管理** —— 从卡池抽取 3 张卡,分发到 3 个 CHeroComp 槽位。
* 抽卡规则:场上已有英雄时按权重混合"对应英雄的升级卡" + "其他英雄卡" + "刷新卡"
* 场上无英雄时仅抽取英雄卡和刷新卡。
* 2. **金币费用管理** —— 抽卡费用refreshCost的扣除。
@@ -15,11 +15,11 @@
* 6. **准备/战斗阶段切换** —— 控制卡牌面板的 展开/收起 动画。
*
* 关键设计:
* - 3 个 CardComp 由 cardPrefab 动态实例化生成,通过 cacheCardComps() 映射为有序数组 cardComps[]
* - 3 个 CHeroComp 由 cardPrefab 动态实例化生成,通过 cacheCardComps() 映射为有序数组 cardComps[]
* 之后所有分发、清空操作均通过此数组进行。
* - buildDrawCards() 动态合并升级卡池和基础卡池后抽取 3 张,不足时循环补齐。
* - 英雄上限校验onUseHeroCard采用 guard/cancel 模式:
* CardComp 发出 UseHeroCard 事件并传入 guard 对象,
* CHeroComp 发出 UseHeroCard 事件并传入 guard 对象,
* 本组件可通过 guard.cancel=true 阻止使用。
*
* 历史:
@@ -28,7 +28,7 @@
* - 英雄升级仅通过升级卡SpecialUpgrade触发按 UUID 精确升级场上对应英雄。
*
* 依赖:
* - CardComp —— 单卡槽位
* - CHeroComp —— 单卡槽位
* - HInfoComp —— 英雄信息面板
* - CardSet 模块 —— 卡池配置、抽卡规则、特殊卡数据
* - HeroAttrsComp —— 英雄属性(升级)
@@ -43,7 +43,7 @@ import { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCard
import { drawSkillCards } from "../common/config/SCardSet";
import { drawEquipCards } from "../common/config/EquipSet";
import { drawItemCards } from "../common/config/ICardSet";
import { CardComp } from "./CardComp";
import { CHeroComp } from "./CHeroComp";
import { SCardComp } from "./SCardComp";
import { EquipListComp } from "./EquipListComp";
import { oops } from "db://oops-framework/core/Oops";
@@ -188,8 +188,8 @@ export class MissionCardComp extends CCComp {
// ======================== 运行时状态 ========================
/** 三个槽位对应的 CardComp 控制器缓存(有序数组) */
private cardComps: CardComp[] = [];
/** 三个槽位对应的 CHeroComp 控制器缓存(有序数组) */
private cardComps: CHeroComp[] = [];
/** 技能卡槽控制器缓存 */
private skillCardComps: SCardComp[] = [];
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
@@ -237,7 +237,7 @@ export class MissionCardComp extends CCComp {
/**
* 组件加载:
* 1. 绑定生命周期事件和按钮交互事件。
* 2. 缓存 3 个 CardComp 子控制器引用。
* 2. 缓存 3 个 CHeroComp 子控制器引用。
* 3. 计算并设置槽位水平布局。
* 4. 初始化卡牌面板缩放参数。
* 5. 初始化面板轮播布局。
@@ -767,7 +767,7 @@ export class MissionCardComp extends CCComp {
}
/**
* 使用英雄卡的 guard 校验(由 CardComp 通过 UseHeroCard 事件调用):
* 使用英雄卡的 guard 校验(由 CHeroComp 通过 UseHeroCard 事件调用):
* - 当前英雄数 < 上限 → 允许使用。
* - 已满 → 阻止使用cancel=true弹 toast。
*
@@ -823,7 +823,7 @@ export class MissionCardComp extends CCComp {
/**
* 单张卡牌被成功使用后的整池刷新回调:
* - payload 为该卡槽组件实例CardComp 或 SCardComp
* - payload 为该卡槽组件实例CHeroComp 或 SCardComp
* - 命中英雄卡槽 → 重新构建 3 张英雄池卡牌并分发到所有英雄槽。
* - 命中技能卡槽 → 重新构建 3 张技能卡并分发到所有技能槽。
* - 未命中任何槽位 → 忽略。
@@ -831,7 +831,7 @@ export class MissionCardComp extends CCComp {
* Why: 购买一张卡后立即刷新整个卡池(不扣金币),让玩家持续从新池中挑选;
* 英雄卡池与技能卡池遵循同一逻辑。
* SpecialRefresh 自身效果onUseSpecialCard 中的 tryRefreshHeroCards
* 已是整池刷新,因此 CardComp 对其不派发 CardUsed避免双重刷新。
* 已是整池刷新,因此 CHeroComp 对其不派发 CardUsed避免双重刷新。
*/
private onCardUsed(event: string, args: any) {
const source = args;
@@ -1175,8 +1175,19 @@ export class MissionCardComp extends CCComp {
this.populateShopItems();
}
/** 将 cardPrefab 实例化为 3 个卡槽并映射为 CardComp形成固定顺序控制数组 */
/** 将 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);
return;
}
// 清理旧卡槽(如果存在)
for (const comp of this.cardComps) {
if (comp && comp.node && comp.node.isValid) {
@@ -1190,7 +1201,7 @@ export class MissionCardComp extends CCComp {
for (let i = 0; i < 3; i++) {
const node = instantiate(this.cardPrefab);
this.cards_node.addChild(node);
const comp = node.getComponent(CardComp) || node.addComponent(CardComp);
const comp = node.getComponent(CHeroComp) || node.addComponent(CHeroComp);
this.cardComps.push(comp);
mLogger.log(this.debugMode, "MissionCardComp", "cacheCardComps instantiate", {
index: i,
@@ -1524,7 +1535,7 @@ export class MissionCardComp extends CCComp {
return undefined;
}
/** 全量分发给 3 槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
/** 全量分发给 3 槽;每个槽位是否接收由 CHeroComp 自己判断(锁定可跳过) */
private dispatchCardsToSlots(cards: CardConfig[]) {
if (!this.cardComps) return;
for (let i = 0; i < this.cardComps.length; i++) {
@@ -1851,8 +1862,8 @@ export class MissionCardComp extends CCComp {
this.resetButtonScale(this.cards_chou);
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
this.cardComps = [] as any;
this.skillCardComps = [] as any;
this.cardComps = [];
this.skillCardComps = [];
this.purchasedEquipUuids.clear();
this.purchasedItemUuids.clear();