refactor(missionCard): 重构卡牌池刷新逻辑,改为整池刷新

1.  修改SCardComp的注释,将补卡逻辑改为整池刷新说明
2.  重构MissionCardComp的onCardUsed回调,替换单张补卡为整池刷新
3.  删除buildSingleHeroCard和buildSingleSkillCard两个单张补卡方法
4.  调整注释说明,新增特殊刷新避免重复触发的逻辑
This commit is contained in:
pan
2026-07-21 17:01:35 +08:00
parent fb299f6328
commit db3b5f3649
3 changed files with 329 additions and 107 deletions

View File

@@ -37,11 +37,11 @@
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 98 "__id__": 110
} }
], ],
"_prefab": { "_prefab": {
"__id__": 100 "__id__": 112
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
@@ -1805,23 +1805,29 @@
}, },
{ {
"__id__": 87 "__id__": 87
},
{
"__id__": 93
},
{
"__id__": 99
} }
], ],
"_active": true, "_active": true,
"_components": [ "_components": [
{ {
"__id__": 93 "__id__": 105
}, },
{ {
"__id__": 95 "__id__": 107
} }
], ],
"_prefab": { "_prefab": {
"__id__": 97 "__id__": 109
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -26.182, "x": -22.948,
"y": -0.337, "y": -0.337,
"z": 0 "z": 0
}, },
@@ -1885,7 +1891,7 @@
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.2, "x": 0.2,
"y": 0.14, "y": 0.2,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,
@@ -1913,7 +1919,7 @@
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 80, "width": 80,
"height": 100 "height": 85
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1943,13 +1949,13 @@
"_dstBlendFactor": 4, "_dstBlendFactor": 4,
"_color": { "_color": {
"__type__": "cc.Color", "__type__": "cc.Color",
"r": 253, "r": 255,
"g": 213, "g": 255,
"b": 0, "b": 255,
"a": 255 "a": 255
}, },
"_spriteFrame": { "_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@79d6f", "__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@b4af4",
"__expectedType__": "cc.SpriteFrame" "__expectedType__": "cc.SpriteFrame"
}, },
"_type": 0, "_type": 0,
@@ -1989,14 +1995,14 @@
}, },
{ {
"__type__": "cc.Node", "__type__": "cc.Node",
"_name": "Label", "_name": "Slider_Level_02_Bg",
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"_parent": { "_parent": {
"__id__": 80 "__id__": 80
}, },
"_children": [], "_children": [],
"_active": true, "_active": false,
"_components": [ "_components": [
{ {
"__id__": 88 "__id__": 88
@@ -2010,8 +2016,280 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -0.103, "x": 0,
"y": -0.601, "y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.6,
"y": 0.6,
"z": 1
},
"_mobility": 0,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 87
},
"_enabled": true,
"__prefab": {
"__id__": 89
},
"_contentSize": {
"__type__": "cc.Size",
"width": 20,
"height": 20
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c8MjBtG2ZAeJMgaljbWriK"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 87
},
"_enabled": true,
"__prefab": {
"__id__": 91
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@46c83",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d6anUnkoJCNoTLRDU0POvf"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "b5ndU4FTRH/r1NQ9j22a6H",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Slider_Level_03_InnerBorder1",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 80
},
"_children": [],
"_active": false,
"_components": [
{
"__id__": 94
},
{
"__id__": 96
}
],
"_prefab": {
"__id__": 98
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.6,
"y": 0.6,
"z": 1
},
"_mobility": 0,
"_layer": 1,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 93
},
"_enabled": true,
"__prefab": {
"__id__": 95
},
"_contentSize": {
"__type__": "cc.Size",
"width": 23,
"height": 24
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93/XbJIIRDBJl2pf7NOTi2"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 93
},
"_enabled": true,
"__prefab": {
"__id__": 97
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_spriteFrame": {
"__uuid__": "cb93c900-b440-4571-91d1-7da1636e3d73@95588",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 1,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c2U0QyM1hN9L9LRmqrayYY"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "66M2yZHdhHA4Ogo8TVzoR9",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "Label",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 80
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 100
},
{
"__id__": 102
}
],
"_prefab": {
"__id__": 104
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0.433,
"y": 2.048,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -2043,16 +2321,16 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 87 "__id__": 99
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 89 "__id__": 101
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 7.29, "width": 9.57,
"height": 40 "height": 25
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -2071,11 +2349,11 @@
"_objFlags": 0, "_objFlags": 0,
"__editorExtras__": {}, "__editorExtras__": {},
"node": { "node": {
"__id__": 87 "__id__": 99
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 91 "__id__": 103
}, },
"_customMaterial": null, "_customMaterial": null,
"_srcBlendFactor": 2, "_srcBlendFactor": 2,
@@ -2087,26 +2365,26 @@
"b": 255, "b": 255,
"a": 255 "a": 255
}, },
"_string": "1", "_string": "0",
"_horizontalAlign": 1, "_horizontalAlign": 1,
"_verticalAlign": 1, "_verticalAlign": 1,
"_actualFontSize": 25, "_actualFontSize": 13,
"_fontSize": 25, "_fontSize": 13,
"_fontFamily": "Arial", "_fontFamily": "Arial",
"_lineHeight": 40, "_lineHeight": 25,
"_overflow": 0, "_overflow": 0,
"_enableWrapText": true, "_enableWrapText": true,
"_font": { "_font": {
"__uuid__": "eca7fc51-87d0-4bb0-89e1-aa9c39411bc6", "__uuid__": "dd574056-40e9-4545-8eec-eea63d489818",
"__expectedType__": "cc.BitmapFont" "__expectedType__": "cc.BitmapFont"
}, },
"_isSystemFontUsed": false, "_isSystemFontUsed": false,
"_spacingX": 0, "_spacingX": 0,
"_isItalic": false, "_isItalic": false,
"_isBold": true, "_isBold": false,
"_isUnderline": false, "_isUnderline": false,
"_underlineHeight": 2, "_underlineHeight": 2,
"_cacheMode": 0, "_cacheMode": 1,
"_enableOutline": true, "_enableOutline": true,
"_outlineColor": { "_outlineColor": {
"__type__": "cc.Color", "__type__": "cc.Color",
@@ -2115,7 +2393,7 @@
"b": 0, "b": 0,
"a": 255 "a": 255
}, },
"_outlineWidth": 1, "_outlineWidth": 2,
"_enableShadow": false, "_enableShadow": false,
"_shadowColor": { "_shadowColor": {
"__type__": "cc.Color", "__type__": "cc.Color",
@@ -2159,12 +2437,12 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 94 "__id__": 106
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 10, "width": 20,
"height": 13 "height": 20
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -2187,7 +2465,7 @@
}, },
"_enabled": false, "_enabled": false,
"__prefab": { "__prefab": {
"__id__": 96 "__id__": 108
}, },
"_resizeMode": 1, "_resizeMode": 1,
"_layoutType": 1, "_layoutType": 1,
@@ -2238,7 +2516,7 @@
}, },
"_enabled": true, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 99 "__id__": 111
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",

