refactor(card): 升级动态升级卡的英雄绑定逻辑

将动态升级卡从绑定英雄UUID改为绑定实体EID,适配ECS实体管理模式:
1. 更新CardConfig定义,替换target_hero_uuid为target_hero_eid
2. 新增EID反查UUID的工具方法,适配渲染和交互逻辑
3. 修改抽卡、卡牌点击和使用的全流程逻辑
4. 优化升级卡生成和匹配的稳定性
This commit is contained in:
panFD
2026-07-21 00:03:44 +08:00
parent 48e0ab06c5
commit 8818ec9c3f
4 changed files with 73 additions and 66 deletions

View File

@@ -110,11 +110,11 @@ export interface CardConfig {
trigger_limit?: number;
/**
* 动态升级卡专属:目标英雄 UUID
* 动态升级卡专属:目标英雄实体 eid运行时唯一
* 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。
* 使用该卡时精确升级场上对应 UUID 的英雄。
* 使用该卡时精确升级场上对应 eid 的英雄实体
*/
target_hero_uuid?: number;
target_hero_eid?: number;
}
/** 升级卡折扣表(已废弃,保留以兼容历史引用) */
@@ -274,7 +274,7 @@ export enum SpecialRefreshHeroType {
export interface SpecialUpgradeCardConfig extends CardConfig {
name: string
info: string
/** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_uuid 对应的英雄决定) */
/** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_eid 对应的英雄决定) */
currentLv: number
/** 模板字段targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1 */
targetLv: number
@@ -292,7 +292,7 @@ export interface SpecialRefreshCardConfig extends CardConfig {
* 升级卡模板。
* 仅有 7001 一条记录作为动态升级卡的基础配置cost/weight/name/info
* 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig
* 通过 target_hero_uuid 字段绑定具体英雄。
* 通过 target_hero_eid 字段绑定具体英雄实体
*/
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: {

View File

@@ -526,9 +526,12 @@ export class CardComp extends CCComp {
this.showCallBtn();
oops.message.dispatchEvent(GameEvent.CardSelected, this);
// 英雄卡 / 升级卡打开信息面板(点击替代长按)
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_uuid;
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_eid;
if (this.card_type === CardType.Hero || isUpgradeCard) {
const heroUuid = isUpgradeCard ? (this.cardData!.target_hero_uuid as number) : this.card_uuid;
// 升级卡通过 eid 反查实体得到 hero_uuid若实体已不存在则降级为本卡 uuid
const heroUuid = isUpgradeCard
? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid)
: 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);
@@ -536,6 +539,20 @@ export class CardComp extends CCComp {
}
}
/**
* 通过 eid 反查场上英雄实体的 hero_uuid。
* 用于动态升级卡渲染:升级卡绑定的是 eid但显示需要 HeroInfo[uuid]。
* @returns 找到则返回 hero_uuid找不到实体已销毁等返回 null
*/
private queryHeroUuidByEid(eid: number): number | null {
if (!eid) return null;
const entity = ecs.getEntityByEid(eid);
if (!entity) return null;
const model = entity.get(HeroAttrsComp);
if (!model) return null;
return model.hero_uuid ?? null;
}
/**
* 其他卡牌被点击时联动隐藏本卡 call_btn。
* @param event 事件名ListenerFunc 约定的第一个参数)
@@ -639,11 +656,14 @@ 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;
// 渲染参数动态升级卡SpecialUpgrade + target_hero_eid按英雄卡样式渲染
// 通过 target_hero_eid 反查实体得到 hero_uuid再用 HeroInfo 渲染名称/AP/HP/动画
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_eid;
const renderType: CardType = isUpgradeCard ? CardType.Hero : this.card_type;
const renderUuid: number = isUpgradeCard ? (this.cardData.target_hero_uuid as number) : this.card_uuid;
// 升级卡通过 eid 反查实体得到 hero_uuid若实体已不存在则降级为本卡 uuid
const renderUuid: number = isUpgradeCard
? (this.queryHeroUuidByEid(this.cardData.target_hero_eid as number) ?? this.card_uuid)
: this.card_uuid;
const renderKind: CKind = isUpgradeCard ? CKind.Hero : this.cardData.kind;
// ---- 卡牌种类标识(近战 / 远程 / 辅助等) ----

View File

@@ -246,9 +246,10 @@ export class CardLiteComp extends CCComp {
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
} 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];
// 动态升级卡:显示对应英雄名 + "升级"后缀(注意:升级卡绑定的是 target_hero_eid 而非 uuid
// 此轻量列表场景通常不展示升级卡;为保持渲染健壮性,回退到本卡 uuid 查 HeroInfo
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_eid) {
const targetHero = HeroInfo[this.card_uuid];
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
} else {
const specialCard = this.card_type === CardType.SpecialUpgrade

View File

@@ -540,8 +540,8 @@ export class MissionCardComp extends CCComp {
/**
* 使用特殊卡事件回调:
* - SpecialUpgrade按卡牌携带的 target_hero_uuid 精确升级场上对应英雄。
* target_hero_uuid 缺失时回退为随机升级一个可升级的英雄
* - SpecialUpgrade按卡牌携带的 target_hero_eid 精确升级场上对应英雄实体
* target_hero_eid 缺失或实体不存在时升级失败
* - SpecialRefresh按英雄类型重新抽取英雄卡。
*/
private onUseSpecialCard(event: string, args: any) {
@@ -553,8 +553,8 @@ export class MissionCardComp extends CCComp {
if (type === CardType.SpecialUpgrade) {
const template = SpecialUpgradeCardList[uuid];
if (!template) return;
const targetHeroUuid = Number(payload?.target_hero_uuid ?? 0);
success = this.tryUpgradeHeroByUuid(targetHeroUuid);
const targetHeroEid = Number(payload?.target_hero_eid ?? 0);
success = this.tryUpgradeHeroByEid(targetHeroEid);
if (!success) {
oops.gui.toast(`场上没有可升级的英雄`);
}
@@ -735,7 +735,7 @@ export class MissionCardComp extends CCComp {
*
* 抽卡规则:
* 1. **必出规则**:当场上有可升级英雄时,每次刷新必出 1 张升级卡,并放在返回数组首位(对应卡池最左侧槽位)。
* 多次刷新时,按场上英雄 target_hero_uuid 升序循环切换升级卡upgradeRotationIndex
* 多次刷新时,按场上英雄 target_hero_eid 升序循环切换升级卡upgradeRotationIndex
* 2. 其余 2 张从混合池(剩余升级卡 + 基础英雄卡)按权重抽取。
*
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,全部出升级卡(若全部已满级则降级回混合池)。
@@ -772,15 +772,15 @@ export class MissionCardComp extends CCComp {
this.upgradeRotationIndex = (this.upgradeRotationIndex + 1) % upgradeCards.length;
const mandatory = upgradeCards[idx];
result.push(mandatory);
usedUpgradeTargets.add(mandatory.target_hero_uuid ?? 0);
usedUpgradeTargets.add(mandatory.target_hero_eid ?? 0);
}
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_uuid
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_eid
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);
if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
return !usedUpgradeTargets.has(c.target_hero_eid);
}
return true;
});
@@ -798,7 +798,7 @@ export class MissionCardComp extends CCComp {
/**
* 从混合池中按权重抽取 n 张卡。
* 升级卡之间强制 unique同一个 target_hero_uuid 只能出现一次)。
* 升级卡之间强制 unique同一个 target_hero_eid 只能出现一次)。
*/
private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] {
if (pool.length === 0 || count <= 0) return [];
@@ -807,8 +807,8 @@ export class MissionCardComp extends CCComp {
while (selected.length < count) {
const available = pool.filter(c => {
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) {
return !usedUpgradeTargets.has(c.target_hero_uuid);
if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
return !usedUpgradeTargets.has(c.target_hero_eid);
}
return true;
});
@@ -816,8 +816,8 @@ export class MissionCardComp extends CCComp {
const pick = this.weightedPick(available);
if (!pick) break;
selected.push(pick);
if (pick.type === CardType.SpecialUpgrade && pick.target_hero_uuid) {
usedUpgradeTargets.add(pick.target_hero_uuid);
if (pick.type === CardType.SpecialUpgrade && pick.target_hero_eid) {
usedUpgradeTargets.add(pick.target_hero_eid);
}
}
return selected;
@@ -836,9 +836,9 @@ export class MissionCardComp extends CCComp {
}
/**
* 扫描场上存活英雄,为每个 UUID 至多生成一张升级卡
* 扫描场上存活英雄,为每个英雄实体生成一张升级卡(按 eid 绑定)
* - 已达 HERO_MAX_LV 的英雄不出卡。
* - 同 UUID 多个英雄只生成一张(取等级最低的那个作为升级目标)。
* - 同 eid 只生成一张eid 本就唯一,不会重复)。
* - cost = 模板 cost + (当前等级 - 1) * BASE_COST等级越高升级越贵。
*/
private buildHeroUpgradeCards(): CardConfig[] {
@@ -848,31 +848,22 @@ export class MissionCardComp extends CCComp {
const actors = this.queryAliveHeroActors();
if (actors.length === 0) return [];
// 同 UUID 取等级最低的英雄
const heroMap = new Map<number, { uuid: number, lv: number }>();
for (const actor of actors) {
const uuid = actor.model.hero_uuid;
const lv = actor.model.lv;
const existing = heroMap.get(uuid);
if (!existing || lv < existing.lv) {
heroMap.set(uuid, { uuid, lv });
}
}
const result: CardConfig[] = [];
heroMap.forEach(({ uuid, lv }) => {
if (lv >= FightSet.HERO_MAX_LV) return; // 已达上限不再出升级卡
for (const actor of actors) {
const eid = actor.eid;
const lv = actor.model.lv;
if (lv >= FightSet.HERO_MAX_LV) continue; // 已达上限不再出升级卡
const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST;
result.push({
...template,
cost: dynamicCost,
weight: template.weight,
target_hero_uuid: uuid,
target_hero_eid: eid,
hero_lv: lv + 1,
});
});
// 按 target_hero_uuid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
result.sort((a, b) => (a.target_hero_uuid ?? 0) - (b.target_hero_uuid ?? 0));
}
// 按 target_hero_eid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
result.sort((a, b) => (a.target_hero_eid ?? 0) - (b.target_hero_eid ?? 0));
return result;
}
@@ -1077,39 +1068,34 @@ export class MissionCardComp extends CCComp {
return count;
}
private queryAliveHeroActors(): Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> {
const actors: Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> = [];
private queryAliveHeroActors(): Array<{ eid: number, model: HeroAttrsComp, view: HeroViewComp | null }> {
const actors: Array<{ eid: number, model: HeroAttrsComp, view: HeroViewComp | null }> = [];
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (!model) return;
if (model.fac !== FacSet.HERO) return;
if (model.is_dead) return;
const view = entity.get(HeroViewComp);
actors.push({ model, view });
actors.push({ eid: entity.eid, model, view });
});
return actors;
}
/**
* 按英雄 UUID 精确升级一个存活英雄
* 若同 UUID 多个英雄,取等级最低且未达上限的;若都达上限则失败。
* 按 eid 精确升级场上对应英雄实体
*
* @param heroUuid 要升级的英雄 UUID
* @param heroEid 要升级的英雄实体 eid
* @returns true = 升级成功
*/
private tryUpgradeHeroByUuid(heroUuid: number): boolean {
if (!heroUuid) return false;
const candidates = this.queryAliveHeroActors().filter(item =>
item.model.hero_uuid === heroUuid && item.model.lv < FightSet.HERO_MAX_LV
);
if (candidates.length === 0) return false;
// 取等级最低的(先升级低等级英雄)
candidates.sort((a, b) => a.model.lv - b.model.lv);
const target = candidates[0];
const nextLv = Math.min(FightSet.HERO_MAX_LV, target.model.lv + 1);
this.applyHeroLevel(target.model, nextLv);
if (target.view) {
target.view.playBuff("buff_lvup");
private tryUpgradeHeroByEid(heroEid: number): boolean {
if (!heroEid) return false;
const actor = this.queryAliveHeroActors().find(item => item.eid === heroEid);
if (!actor) return false;
if (actor.model.lv >= FightSet.HERO_MAX_LV) return false;
const nextLv = Math.min(FightSet.HERO_MAX_LV, actor.model.lv + 1);
this.applyHeroLevel(actor.model, nextLv);
if (actor.view) {
actor.view.playBuff("buff_lvup");
}
return true;
}