From 3060ee7df7f8493f70891da212c48e333c56c17d Mon Sep 17 00:00:00 2001 From: pan Date: Wed, 12 Aug 2026 16:15:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(hero):=20=E9=87=8D=E6=9E=84=E8=8B=B1?= =?UTF-8?q?=E9=9B=84=E6=AD=BB=E4=BA=A1=E4=B8=8E=E5=A4=8D=E6=B4=BB=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 统一英雄真实死亡流程:死亡时快照数据到全局列表,动画结束后统一销毁实体 2. 移除回合自动复活逻辑,改为由复活系统主动读取快照重新召唤 3. 修复近战追敌可能脱离战场的问题,增加推进距离上限 4. 重构死亡计数与全员存活检测逻辑,基于死亡快照列表而非实体状态 --- .../script/game/common/SingletonModuleComp.ts | 19 +++++++++ assets/script/game/hero/HeroAtkSystem.ts | 40 +++++++++++++++---- assets/script/game/hero/HeroViewComp.ts | 11 ++--- assets/script/game/hero/MoveComp.ts | 5 ++- assets/script/game/map/MissionComp.ts | 13 +++--- assets/script/game/map/MissionHeroComp.ts | 33 ++++++++------- 6 files changed, 79 insertions(+), 42 deletions(-) diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index 82b13142..245c089f 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -38,6 +38,23 @@ export interface CloudData { openid: string; data: GameDate; } + +/** + * 死亡英雄快照:英雄真实死亡时记录,供后续复活机制(重新召唤)消费。 + * 仅记录静态/成长数据,不含战斗内临时状态(buff、CD 等)。 + */ +export interface DeadHeroRecord { + /** 英雄模板 uuid(决定重新召唤哪个英雄) */ + uuid: number; + /** 死亡时的英雄等级(复活后继承) */ + lv: number; + /** 卡牌等级(驱动 bg_node 颜色) */ + card_lv: number; + /** 死亡时占用的站位索引(复活时优先归位,-1 表示未分配) */ + posIndex: number; + /** 死亡时的回合数(用于统计/按回合清理) */ + wave: number; +} /** 游戏模块 */ @ecs.register('SingletonModule') export class SingletonModuleComp extends ecs.Comp { @@ -60,6 +77,8 @@ export class SingletonModuleComp extends ecs.Comp { stop_mon_action: false, heroGrid: [-1, -1, -1, -1, -1, -1], monGrid: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], + /** 本局真实死亡的英雄快照列表,供后续复活机制消费;整局结束(MissionEnd)时清空 */ + dead_heroes: [] as DeadHeroRecord[], }; data: any = { openid: '', diff --git a/assets/script/game/hero/HeroAtkSystem.ts b/assets/script/game/hero/HeroAtkSystem.ts index 5b06dbbd..fe85d1ac 100644 --- a/assets/script/game/hero/HeroAtkSystem.ts +++ b/assets/script/game/hero/HeroAtkSystem.ts @@ -220,10 +220,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda // 检查死亡 if (TAttrsComp.hp <= 0) { - // 先触发死亡技能(如亡语),不管后续是否复活都应触发 + // 先触发死亡技能(如亡语),不管后续是否续命都应触发 this.triggerDeadSkills(target); - // 复活机制:如果玩家属性内的复活属性值>=1 则执行复活,原地50%血量复活 + // 濒死回血(revive 系):不是真正复活,而是血量归零瞬间消耗 1 次续命次数, + // 按比例回血继续战斗。次数每回合重置(见 MissionComp.healAllHeroes)。 let canRevive = false; let maxReviveCount = 0; let reviveHpPercent = 50; // 默认恢复50% @@ -338,16 +339,16 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda /** * 处理角色死亡 - * + * * 死亡处理流程: * 1. 标记死亡状态(is_dead = true) - * 2. 触发死亡事件(onDeath) - * 3. 记录调试信息(如启用调试模式) - * + * 2. 英雄阵营:快照死亡状态到 smc.mission.dead_heroes(供后续复活机制重新召唤) + * 3. 触发死亡事件(onDeath) + * * @param entity 死亡的实体 - * + * * @important 死亡状态一旦设置,该实体将不再处理新的伤害事件 - * 这确保了死亡逻辑的单一性和一致性 + * 英雄为真实死亡:视图动画结束后由 HeroViewComp.realDead 销毁实体 */ private doDead(entity: ecs.Entity): void { const TAttrsComp = entity.get(HeroAttrsComp); @@ -355,6 +356,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda TAttrsComp.is_dead = true; + // 英雄真实死亡:记录快照,供后续复活机制(重新召唤)消费 + if (TAttrsComp.fac === FacSet.HERO) { + this.recordDeadHero(TAttrsComp); + } + // 触发死亡事件 this.onDeath(entity); @@ -363,6 +369,24 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda } } + /** + * 快照死亡英雄的静态/成长数据到 smc.mission.dead_heroes。 + * 仅记录重新召唤所需字段,不含战斗内临时状态(buff、技能 CD 等)。 + * + * @param attrs 死亡英雄的属性组件 + */ + private recordDeadHero(attrs: HeroAttrsComp): void { + smc.mission.dead_heroes.push({ + uuid: attrs.hero_uuid, + lv: attrs.lv, + card_lv: attrs.card_lv, + posIndex: attrs.posIndex, + wave: Math.max(1, smc.vmdata?.mission_data?.level ?? 1), + }); + mLogger.log(this.debugMode, 'HeroAtkSystem', + ` 死亡英雄已记录: ${attrs.hero_name}(uuid=${attrs.hero_uuid}) lv=${attrs.lv} pos=${attrs.posIndex} wave=${smc.vmdata?.mission_data?.level}, 当前共 ${smc.mission.dead_heroes.length} 个`); + } + /** diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index 1abfb249..a1950a5e 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -516,17 +516,12 @@ export class HeroViewComp extends CCComp { let isHero = this.model.fac === FacSet.HERO; let dirX = isHero ? -800 : 800; - // 死亡往后飞出屏幕动画 + // 死亡往后飞出屏幕动画,结束后统一销毁实体 + // 英雄为真实死亡:快照已在 doDead 阶段写入 smc.mission.dead_heroes,此处直接销毁 tween(this.node) .by(0.5, { position: v3(dirX, 700, 0) }, { easing: "quadOut" }) .call(() => { - if (isHero) { - // 将英雄移到玩家看不到的墓地,留待下回合上场 - this.node.setPosition(v3(-2000, -2000, 0)); - } else { - // 动画结束后销毁怪物实体 - this.ent.destroy(); - } + this.ent.destroy(); }) .start(); } diff --git a/assets/script/game/hero/MoveComp.ts b/assets/script/game/hero/MoveComp.ts index 025c766f..3da8ab4b 100644 --- a/assets/script/game/hero/MoveComp.ts +++ b/assets/script/game/hero/MoveComp.ts @@ -223,7 +223,8 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate // 未贴近:向怪物方向推进(攻击范围内也继续移动),于接触距离处停下 const dir = enemyX > selfX ? 1 : -1; move.direction = dir; - const stopX = enemyX - dir * MoveSystem.MELEE_ENGAGE_X; + // 终止点取接触距离与推进上限的较小者,防止越过 300 继续追击 + const stopX = Math.min(enemyX - dir * MoveSystem.MELEE_ENGAGE_X, MoveSystem.HERO_ADVANCE_LIMIT_X); const speed = model.speed / 3; this.moveEntity(view, dir, speed, stopX); model.is_atking = false; @@ -290,6 +291,8 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate private static readonly FORMATION_STEP_X = 60; /** 近战贴近怪物的固定接触距离:小于攻击距离 dis,使英雄进攻击范围后仍继续贴近到此间距才停 */ private static readonly MELEE_ENGAGE_X = 60; + /** 英雄向敌人推进的最远终止点 X,防止近战追敌时无限右移脱离战场 */ + private static readonly HERO_ADVANCE_LIMIT_X = 300; /** * 计算某个槽位的目标 X。 diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index aee1c006..5a58b488 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -560,17 +560,15 @@ export class MissionComp extends CCComp { let allAlive = true; let hasHero = false; - let heroDeathCount = 0; ecs.query(this.heroAttrsMatcher).forEach(entity => { const attrs = entity.get(HeroAttrsComp); if (attrs && attrs.fac === FacSet.HERO) { hasHero = true; - if (attrs.is_dead) { - allAlive = false; - heroDeathCount++; - } } }); + // 英雄为真实死亡(实体已销毁),本回合死亡数取死亡记录长度 + const heroDeathCount = smc.mission.dead_heroes.length; + if (heroDeathCount > 0) allAlive = false; // 【动态难度调节】根据本回合战况自动放水 / 加压 // 清场加速节省的时间还原进口径,避免"打得好"被节奏加速+强度加压双重惩罚 @@ -978,15 +976,14 @@ export class MissionComp extends CCComp { /** 检测场上英雄是否全员存活(清屏 Perfect 判定用,独立于评分统计逻辑) */ private checkAllHeroAlive(): boolean { let hasHero = false; - let allAlive = true; ecs.query(this.heroAttrsMatcher).forEach(entity => { const attrs = entity.get(HeroAttrsComp); if (attrs && attrs.fac === FacSet.HERO) { hasHero = true; - if (attrs.is_dead) allAlive = false; } }); - return hasHero && allAlive; + // 英雄为真实死亡(实体已销毁),有死亡记录即非全员存活 + return hasHero && smc.mission.dead_heroes.length === 0; } /** diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index c1f1bd1b..c65478fe 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -15,6 +15,11 @@ * 旧英雄移动合并销毁,新英雄以 **两者最高等级 + 1** 落地(封顶 HERO_MAX_LV), * 等级已达上限时拒绝召唤。英雄也可通过升级卡(SpecialUpgrade)升级。 * + * 死亡机制: + * 英雄为真实死亡(HeroAtkSystem.doDead → 快照至 smc.mission.dead_heroes → 实体销毁), + * 本组件不做回合自动复活;复活由后续复活机制读取 dead_heroes 重新召唤。 + * revive 系技能为"濒死回血"(血量归零时消耗次数回血续命),不触发真死。 + * * 依赖: * - Hero(hero/Hero.ts)—— 英雄 ECS 实体类 * - HeroAttrsComp —— 英雄属性组件 @@ -102,34 +107,28 @@ export class MissionHeroComp extends CCComp { // ======================== 事件处理 ======================== - /** 关卡结束时清理全部英雄 ECS 实体 */ + /** 关卡结束时清理全部存活英雄 ECS 实体,并清空本局死亡英雄记录 */ clear_heros() { const heroes = this.getAllHeroes(); for (let i = 0; i < heroes.length; i++) { heroes[i].destroy(); } + // 整局结束,死亡记录失效(复活机制仅限局内) + smc.mission.dead_heroes = []; } - /** 战斗准备阶段:重置出战英雄计数,恢复满血重新登场 */ + /** + * 战斗准备阶段:刷新存活英雄计数与血条。 + * + * 说明:英雄为真实死亡(实体已销毁,快照存于 smc.mission.dead_heroes), + * 本阶段不再做回合自动复活;复活由后续复活机制(重新召唤)显式触发。 + */ fight_ready() { const heroes = this.getAllHeroes(); smc.vmdata.mission_data.hero_num = heroes.length; for (let i = 0; i < heroes.length; i++) { - const hero = heroes[i]; - const model = hero.get(HeroAttrsComp); - const view = hero.get(HeroViewComp); - if (model && view) { - if (model.is_dead) { - view.alive(); - const posIndex = this.pickPositionIndexForHero([hero.eid]); - const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex]; - // 计算出生点(空中) - const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0); - view.node.setPosition(spawnPos); - model.posIndex = posIndex; - if (posIndex >= 0) smc.mission.heroGrid[posIndex] = hero.eid; - hero.playDropAnim(spawnPos, landingPos.y); - } + const model = heroes[i].get(HeroAttrsComp); + if (model) { model.dirty_hp = true; } }