From a65a26b0bcdaf96743f6c23a07f5564f1a29ac99 Mon Sep 17 00:00:00 2001 From: panw Date: Wed, 22 Apr 2026 17:05:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=88=98=E6=96=97?= =?UTF-8?q?=E5=87=86=E5=A4=87=E9=98=B6=E6=AE=B5=E8=8B=B1=E9=9B=84=E5=A4=8D?= =?UTF-8?q?=E6=B4=BB=E4=B8=8E=E5=85=A5=E5=9C=BA=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在准备阶段开始时,通过 PhasePrepareStart 事件触发英雄状态重置: - 死亡英雄复活并恢复满血,播放下落入场动画 - 英雄实体在死亡时移至墓地并禁用碰撞,避免战斗逻辑干扰 - 更新英雄数量UI以反映复活后的状态 --- assets/script/game/hero/Hero.ts | 28 +++++++++++++-- assets/script/game/hero/HeroViewComp.ts | 26 +++++++++----- assets/script/game/map/MissionCardComp.ts | 7 +++- assets/script/game/map/MissionComp.ts | 1 + assets/script/game/map/MissionHeroComp.ts | 43 +++++++++++++++++------ 5 files changed, 81 insertions(+), 24 deletions(-) diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 0802b1f9..3ff3b468 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -155,6 +155,28 @@ export class Hero extends ecs.Entity { move.moving = false; hv.as.idle(); + this.playDropAnim(pos, dropToY); + } + + /** + * 播放英雄入场(下落)动画 + * @param pos 初始位置(通常在空中) + * @param dropToY 最终落地的 Y 坐标 + */ + playDropAnim(pos: Vec3, dropToY: number) { + const view = this.get(HeroViewComp); + const node = view?.node; + if (!node || !node.isValid) return; + + const model = this.get(HeroAttrsComp); + const move = this.get(MoveComp); + const collider = node.getComponent(BoxCollider2D); + + // 入场过程暂不参与碰撞,防止半空触发战斗逻辑 + if (collider) collider.enabled = false; + move.moving = false; + view.as.idle(); + // 依据下落距离自适应入场时长,保证手感稳定 const dropDistance = Math.abs(pos.y - dropToY); const dropDuration = Math.max(0.18, Math.min(0.38, dropDistance / 1200)); @@ -167,7 +189,7 @@ export class Hero extends ecs.Entity { if (!node || !node.isValid) return; // 落地后锁定最终位置,切换到落地完成状态 node.setPosition(pos.x, dropToY, 0); - hv.playEnd("down"); + view.playEnd("down"); move.moving = true; // 落地后再启用碰撞,避免空中阶段触发伤害结算 if (collider) { @@ -177,12 +199,12 @@ export class Hero extends ecs.Entity { } // 落地后触发 call 技能 - if (model.call && model.call.length > 0) { + if (model && model.call && model.call.length > 0) { model.call.forEach(uuid => { oops.message.dispatchEvent(GameEvent.TriggerSkill, { s_uuid: uuid, heroAttrs: model, - heroView: hv, + heroView: view, triggerType: 'call' }); }); diff --git a/assets/script/game/hero/HeroViewComp.ts b/assets/script/game/hero/HeroViewComp.ts index 2e54cd76..b4776e3a 100644 --- a/assets/script/game/hero/HeroViewComp.ts +++ b/assets/script/game/hero/HeroViewComp.ts @@ -392,6 +392,7 @@ export class HeroViewComp extends CCComp { this.model.is_dead=false this.model.is_count_dead=false + this.deadCD = 0; this.as.do_buff(); this.status_change("idle"); this.model.hp =this.model.hp_max*50/100; @@ -448,17 +449,24 @@ export class HeroViewComp extends CCComp { } if(this.model.fac === FacSet.HERO){ - + // 将英雄移到玩家看不到的墓地 + this.node.setPosition(v3(-2000, -2000, 0)); + const collider = this.node.getComponent(Collider2D); + if (collider) { + collider.enabled = false; + } + // 隐藏UI + this.top_node.active = false; + } else { + // 🔥 方案B:治理性措施 - 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞 + const collider = this.node.getComponent(Collider2D); + if (collider) { + collider.enabled = false; + } + + this.ent.destroy(); } - // 🔥 方案B:治理性措施 - 在销毁实体前先禁用碰撞体,从源头减少"尸体"参与碰撞 - const collider = this.getComponent(Collider2D); - if (collider) { - collider.enabled = false; - } - - this.ent.destroy(); - } do_atked(damage:number,isCrit:boolean,s_uuid:number,isBack:boolean=false){ // 受到攻击时更新最后更新时间 diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index a9559b5e..db552c02 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -264,7 +264,8 @@ export class MissionCardComp extends CCComp { this.on(GameEvent.MissionEnd, this.onMissionEnd, this); this.on(GameEvent.NewWave, this.onNewWave, this); this.on(GameEvent.FightStart, this.onFightStart, this); - /** 全局消息事件 */ + this.on("PhasePrepareStart", this.onPhasePrepareStart, this); + oops.message.on(GameEvent.CoinAdd, this.onCoinAdd, this); oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this); oops.message.on(GameEvent.HeroDead, this.onHeroDead, this); @@ -337,6 +338,10 @@ export class MissionCardComp extends CCComp { this.updatePoolLvUI(); } + private onPhasePrepareStart() { + this.updateHeroNumUI(true, true); + } + /** 新一波:展开面板 → 刷新费用 UI → 重新抽卡分发 */ private onNewWave() { this.isBattlePhase = false; diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 934535f8..5fc44347 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -342,6 +342,7 @@ export class MissionComp extends CCComp { smc.vmdata.mission_data.in_fight = false; smc.mission.stop_spawn_mon = true; // 不隐藏开始按钮,点击事件在 onStartFightBtnClick 内部做了阶段拦截 + oops.message.dispatchEvent("PhasePrepareStart"); break; case MissionPhase.Prepare: diff --git a/assets/script/game/map/MissionHeroComp.ts b/assets/script/game/map/MissionHeroComp.ts index ec9c320b..03c60f36 100644 --- a/assets/script/game/map/MissionHeroComp.ts +++ b/assets/script/game/map/MissionHeroComp.ts @@ -25,7 +25,7 @@ * - FightSet —— 战斗常量(MERGE_NEED / MERGE_MAX) * - oneCom —— 一次性特效组件(控制爆点特效生命周期) */ -import { _decorator, instantiate, Prefab, v3, Vec3 } from "cc"; +import { _decorator, instantiate, Prefab, v3, Vec3, BoxCollider2D } from "cc"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { Hero } from "../hero/Hero"; @@ -35,8 +35,9 @@ import { GameEvent } from "../common/config/GameEvent"; import { HeroInfo, HeroPos, HType } from "../common/config/heroSet"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; -import { FacSet, FightSet } from "../common/config/GameSet"; +import { FacSet, FightSet, BoxSet } from "../common/config/GameSet"; import { oneCom } from "../skill/oncend"; +import { HeroViewComp } from "../hero/HeroViewComp"; const { ccclass } = _decorator; /** @@ -89,11 +90,13 @@ export class MissionHeroCompComp extends CCComp { this.on(GameEvent.MissionEnd,this.clear_heros,this) // 注册全局消息 oops.message.on(GameEvent.CallHero,this.call_hero,this) + oops.message.on("PhasePrepareStart",this.fight_ready,this) } onDestroy(){ // 清理全部监听 oops.message.off(GameEvent.CallHero,this.call_hero,this) + oops.message.off("PhasePrepareStart",this.fight_ready,this) oops.message.off(GameEvent.FightReady,this.fight_ready,this) oops.message.off(GameEvent.Zhaohuan,this.zhao_huan,this) oops.message.off(GameEvent.MissionEnd,this.clear_heros,this) @@ -104,17 +107,36 @@ export class MissionHeroCompComp extends CCComp { // ======================== 事件处理 ======================== - /** 关卡结束时清理全部存活英雄 ECS 实体 */ + /** 关卡结束时清理全部英雄 ECS 实体 */ clear_heros(){ - const heroes = this.getAliveHeroes(); + const heroes = this.getAllHeroes(); for (let i = 0; i < heroes.length; i++) { heroes[i].destroy(); } } - /** 战斗准备阶段:重置出战英雄计数 */ + /** 战斗准备阶段:重置出战英雄计数,恢复满血重新登场 */ fight_ready(){ - smc.vmdata.mission_data.hero_num=0 + 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 landingPos = this.resolveHeroLandingPos(model.hero_uuid); + // 不再直接设置位置,而是播放下落入场动画 + // 计算出出生点(空中) + const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0); + view.node.setPosition(spawnPos); + hero.playDropAnim(spawnPos, landingPos.y); + } + model.hp = model.hp_max; + model.dirty_hp = true; + } + } } /** 预留:召唤事件扩展入口 */ @@ -219,14 +241,13 @@ export class MissionHeroCompComp extends CCComp { // ======================== 英雄查询 ======================== - /** 获取当前全部存活友方英雄 ECS 实体列表 */ - private getAliveHeroes(): Hero[] { + /** 获取当前全部友方英雄 ECS 实体列表(包括存活和墓地) */ + private getAllHeroes(): Hero[] { const heroes: Hero[] = []; 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; heroes.push(entity as Hero); }); return heroes; @@ -321,7 +342,7 @@ export class MissionHeroCompComp extends CCComp { this.addHero(uuid, hero_lv, pool_lv); if (!this.canMergeLevel(hero_lv)) return; const needCount = this.getMergeNeedCount(); - const aliveHeroes = this.getAliveHeroes(); + const aliveHeroes = this.getAllHeroes(); const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, hero_lv, needCount); if (mergeHeroes.length !== needCount) return; this.is_merging = true; @@ -442,7 +463,7 @@ export class MissionHeroCompComp extends CCComp { if (!this.canMergeLevel(checkLv)) { break; } - const aliveHeroes = this.getAliveHeroes(); + const aliveHeroes = this.getAllHeroes(); const sameCount = this.countMergeHeroes(aliveHeroes, uuid, checkLv); if (sameCount < needCount) { break;