Compare commits
2 Commits
ccce8a11ea
...
a5dc1ef540
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5dc1ef540 | ||
|
|
db98b60737 |
File diff suppressed because it is too large
Load Diff
@@ -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 级封顶,超出等级只长属性) */
|
||||
|
||||
@@ -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
|
||||
// 根据配置路径加载英雄预制体
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
*
|
||||
* 关键设计:
|
||||
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
|
||||
* - handleSingleSummon() 仅负责生成英雄;英雄升级由升级卡系统(MissionCardComp)独立处理。
|
||||
* - handleSingleSummon() 负责生成英雄;同 uuid 重复召唤走二合一合成升级。
|
||||
*
|
||||
* 历史:
|
||||
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。
|
||||
* 英雄等级提升现在仅通过升级卡(SpecialUpgrade)实现。
|
||||
* 合成规则:
|
||||
* 场上存在同 uuid 英雄时再次召唤触发二合一:
|
||||
* 旧英雄移动合并销毁,新英雄以 **两者最高等级 + 1** 落地(封顶 HERO_MAX_LV),
|
||||
* 等级已达上限时拒绝召唤。英雄也可通过升级卡(SpecialUpgrade)升级。
|
||||
*
|
||||
* 依赖:
|
||||
* - Hero(hero/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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user