From db98b607373753e9895e4caa926580ed501d8c18 Mon Sep 17 00:00:00 2001 From: pan Date: Wed, 12 Aug 2026 15:10:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(game):=20=E8=B0=83=E6=95=B4=E8=8B=B1?= =?UTF-8?q?=E9=9B=84=E6=9C=80=E5=A4=A7=E6=95=B0=E9=87=8F=E4=B8=8E=E5=90=88?= =?UTF-8?q?=E6=88=90=E5=8D=87=E7=BA=A7=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将英雄最大数量从3提升至10 2. 重构英雄等级上限逻辑,改为从配置项读取 3. 新增同uuid英雄二合一合成升级机制,等级封顶为配置上限 4. 优化召唤校验逻辑,支持同uuid英雄合成并提示等级上限 --- assets/script/game/common/config/GameSet.ts | 2 +- assets/script/game/hero/Hero.ts | 4 +- assets/script/game/map/MissionHeroComp.ts | 106 ++++++++++++++++---- 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/assets/script/game/common/config/GameSet.ts b/assets/script/game/common/config/GameSet.ts index e38c319a..fbb35314 100644 --- a/assets/script/game/common/config/GameSet.ts +++ b/assets/script/game/common/config/GameSet.ts @@ -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 级封顶,超出等级只长属性) */ diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index f486df18..40b66a0b 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -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 // 根据配置路径加载英雄预制体 diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index 43b004f0..c1f1bd1b 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -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,10 +155,14 @@ 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(`该英雄已召唤,只能通过升级卡升级`); - return; + 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 }); @@ -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); 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) { - this.addHero(uuid, hero_lv, card_lv); + 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 { + return new Promise((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(); + }); + }); }