View File

@@ -574,14 +574,16 @@ export class MissionCardComp extends CCComp {
} }
/** /**
* 单张卡牌被成功使用后的补卡回调: * 单张卡牌被成功使用后的整池刷新回调:
* - payload 为该卡槽组件实例CardComp 或 SCardComp * - payload 为该卡槽组件实例CardComp 或 SCardComp
* - 命中英雄卡槽 → 调用 buildSingleHeroCard 补一张英雄/升级卡 * - 命中英雄卡槽 → 重新构建 3 张英雄池卡牌并分发到所有英雄槽
* - 命中技能卡槽 → 调用 buildSingleSkillCard 补一张技能卡 * - 命中技能卡槽 → 重新构建 3 张技能卡并分发到所有技能槽
* - 未命中任何槽位(如已销毁/未缓存)→ 忽略。 * - 未命中任何槽位 → 忽略。
* *
* Why: 购买一张卡后立即补充新卡,避免出现空槽,提升抽卡体验 * Why: 购买一张卡后立即刷新整个卡池(不扣金币),让玩家持续从新池中挑选
* 英雄卡池与技能卡池遵循同一逻辑。 * 英雄卡池与技能卡池遵循同一逻辑。
* SpecialRefresh 自身效果onUseSpecialCard 中的 tryRefreshHeroCards
* 已是整池刷新,因此 CardComp 对其不派发 CardUsed避免双重刷新。
*/ */
private onCardUsed(event: string, args: any) { private onCardUsed(event: string, args: any) {
const source = args; const source = args;
@@ -589,26 +591,20 @@ export class MissionCardComp extends CCComp {
const heroIdx = this.cardComps.findIndex(c => c === source); const heroIdx = this.cardComps.findIndex(c => c === source);
if (heroIdx >= 0) { if (heroIdx >= 0) {
const card = this.buildSingleHeroCard(); const cards = this.buildDrawCards();
if (card) { this.dispatchCardsToSlots(cards);
this.cardComps[heroIdx].applyDrawCard(card); mLogger.log(this.debugMode, "MissionCardComp", "refresh hero pool after buy", {
} triggerSlot: heroIdx
mLogger.log(this.debugMode, "MissionCardComp", "refill hero slot", {
index: heroIdx,
uuid: card?.uuid ?? 0
}); });
return; return;
} }
const skillIdx = this.skillCardComps.findIndex(c => c === source); const skillIdx = this.skillCardComps.findIndex(c => c === source);
if (skillIdx >= 0) { if (skillIdx >= 0) {
const card = this.buildSingleSkillCard(); const cards = this.buildSkillDrawCards();
if (card) { this.dispatchCardsToSkillSlots(cards);
this.skillCardComps[skillIdx].applyDrawCard(card); mLogger.log(this.debugMode, "MissionCardComp", "refresh skill pool after buy", {
} triggerSlot: skillIdx
mLogger.log(this.debugMode, "MissionCardComp", "refill skill slot", {
index: skillIdx,
uuid: card?.uuid ?? 0
}); });
return; return;
} }
@@ -999,58 +995,6 @@ export class MissionCardComp extends CCComp {
return filled; return filled;
} }
/**
* 构建单张英雄池卡牌,用于购买后补卡。
*
* 规则与 buildDrawCards 保持一致(不含"必出轮询"逻辑):
* 1. 满员且有可升级英雄 → 仅从升级卡池按权重抽 1 张。
* 2. 否则从 升级卡 + 未召唤英雄卡 的混合池按权重抽 1 张。
*
* @returns 卡牌数据;池为空时返回 null
*/
private buildSingleHeroCard(): CardConfig | null {
const upgradeCards = this.buildHeroUpgradeCards();
const aliveHeroCount = this.getAliveHeroCount();
const heroMax = this.getMissionHeroMaxNum();
if (aliveHeroCount >= heroMax && upgradeCards.length > 0) {
return this.weightedPick(upgradeCards);
}
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
const aliveHeroUuids = this.getAliveHeroUuids();
const availableHeroCards = heroCards.filter(c => !aliveHeroUuids.has(c.uuid));
const mixedPool: CardConfig[] = [...upgradeCards, ...availableHeroCards];
if (mixedPool.length === 0) return null;
return this.weightedPick(mixedPool);
}
/**
* 构建单张技能卡,用于购买后补卡。
* 规则与 buildSkillDrawCards 对齐(按当前 wave 过滤)。
*
* @returns 卡牌数据;池为空时返回 null
*/
private buildSingleSkillCard(): CardConfig | null {
const currentWave = this.getCurrentWave();
const cards = drawCardsByRule(1, {
count: 1,
type: CardType.Skill,
wave: currentWave,
unique: true
});
if (cards.length > 0) return cards[0];
// 兜底:放宽 wave 限制再尝试
const fallback = drawCardsByRule(1, {
count: 1,
type: CardType.Skill,
unique: true
});
return fallback[0] ?? null;
}
private tryRefreshHeroCards(heroType?: HType): boolean { private tryRefreshHeroCards(heroType?: HType): boolean {
const cards = drawCardsByRule(1, { const cards = drawCardsByRule(1, {
count: 3, count: 3,

View File

@@ -109,7 +109,7 @@ export class SCardComp extends CCComp {
this.clearAfterUse(); this.clearAfterUse();
this.isUsing = false; this.isUsing = false;
oops.message.dispatchEvent(GameEvent.UseSkillCard, used); oops.message.dispatchEvent(GameEvent.UseSkillCard, used);
// 派发 CardUsed 让 MissionCardComp 立即补一张新技能卡到本槽位 // 派发 CardUsed 让 MissionCardComp 刷新整个技能卡池
oops.message.dispatchEvent(GameEvent.CardUsed, this); oops.message.dispatchEvent(GameEvent.CardUsed, this);
}); });
return used; return used;