2 Commits

Author SHA1 Message Date
pan
a5dc1ef540 refactor(missionCard): 重构抽卡逻辑与按钮命名
1.  重命名抽卡按钮相关变量与注释为刷新卡池
2.  调整抽卡规则:不再过滤已召唤英雄,支持同英雄二合一合成
3.  新增招募计数循环机制:每4次招募触发一次三选一
4.  添加近战保底逻辑,全远程阵容时提升近战卡权重
5.  移除冗余的已召唤英雄过滤逻辑
2026-08-12 15:31:27 +08:00
pan
db98b60737 feat(game): 调整英雄最大数量与合成升级规则
1. 将英雄最大数量从3提升至10
2. 重构英雄等级上限逻辑,改为从配置项读取
3. 新增同uuid英雄二合一合成升级机制,等级封顶为配置上限
4. 优化召唤校验逻辑,支持同uuid英雄合成并提示等级上限
2026-08-12 15:10:29 +08:00
5 changed files with 1225 additions and 1081 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ export enum FightSet {
CRIT_DAMAGE = 50,//暴击伤害
MORE_RC = 10,//更多次数 广告获取的次数
HEARTPOS = -320,//基地位置
HERO_MAX_NUM = 3,//英雄最大数量
HERO_MAX_NUM = 10,//英雄最大数量
/** 英雄可升级到的最高等级(通过升级卡提升) */
HERO_MAX_LV = 6,
/** 英雄技能等级上限(无限升级下技能 5 级封顶,超出等级只长属性) */

View File

@@ -76,8 +76,8 @@ export class Hero extends ecs.Entity {
load(pos: Vec3 = Vec3.ZERO, scale: number = 1, uuid: number = 1001, dropToY: number = pos.y, hero_lv: number = 1, card_lv: number = 1, posIndex: number = -1) {
// 英雄始终朝右,表现缩放固定为正向
scale = 1
// 英雄等级在当前规则下上限为 3避免超配表范围
if (hero_lv > 3) hero_lv = 3
// 英雄等级上限以配置为准,合成可升到 HERO_MAX_LV
if (hero_lv > FightSet.HERO_MAX_LV) hero_lv = FightSet.HERO_MAX_LV
// 英雄尺寸随等级做轻量放大,强化成长反馈
let size = 1.15
// 根据配置路径加载英雄预制体

View File

@@ -5,7 +5,7 @@
* 职责:
* 1. **卡牌分发管理** —— 从卡池抽取 3 张英雄卡,分发到 3 个 CHeroComp 槽位。
* 抽卡规则:从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复;
* 场上已召唤英雄不再刷出
* 不过滤已召唤英雄,抽到同 uuid 可触发二合一合成升级
* 2. **金币费用管理** —— 抽卡费用refreshCost的扣除。
* 3. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许再召唤英雄。
* 4. **场上英雄信息面板HInfoComp 列表)同步** ——
@@ -160,9 +160,9 @@ export class MissionCardComp extends CCComp {
/** 药品刷新按钮节点 */
@property(Node)
shop_refresh: Node = null!;
/** 抽卡(刷新)按钮节点 */
/** 刷新卡池按钮节点 */
@property(Node)
cards_chou: Node = null!
cards_refresh: Node = null!
/** 金币显示节点(含 icon + num 子节点) */
@property(Node)
@@ -192,6 +192,13 @@ export class MissionCardComp extends CCComp {
private itemCardComps: ItemListComp[] = [];
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
private hasCalledHero: boolean = false;
/**
* 招募计数:每次点击招募 +1。
* 节奏为「每 4 次一循环」:前 3 次直接随机召唤,第 4 次走三选一。
*/
private recruitCount: number = 0;
/** 全远程阵容时近战卡的权重放大倍数(近战保底) */
private static readonly MELEE_PITY_WEIGHT_MUL = 3;
// ======================== 面板划出状态 ========================
@@ -271,10 +278,10 @@ export class MissionCardComp extends CCComp {
// 清理新手按钮指引的调度与动画,防止回调泄漏
this.stopButtonGuide();
this.unscheduleAllCallbacks();
if (this.cards_chou && this.cards_chou.isValid) {
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_chou.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_chou.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
if (this.cards_refresh && this.cards_refresh.isValid) {
this.cards_refresh.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_refresh.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_refresh.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
}
if (this.showHeros && this.showHeros.isValid) {
this.showHeros.off(NodeEventType.TOUCH_END, this.onShowHerosClick, this);
@@ -324,7 +331,7 @@ export class MissionCardComp extends CCComp {
this.layoutCardSlots();
this.clearAllCards();
this.resetButtonScale(this.cards_chou);
this.resetButtonScale(this.cards_refresh);
this.resetPanelOpenCosts();
this.updateCoinAndCostUI();
this.updateHeroNumUI(false, false);
@@ -399,7 +406,7 @@ export class MissionCardComp extends CCComp {
* 绑定所有事件监听:
* - 节点级事件MissionStart / MissionEnd / FightStart
* - 全局消息CoinAdd / MasterCalled / HeroDead / UseHeroCard / UseSpecialCard
* - 按钮触控:抽卡cards_chou
* - 按钮触控:刷新卡池cards_refresh
*/
private bindEvents() {
/** 生命周期事件(节点级) */
@@ -422,9 +429,9 @@ export class MissionCardComp extends CCComp {
oops.message.on(GameEvent.HeroUpgrade, this.onHeroUpgrade, this);
/** 按钮触控事件:抽卡 */
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_chou?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_chou?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
this.cards_refresh?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_refresh?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
/** 隐藏卡牌面板按钮 */
@@ -512,7 +519,7 @@ export class MissionCardComp extends CCComp {
let targetNode: Node | null = null;
switch (type) {
case "refresh_coin":
targetNode = this.cards_chou;
targetNode = this.cards_refresh;
break;
case "buy_coin":
targetNode = this.coins_node;
@@ -686,10 +693,10 @@ export class MissionCardComp extends CCComp {
oops.message.off(GameEvent.CardUsed, this.onCardUsed, this);
oops.message.off(GameEvent.HeroBoxEmptyClick, this.onHeroBoxEmptyClick, this);
oops.message.off(GameEvent.HeroUpgrade, this.onHeroUpgrade, this);
if (this.cards_chou && this.cards_chou.isValid) {
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_chou.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_chou.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
if (this.cards_refresh && this.cards_refresh.isValid) {
this.cards_refresh.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_refresh.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_refresh.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
}
if (this.showHeros && this.showHeros.isValid) {
this.showHeros.off(NodeEventType.TOUCH_END, this.onShowHerosClick, this);
@@ -910,16 +917,16 @@ export class MissionCardComp extends CCComp {
/** 抽卡按钮按下反馈 */
private onDrawTouchStart() {
this.playButtonPressAnim(this.cards_chou);
this.playButtonPressAnim(this.cards_refresh);
}
/** 抽卡按钮释放 → 执行抽卡逻辑 */
private onDrawTouchEnd() {
oops.audio.playEffect("music/button");
this.playButtonClickAnim(this.cards_chou, () => this.onClickDraw());
this.playButtonClickAnim(this.cards_refresh, () => this.onClickDraw());
}
/** 抽卡按钮取消 → 恢复缩放 */
private onDrawTouchCancel() {
this.playButtonResetAnim(this.cards_chou);
this.playButtonResetAnim(this.cards_refresh);
}
// ======================== 新手按钮指引 ========================
@@ -1214,25 +1221,40 @@ export class MissionCardComp extends CCComp {
}
/**
* 英雄面板按钮回调
* 英雄面板herosPanNode为常驻基准面板固定不动
* 点击按钮收起装备/技能/药品覆盖面板并显示英雄抽卡面板cardPanNode
* 英雄招募按钮(每 4 次一循环)
* - 前 3 次:扣面板开启费后直接随机召唤 1 个英雄(不开面板,无需玩家选)。
* - 第 4 次:扣费后展开三选一面板(缩放 0→1玩家选 1
* 满员HERO_MAX_NUM时气泡提示并拦截不扣费、不计数。
*/
private onShowHerosClick() {
oops.audio.playEffect("music/button");
// 已展开则不重复开启(不重复扣费)
if (this.isCardPanelShown) return;
// 英雄槽位已满:气泡提示并中止,不再展开三选一,也不扣开启费用
// 满员拦截:随机招募与三选一都禁止
if (this.getAliveHeroCount() >= this.getMissionHeroMaxNum()) {
this.showSmallTip("panel_hero_full", "英雄槽已满");
return;
}
// 开启英雄抽卡面板需支付金币,金币不足则提示并中止
// 随机招募与三选一都需支付面板开启费,金币不足则提示并中止
if (!this.tryPayPanelOpenCost(0)) return;
// 收起装备/技能/药品覆盖面板
// 先计数再按节奏分支count % 4 == 0 展开三选一,其余直接随机召唤
this.recruitCount++;
const isPickOneOfThree = (this.recruitCount % 4) === 0;
mLogger.log(this.debugMode, "MissionCardComp", "hero recruit", {
recruitCount: this.recruitCount,
mode: isPickOneOfThree ? "pick_one_of_three" : "random_summon"
});
// 收起装备/技能/药品覆盖面板,回到英雄面板
this.slideToPanel(0);
// 显示英雄抽卡面板(缩放 0→1并点亮按钮 active 高亮)
if (isPickOneOfThree) {
// 第 4 次:展开英雄抽卡面板(缩放 0→1点亮按钮 active 高亮)
this.showCardsPanel();
} else {
// 前 3 次:直接随机召唤,不开面板
this.randomSummonOne();
}
}
// ======================== 英雄信息盒herosBoxNode ========================
@@ -1752,10 +1774,10 @@ export class MissionCardComp extends CCComp {
// ======================== 核心业务:抽卡 ========================
/**
* 抽卡按钮核心逻辑:
* 1. 检查金币是否足够 → 不够则 toast 提示。
* 刷新卡池按钮核心逻辑:
* 1. 检查金币/刷新石是否足够 → 不够则 toast 提示。
* 2. 扣除费用、播放金币动画。
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位。
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位(三选一展示)
*/
private onClickDraw() {
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
@@ -1770,7 +1792,7 @@ export class MissionCardComp extends CCComp {
return;
}
mLogger.log(this.debugMode, "MissionCardComp", "click draw", {
mLogger.log(this.debugMode, "MissionCardComp", "click refresh", {
cost,
leftCoin: MissionEconomy.getCoin()
});
@@ -1779,6 +1801,63 @@ export class MissionCardComp extends CCComp {
this.dispatchCardsToSlots(cards);
}
/**
* 直接随机召唤 1 个英雄:
* - 全英雄卡池按权重随机(不过滤已召唤英雄,配合二合一合成)。
* - 近战保底:场上已有英雄且全部为远程/中程(无近战)时,近战卡权重放大。
*/
private randomSummonOne() {
const pool = this.buildRandomSummonPool();
if (pool.length === 0) return;
const pick = this.weightedPick(pool);
if (!pick) return;
mLogger.log(this.debugMode, "MissionCardComp", "random summon", { uuid: pick.uuid });
oops.message.dispatchEvent(GameEvent.CallHero, {
uuid: pick.uuid,
hero_lv: 1,
card_lv: pick.card_lv ?? 1
});
}
/**
* 构建随机召唤卡池:全英雄卡,必要时按近战保底放大近战权重。
* @returns 带运行时权重的卡配置数组weight 已按保底调整)
*/
private buildRandomSummonPool(): CardConfig[] {
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
if (heroCards.length === 0) return [];
// 全远程判定:有存活英雄但没有任何近战 → 触发放大近战权重
const meleeMul = this.isAllRangedFormation()
? MissionCardComp.MELEE_PITY_WEIGHT_MUL
: 1;
if (meleeMul === 1) return heroCards;
return heroCards.map(c => {
const hero = HeroInfo[c.uuid];
const isMelee = hero && hero.type === HType.Melee;
// 复制一份避免污染卡池原始 weight
return isMelee ? { ...c, weight: (c.weight ?? 0) * meleeMul } : c;
});
}
/**
* 判断当前场上是否为「全远程阵容」:
* 有至少 1 个存活英雄且没有任何近战Melee英雄。
*/
private isAllRangedFormation(): boolean {
let heroCount = 0;
let meleeCount = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
heroCount++;
if (model.type === HType.Melee) meleeCount++;
});
return heroCount > 0 && meleeCount === 0;
}
// ======================== 阶段切换 ========================
/** 缓存卡牌面板的基准缩放值(从场景初始状态读取,仅缓存一次) */
@@ -1852,15 +1931,13 @@ export class MissionCardComp extends CCComp {
*
* 规则:
* 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复。
* 2. 过滤掉场上已召唤英雄(已召唤的英雄不再刷出,仅通过升级卡升级
* 2. 过滤已召唤英雄:抽到同 uuid 可触发二合一合成升级。
* 3. 不足 3 张时循环补齐(允许重复)。
*
* 注:升级卡机制已移除,英雄卡池只出普通英雄卡。
*/
private buildDrawCards(): CardConfig[] {
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
const aliveHeroUuids = this.getAliveHeroUuids();
const availableHeroCards = heroCards.filter(c => !aliveHeroUuids.has(c.uuid));
const availableHeroCards = CardPoolList.filter(c => c.type === CardType.Hero);
if (availableHeroCards.length === 0) return [];
@@ -1903,12 +1980,10 @@ export class MissionCardComp extends CCComp {
heroType,
unique: true, // 保证一次刷新内的英雄卡不重复
});
// 过滤掉场上已召唤英雄的普通英雄卡(已召唤的英雄不再重复刷出)
const aliveHeroUuids = this.getAliveHeroUuids();
const available = cards.filter(c => !aliveHeroUuids.has(c.uuid));
if (available.length <= 0) return false;
// 过滤已召唤英雄:抽到同 uuid 可触发二合一合成升级
if (cards.length <= 0) return false;
this.layoutCardSlots();
this.dispatchCardsToSlots(available.slice(0, 3));
this.dispatchCardsToSlots(cards.slice(0, 3));
return true;
}
@@ -2029,7 +2104,7 @@ export class MissionCardComp extends CCComp {
private updateDrawCostUI() {
const stones = MissionEconomy.getRefreshStone();
const stoneCost = MissionEconomy.getRefreshStoneCost();
this.updateOneRefreshBtnUI(this.cards_chou, stones, stoneCost);
this.updateOneRefreshBtnUI(this.cards_refresh, stones, stoneCost);
this.updateOneRefreshBtnUI(this.equip_refresh, stones, stoneCost);
this.updateOneRefreshBtnUI(this.shop_refresh, stones, stoneCost);
this.updateOneRefreshBtnUI(this.skill_refresh, stones, stoneCost);
@@ -2163,22 +2238,6 @@ export class MissionCardComp extends CCComp {
return actors;
}
/**
* 获取场上所有存活英雄的 hero_uuid 集合。
* 用于抽卡时过滤:已召唤的英雄不再从普通英雄卡池中刷出。
*/
private getAliveHeroUuids(): Set<number> {
const uuids = new Set<number>();
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;
uuids.add(model.hero_uuid);
});
return uuids;
}
/**
* 按 eid 精确升级场上对应英雄实体。
*
@@ -2343,7 +2402,7 @@ export class MissionCardComp extends CCComp {
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.resetButtonScale(this.cards_chou);
this.resetButtonScale(this.cards_refresh);
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
this.cardComps = [];

View File

@@ -8,11 +8,12 @@
*
* 关键设计:
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
* - handleSingleSummon() 负责生成英雄;英雄升级由升级卡系统MissionCardComp独立处理
* - handleSingleSummon() 负责生成英雄;同 uuid 重复召唤走二合一合成升级
*
* 历史
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。
* 英雄等级提升现在仅通过升级卡SpecialUpgrade实现。
* 合成规则
* 场上存在同 uuid 英雄时再次召唤触发二合一:
* 英雄移动合并销毁,新英雄以 **两者最高等级 + 1** 落地(封顶 HERO_MAX_LV
* 等级已达上限时拒绝召唤。英雄也可通过升级卡SpecialUpgrade升级。
*
* 依赖:
* - Herohero/Hero.ts—— 英雄 ECS 实体类
@@ -142,7 +143,8 @@ export class MissionHeroComp extends CCComp {
/**
* 召唤请求入口:
* 从事件参数中提取 uuid / hero_lv / card_lv放入串行队列。
* 防御:已召唤的英雄 uuid 拒绝重复召唤(只能通过升级卡升级)。
* 二合一:同 uuid 已召唤且等级未达上限时允许入队,由 handleSingleSummon 执行合成;
* 同 uuid 且等级已达上限时拒绝(只能通过升级卡继续提升之外无法再合成)。
*
* @param event 事件名
* @param args { uuid, hero_lv, card_lv }
@@ -153,11 +155,15 @@ export class MissionHeroComp extends CCComp {
const hero_lv = Math.max(1, Number(payload?.hero_lv ?? 1));
const card_lv = Math.max(1, Number(payload?.card_lv ?? 1));
// 防御:场上已有同 uuid 的存活英雄时,拒绝重复召唤
// 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝
if (this.isHeroAlreadySummoned(uuid)) {
oops.gui.toast(`该英雄已召唤,只能通过升级卡升级`);
const existing = this.findHeroByUuid(uuid);
const existingLv = existing ? (existing.get(HeroAttrsComp)?.lv ?? 1) : 1;
if (Math.max(existingLv, hero_lv) + 1 > FightSet.HERO_MAX_LV) {
oops.gui.toast(`该英雄已达等级上限,无法继续合成`);
return;
}
}
this.summon_queue.push({ uuid, hero_lv, card_lv });
this.processSummonQueue();
@@ -169,14 +175,23 @@ export class MissionHeroComp extends CCComp {
* @returns true 表示场上已有该英雄,不可重复召唤
*/
private isHeroAlreadySummoned(uuid: number): boolean {
let exists = false;
return this.findHeroByUuid(uuid) !== null;
}
/**
* 查找场上指定 uuid 的存活英雄实体。
* @param uuid 英雄模板 uuid
* @returns 命中的 Hero 实体,未命中返回 null
*/
private findHeroByUuid(uuid: number): Hero | null {
let found: Hero | null = null;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
if (exists) return;
if (found) return;
const model = entity.get(HeroAttrsComp);
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
if (model.hero_uuid === uuid) exists = true;
if (model.hero_uuid === uuid) found = entity as Hero;
});
return exists;
return found;
}
// ======================== 英雄生成 ========================
@@ -220,12 +235,27 @@ export class MissionHeroComp extends CCComp {
* @returns 创建的 Hero 实体
*/
private addHero(uuid: number = 1001, hero_lv: number = 1, card_lv: number = 1) {
return this.spawnHeroAt(uuid, hero_lv, card_lv, -1);
}
/**
* 在指定站位生成英雄:
* - posIndex < 0 时自动分配空位。
* - 计算出生点(空中)和落点(地面),播放掉落动画。
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param card_lv 卡牌等级(驱动 bg_node 颜色)
* @param posIndex 指定站位索引,-1 表示自动分配
* @returns 创建的 Hero 实体
*/
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number, posIndex: number) {
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const posIndex = this.pickPositionIndexForHero();
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
const finalPosIndex = posIndex >= 0 ? posIndex : this.pickPositionIndexForHero();
const landingPos = MissionHeroComp.HERO_POSITIONS[finalPosIndex];
let spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, posIndex);
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, finalPosIndex);
// 召唤完成后,派发事件以更新英雄面板
const model = hero.get(HeroAttrsComp);
@@ -275,14 +305,56 @@ export class MissionHeroComp extends CCComp {
}
/**
* 处理单次召唤:仅生成英雄,不再触发合成。
* 处理单次召唤:
* - 场上无同 uuid 英雄:直接生成新英雄。
* - 场上已有同 uuid 英雄:触发二合一,旧英雄合并销毁后生成等级 +1 的新英雄。
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param card_lv 卡牌等级
*/
private async handleSingleSummon(uuid: number, hero_lv: number, card_lv: number = 1) {
const existing = this.findHeroByUuid(uuid);
if (!existing) {
this.addHero(uuid, hero_lv, card_lv);
return;
}
const oldModel = existing.get(HeroAttrsComp);
const oldLv = oldModel?.lv ?? 1;
// 合成后等级 = 两者最高等级 + 1封顶 HERO_MAX_LV
const mergedLv = Math.min(FightSet.HERO_MAX_LV, Math.max(oldLv, hero_lv) + 1);
// 继承旧英雄站位,避免新英雄重新抢位导致阵型跳动
const posIndex = oldModel?.posIndex ?? -1;
await this.mergeHeroAndRespawn(existing, uuid, mergedLv, card_lv, posIndex);
}
/**
* 二合一合成流程:
* 1. 旧英雄播放移动合并动画并销毁(释放站位与实体)。
* 2. 在原站位生成 mergedLv 级新英雄并播放入场动画。
*
* @param oldHero 场上已有的同 uuid 英雄
* @param uuid 英雄 UUID
* @param mergedLv 合成后的英雄等级
* @param card_lv 卡牌等级
* @param posIndex 继承的站位索引(-1 表示重新分配)
*/
private mergeHeroAndRespawn(oldHero: Hero, uuid: number, mergedLv: number, card_lv: number, posIndex: number): Promise<void> {
return new Promise<void>((resolve) => {
// 以旧英雄节点位置作为合并目标点,保证动画收拢到原站位
const oldView = oldHero.get(HeroViewComp);
const birthPos = oldView?.node?.isValid
? oldView.node.getPosition()
: (posIndex >= 0 ? MissionHeroComp.HERO_POSITIONS[posIndex] : MissionHeroComp.HERO_POSITIONS[1]);
oldHero.mergeToBirthAndDestroy(birthPos, () => {
// 旧实体销毁后 posIndex 已释放,直接按原站位生成新英雄
this.spawnHeroAt(uuid, mergedLv, card_lv, posIndex);
resolve();
});
});
}