refactor(missionCard): 重构抽卡逻辑,新增无重复抽取工具方法

1.  抽卡逻辑抽离为pickUniqueHeroCards私有方法,统一处理无重复抽取规则
2.  优化buildDrawCards和tryRefreshHeroCards的抽卡实现,确保同组内和场上英雄不重复
3.  调整刷新次数UI的文本显示格式,移除固定前后缀文案
4.  移除废弃的drawCardsByRule调用,统一使用自有抽卡逻辑
This commit is contained in:
panFD
2026-08-20 00:10:44 +08:00
parent 79746409ef
commit c1286173f7

View File

@@ -19,7 +19,8 @@
* 关键设计: * 关键设计:
* - 3 个 CHeroComp 由 cardPrefab 动态实例化生成,通过 cacheCardComps() 映射为有序数组 cardComps[] * - 3 个 CHeroComp 由 cardPrefab 动态实例化生成,通过 cacheCardComps() 映射为有序数组 cardComps[]
* 之后所有分发、清空操作均通过此数组进行。 * 之后所有分发、清空操作均通过此数组进行。
* - buildDrawCards() 从基础英雄卡池按权重抽取 3 张,不足时循环补齐。 * - buildDrawCards() 从基础英雄卡池按权重抽取 3 张,同组内英雄绝不重复,
* 也不与场上存活英雄撞车(池不足时回退全量池,仍不重复补位)。
* - 英雄上限校验onUseHeroCard采用 guard/cancel 模式: * - 英雄上限校验onUseHeroCard采用 guard/cancel 模式:
* CHeroComp 发出 UseHeroCard 事件并传入 guard 对象, * CHeroComp 发出 UseHeroCard 事件并传入 guard 对象,
* 本组件可通过 guard.cancel=true 阻止使用。 * 本组件可通过 guard.cancel=true 阻止使用。
@@ -41,7 +42,7 @@ import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, Tween, twe
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType } from "../common/config/CardSet"; import { CardConfig, CardPoolList, CardType, SpecialRefreshCardList, SpecialRefreshHeroType } from "../common/config/CardSet";
import { drawEquipCards } from "../common/config/EquipSet"; import { drawEquipCards } from "../common/config/EquipSet";
import { drawItemCards } from "../common/config/ICardSet"; import { drawItemCards } from "../common/config/ICardSet";
import { drawSkillCards } from "../common/config/SCardSet"; import { drawSkillCards } from "../common/config/SCardSet";
@@ -1802,9 +1803,9 @@ export class MissionCardComp extends CCComp {
* 构建本次抽卡结果,返回 3 张英雄卡。 * 构建本次抽卡结果,返回 3 张英雄卡。
* *
* 规则: * 规则:
* 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复。 * 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复(三选一硬性规则)
* 2. 过滤已上场英雄(卡池不足 3 张时自动回退到未过滤池,保证能抽满)。 * 2. 过滤已上场英雄(卡池不足 3 张时自动回退到未过滤池,保证能抽满)。
* 3. 不足 3 张时循环补齐(允许重复) * 3. 候选池唯一英雄不足 3 个时返回实际数量,也绝不重复补位
* *
* 注:升级卡机制已移除,英雄卡池只出普通英雄卡。 * 注:升级卡机制已移除,英雄卡池只出普通英雄卡。
*/ */
@@ -1817,24 +1818,7 @@ export class MissionCardComp extends CCComp {
const filtered = allHeroCards.filter(c => !onFieldUuids.has(c.uuid)); const filtered = allHeroCards.filter(c => !onFieldUuids.has(c.uuid));
const availableHeroCards = filtered.length >= 3 ? filtered : allHeroCards; const availableHeroCards = filtered.length >= 3 ? filtered : allHeroCards;
const result: CardConfig[] = []; return this.pickUniqueHeroCards(availableHeroCards, 3);
const usedUuids = new Set<number>();
// 按权重抽取,同一次抽卡内英雄不重复
while (result.length < 3) {
const pool = availableHeroCards.filter(c => !usedUuids.has(c.uuid));
if (pool.length === 0) break;
const pick = this.weightedPick(pool);
if (!pick) break;
result.push(pick);
usedUuids.add(pick.uuid);
}
// 兜底:不足 3 张时循环补齐(允许重复)
while (result.length < 3) {
result.push(availableHeroCards[result.length % availableHeroCards.length]);
}
return result.slice(0, 3);
} }
/** 单次按权重抽取一张卡 */ /** 单次按权重抽取一张卡 */
@@ -1849,20 +1833,48 @@ export class MissionCardComp extends CCComp {
return cards[cards.length - 1]; return cards[cards.length - 1];
} }
/**
* 从候选池按权重抽取 count 张互不重复的英雄卡。
* Why: 三选一不允许出现一样的英雄——池内唯一英雄不足 count 时
* 返回实际抽到的数量,也绝不以重复卡补位。
* @param pool 已完成上场过滤/类型过滤的候选池
* @param count 目标张数
* @returns 不重复的卡配置数组0 ~ count 张)
*/
private pickUniqueHeroCards(pool: CardConfig[], count: number): CardConfig[] {
const result: CardConfig[] = [];
const usedUuids = new Set<number>();
while (result.length < count) {
const rest = pool.filter(c => !usedUuids.has(c.uuid));
if (rest.length === 0) break;
const pick = this.weightedPick(rest);
if (!pick) break;
result.push(pick);
usedUuids.add(pick.uuid);
}
return result;
}
/**
* 刷新卡效果:按英雄类型重新抽取 3 张英雄卡。
* 先过滤(类型 + 已上场)再抽取,保证三选一内不重复且尽量不与场上英雄撞车;
* 子池不足 3 张时回退到该类型的全量池,仍抽不齐则如实返回(不重复补位)。
*/
private tryRefreshHeroCards(heroType?: HType): boolean { private tryRefreshHeroCards(heroType?: HType): boolean {
let cards = drawCardsByRule({ let pool = CardPoolList.filter(c => c.type === CardType.Hero);
count: 3, if (heroType !== undefined) {
type: CardType.Hero, pool = pool.filter(c => HeroInfo[c.uuid]?.type === heroType);
heroType, }
unique: true, // 保证一次刷新内的英雄卡不重复 if (pool.length === 0) return false;
});
// 过滤已上场英雄;若过滤后为空则回退到未过滤池,避免无可抽卡
const onFieldUuids = this.getAliveHeroUuidSet(); const onFieldUuids = this.getAliveHeroUuidSet();
const filtered = cards.filter(c => !onFieldUuids.has(c.uuid)); const filtered = pool.filter(c => !onFieldUuids.has(c.uuid));
if (filtered.length > 0) cards = filtered; const available = filtered.length >= 3 ? filtered : pool;
const cards = this.pickUniqueHeroCards(available, 3);
if (cards.length <= 0) return false; if (cards.length <= 0) return false;
this.layoutCardSlots(); this.layoutCardSlots();
this.dispatchCardsToSlots(cards.slice(0, 3)); this.dispatchCardsToSlots(cards);
return true; return true;
} }
@@ -2048,10 +2060,10 @@ export class MissionCardComp extends CCComp {
this.updateDrawCostUI(); this.updateDrawCostUI();
} }
/** 刷新选择英雄次数提示 Label"第X次"),未绑定或节点失效时静默跳过 */ /** 刷新选择英雄次数提示 Label仅数字,前后缀文字由 UI 提供),未绑定或节点失效时静默跳过 */
private updatePickCountLabel() { private updatePickCountLabel() {
if (this.pickCountLabel && this.pickCountLabel.isValid) { if (this.pickCountLabel && this.pickCountLabel.isValid) {
this.pickCountLabel.string = `${this.currentPickIndex}`; this.pickCountLabel.string = `${this.currentPickIndex}`;
} }
} }