refactor(map): 重构抽卡逻辑与卡牌渲染规则

1.  实现升级卡轮询机制,按英雄UUID升序循环切换必出升级卡
2.  重构buildDrawCards抽卡流程,分离必出升级卡与随机抽取逻辑
3.  移除抽卡池内的刷新功能卡,调整特殊卡渲染逻辑
4.  统一英雄卡与升级卡的交互与渲染表现
This commit is contained in:
panFD
2026-07-20 22:35:21 +08:00
parent cff7b4703f
commit 1612e02790
3 changed files with 1740 additions and 679 deletions

View File

@@ -23,7 +23,7 @@ import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, EventTouch, Input, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, input, resources, Light, UITransform, Widget } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList, CKind, CardPoolList } from "../common/config/CardSet";
import { CardConfig, CardType, SpecialRefreshCardList, CKind, CardPoolList } from "../common/config/CardSet";
import { CardBgComp } from "./CardBgComp";
import { HeroInfo } from "../common/config/heroSet";
import { SkillSet } from "../common/config/SkillSet";
@@ -525,9 +525,10 @@ export class CardComp extends CCComp {
private onCardTap() {
this.showCallBtn();
oops.message.dispatchEvent(GameEvent.CardSelected, this);
// 英雄卡打开信息面板(点击替代长按)
if (this.card_type === CardType.Hero) {
const heroUuid = this.card_uuid;
// 英雄卡 / 升级卡打开信息面板(点击替代长按)
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_uuid;
if (this.card_type === CardType.Hero || isUpgradeCard) {
const heroUuid = isUpgradeCard ? (this.cardData!.target_hero_uuid as number) : this.card_uuid;
const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1);
const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1);
oops.gui.remove(UIID.HInfo);
@@ -638,8 +639,15 @@ export class CardComp extends CCComp {
this.node.setPosition(this.restPosition.x, this.restPosition.y, this.restPosition.z);
// 渲染参数动态升级卡SpecialUpgrade + target_hero_uuid按英雄卡样式渲染
// 使用 target_hero_uuid 对应的英雄信息(名称/AP/HP/动画),等级显示为目标等级。
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_uuid;
const renderType: CardType = isUpgradeCard ? CardType.Hero : this.card_type;
const renderUuid: number = isUpgradeCard ? (this.cardData.target_hero_uuid as number) : this.card_uuid;
const renderKind: CKind = isUpgradeCard ? CKind.Hero : this.cardData.kind;
// ---- 卡牌种类标识(近战 / 远程 / 辅助等) ----
const kindName = CKind[this.cardData.kind];
const kindName = CKind[renderKind];
if (this.BG_node) {
const bgLv = this.cardData.base_pool_lv ?? this.cardData.pool_lv;
@@ -714,8 +722,8 @@ export class CardComp extends CCComp {
if (widget) widget.updateAlignment();
}
if (this.card_type === CardType.Hero) {
const hero = HeroInfo[this.card_uuid];
if (renderType === CardType.Hero) {
const hero = HeroInfo[renderUuid];
const heroLv = Math.max(1, this.cardData.hero_lv ?? hero?.lv ?? 1);
this.setLabel(this.name_node, `${hero?.name || ""}`);
if (this.lvl_node) {
@@ -733,27 +741,14 @@ export class CardComp extends CCComp {
if (this.info_node) this.info_node.active = false;
} else {
if (this.lvl_node) this.lvl_node.node.active = false;
// 动态升级卡:显示对应英雄名 + "升级"后缀 + 目标等级
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
const targetLv = Math.max(1, Math.floor(this.cardData.hero_lv ?? 1));
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
if (this.info_node) {
this.info_node.active = true;
this.setLabel(this.info_node, `升至 Lv.${targetLv}`);
}
} else {
// 特殊卡(升级 / 刷新):显示卡名 + 品质后缀 + 描述
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
if (this.info_node) {
this.info_node.active = true;
this.setLabel(this.info_node, specialCard?.info || "");
}
// 特殊卡(刷新):显示卡名 + 品质后缀 + 描述
const specialCard = SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
if (this.info_node) {
this.info_node.active = true;
this.setLabel(this.info_node, specialCard?.info || "");
}
this.ap_node.active = false;
this.hp_node.active = false;
@@ -770,10 +765,10 @@ export class CardComp extends CCComp {
// ---- 图标 ----
const iconNode = this.icon_node as Node;
if (this.card_type === CardType.Hero) {
if (renderType === CardType.Hero) {
iconNode.setScale(new Vec3(-1.5, 1.5, 1));
// 英雄卡使用 AnimationClip加载 idle 动画
this.updateHeroAnimation(iconNode, this.card_uuid, this.iconVisualToken);
// 英雄卡 / 升级卡使用 AnimationClip加载 idle 动画
this.updateHeroAnimation(iconNode, renderUuid, this.iconVisualToken);
return;
}
iconNode.setScale(new Vec3(1, 1, 1));

View File

@@ -151,7 +151,12 @@ export class MissionCardComp extends CCComp {
/** 卡牌面板收起态缩放scale=0 隐藏) */
private cardsHideScale: Vec3 = new Vec3(0, 0, 1);
/** 卡牌原始定位点 */
private cardsPos = [-240, 0, 240]
private cardsPos = [-220, 0, 220]
/**
* 必出升级卡的轮询索引:每次刷新按场上可升级英雄 UUID 升序轮询选 1 张。
* 每次 buildDrawCards 取模更新,保证多次刷新时升级卡依次切换。
*/
private upgradeRotationIndex: number = 0;
// ======================== 生命周期 ========================
@@ -728,17 +733,12 @@ export class MissionCardComp extends CCComp {
/**
* 构建本次抽卡结果,保证最终可分发 3 条数据。
*
* 抽卡池构成
* - 动态升级卡:扫描场上存活英雄,每个 UUID 至多生成一张升级卡
* (已达到 HERO_MAX_LV 的英雄不出卡)。同 UUID 只出一张,避免重复
* - 基础英雄卡:所有英雄的 lv1 卡
* - 刷新功能卡SpecialRefresh 卡。
* 抽卡规则
* 1. **必出规则**:当场上有可升级英雄,每次刷新必出 1 张升级卡,并放在返回数组首位(对应卡池最左侧槽位)。
* 多次刷新时,按场上英雄 target_hero_uuid 升序循环切换升级卡upgradeRotationIndex
* 2. 其余 2 张从混合池(剩余升级卡 + 基础英雄卡)按权重抽取
*
* 三类卡按 weight 权重混合抽 3 张,且升级卡之间 unique 去重
*
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,
* 不再抽取基础英雄卡和刷新卡,只抽取场上已有英雄的升级卡
* (若全部已满级则降级回混合池,避免卡池为空)。
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,全部出升级卡(若全部已满级则降级回混合池)
*/
private buildDrawCards(): CardConfig[] {
const upgradeCards = this.buildHeroUpgradeCards();
@@ -758,21 +758,42 @@ export class MissionCardComp extends CCComp {
}
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
const refreshCards: CardConfig[] = Object.values(SpecialRefreshCardList);
const mixedPool: CardConfig[] = [...upgradeCards, ...heroCards, ...refreshCards];
// 英雄池只刷英雄卡(含动态升级卡),不再混入刷新功能卡
const mixedPool: CardConfig[] = [...upgradeCards, ...heroCards];
if (mixedPool.length === 0) return [];
const picked = this.pickMixedCards(mixedPool, 3, upgradeCards);
if (picked.length >= 3) return picked.slice(0, 3);
const result: CardConfig[] = [];
const usedUpgradeTargets = new Set<number>();
/** 兜底:不足 3 张时循环补齐 */
const filled = [...picked];
while (filled.length < 3) {
if (mixedPool.length === 0) break;
filled.push(mixedPool[filled.length % mixedPool.length]);
// 必出 1 张升级卡,按 UUID 升序轮询,放在返回数组首位(对应最左侧卡槽)
if (upgradeCards.length > 0) {
const idx = this.upgradeRotationIndex % upgradeCards.length;
this.upgradeRotationIndex = (this.upgradeRotationIndex + 1) % upgradeCards.length;
const mandatory = upgradeCards[idx];
result.push(mandatory);
usedUpgradeTargets.add(mandatory.target_hero_uuid ?? 0);
}
return filled;
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_uuid
const remainingCount = 3 - result.length;
if (remainingCount > 0) {
const remainingPool = mixedPool.filter(c => {
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) {
return !usedUpgradeTargets.has(c.target_hero_uuid);
}
return true;
});
const rest = this.pickMixedCards(remainingPool, remainingCount, upgradeCards);
result.push(...rest);
}
// 兜底:不足 3 张时循环补齐
while (result.length < 3) {
if (mixedPool.length === 0) break;
result.push(mixedPool[result.length % mixedPool.length]);
}
return result.slice(0, 3);
}
/**
@@ -850,6 +871,8 @@ export class MissionCardComp extends CCComp {
hero_lv: lv + 1,
});
});
// 按 target_hero_uuid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
result.sort((a, b) => (a.target_hero_uuid ?? 0) - (b.target_hero_uuid ?? 0));
return result;
}