/** * @file MissionHeroComp.ts * @description 英雄召唤管理组件(逻辑层 + 视图层) * * 职责: * 1. 处理 **英雄召唤**:接收 CallHero 事件 → 通过串行队列执行召唤。 * 2. 管理英雄的出生点和掉落动画。 * * 关键设计: * - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。 * - handleSingleSummon() 仅负责生成英雄;英雄升级由升级卡系统(MissionCardComp)独立处理。 * * 历史: * 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。 * 英雄等级提升现在仅通过升级卡(SpecialUpgrade)实现。 * * 依赖: * - Hero(hero/Hero.ts)—— 英雄 ECS 实体类 * - HeroAttrsComp —— 英雄属性组件 * - HeroInfo / HeroPos / HType(heroSet)—— 英雄静态配置 * - FightSet —— 战斗常量 */ 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"; import { smc } from "../common/SingletonModuleComp"; import { Timer } from "db://oops-framework/core/common/timer/Timer"; 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, BoxSet } from "../common/config/GameSet"; import { HeroViewComp } from "../hero/HeroViewComp"; import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet"; import { MoveComp } from "../hero/MoveComp"; const { ccclass } = _decorator; /** * MissionHeroComp —— 英雄召唤管理器 * * 管理英雄的召唤请求队列和出生动画。 * 英雄升级由 MissionCardComp 的升级卡系统统一处理。 */ @ccclass('MissionHeroComp') @ecs.register('MissionHeroComp', false) export class MissionHeroComp extends CCComp { // ======================== 常量 ======================== /** 硬编码的6个英雄占位点 */ public static readonly HERO_POSITIONS: Vec3[] = [ v3(-175, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front v3(-170, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front v3(-175, BoxSet.GAME_LINE - 100, 0), // index 2 (node_index 3): Bot Front v3(-280, BoxSet.GAME_LINE + 100, 0), // index 3 (node_index 4): Top Back v3(-280, BoxSet.GAME_LINE, 0), // index 4 (node_index 5): Mid Back v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back ]; /** 英雄出生时的掉落高度(从空中落到地面的像素差) */ private static readonly HERO_DROP_HEIGHT = 260 // ======================== 运行时属性 ======================== /** 预留计时器 */ timer:Timer=new Timer(2) /** 预留状态:友方是否全部死亡 */ Friend_is_dead:boolean=false /** 当前处理的英雄 uuid */ current_hero_uuid:number=0 /** 当前英雄数量缓存 */ current_hero_num:number=-1 /** 是否正在消费召唤队列(防止并发) */ is_processing_queue:boolean=false /** 召唤请求队列:保证召唤按顺序串行执行 */ summon_queue:{ uuid: number; hero_lv: number; pool_lv: number }[]=[] /** 预留英雄列表 */ heros:any=[] // ======================== 生命周期 ======================== onLoad(){ // 注册节点级事件 this.on(GameEvent.FightReady,this.fight_ready,this) this.on(GameEvent.Zhaohuan,this.zhao_huan,this) 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(){ super.onDestroy(); // 清理全部监听 oops.message.off(GameEvent.CallHero,this.call_hero,this) oops.message.off("PhasePrepareStart",this.fight_ready,this) } start() { } // ======================== 事件处理 ======================== /** 关卡结束时清理全部英雄 ECS 实体 */ clear_heros(){ const heroes = this.getAllHeroes(); for (let i = 0; i < heroes.length; i++) { heroes[i].destroy(); } } /** 战斗准备阶段:重置出战英雄计数,恢复满血重新登场 */ 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); } model.dirty_hp = true; } } } /** 预留:召唤事件扩展入口 */ private zhao_huan(event: string, args: any){ } /** * 召唤请求入口: * 从事件参数中提取 uuid / hero_lv / pool_lv,放入串行队列。 * * @param event 事件名 * @param args { uuid, hero_lv, pool_lv } */ private async call_hero(event: string, args: any){ const payload = args ?? event; const uuid = Number(payload?.uuid ?? 1001); const hero_lv = Math.max(1, Number(payload?.hero_lv ?? 1)); const pool_lv = Math.max(1, Number(payload?.pool_lv ?? 1)); this.summon_queue.push({ uuid, hero_lv, pool_lv }); this.processSummonQueue(); } // ======================== 英雄生成 ======================== /** * 动态分配英雄上场的位置 * @param excludeEids 排除计算的实体ID数组(避免复活时把自己算成占据的位置) */ private pickPositionIndexForHero(excludeEids: number[] = []): number { const heroes = this.getAllHeroes().filter(h => { const m = h.get(HeroAttrsComp); return m && !m.is_dead && !excludeEids.includes(h.eid); }); const occupied = new Set(); for (const h of heroes) { const m = h.get(HeroAttrsComp); if (m && m.posIndex >= 0) occupied.add(m.posIndex); } // 优先中前(1) -> 上前(0) -> 下前(2) -> 中后(4) -> 上后(3) -> 下后(5) const slotPriority = [1, 0, 2, 4, 3, 5]; for (const idx of slotPriority) { if (!occupied.has(idx)) { return idx; } } // 溢出:默认中前 return 1; } /** * 生成一个英雄 ECS 实体: * - 计算出生点(空中)和落点(地面)。 * - 调用 hero.load() 初始化并播放掉落动画。 * * @param uuid 英雄 UUID * @param hero_lv 英雄等级 * @param pool_lv 卡池等级(历史遗留,新机制下不再使用) * @returns 创建的 Hero 实体 */ private addHero(uuid:number=1001,hero_lv:number=1, pool_lv:number=1) { let hero = ecs.getEntity(Hero); let scale = 1 const posIndex = this.pickPositionIndexForHero(); const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex]; let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0); hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv,posIndex); // 召唤完成后,派发事件以更新英雄面板 const model = hero.get(HeroAttrsComp); if (model) { oops.message.dispatchEvent(GameEvent.MasterCalled, { eid: hero.eid, model: model }); } return 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; heroes.push(entity as Hero); }); return heroes; } // ======================== 召唤队列 ======================== /** * 串行消费召唤队列: * 使用 is_processing_queue 标志防止同帧多次调用。 * 逐个取出队列中的请求并处理。 */ private async processSummonQueue() { if (this.is_processing_queue) return; this.is_processing_queue = true; try { while (this.summon_queue.length > 0) { const payload = this.summon_queue.shift(); if (!payload) continue; await this.handleSingleSummon(payload.uuid, payload.hero_lv, payload.pool_lv); } } finally { this.is_processing_queue = false; } } /** * 处理单次召唤:仅生成英雄,不再触发合成。 * * @param uuid 英雄 UUID * @param hero_lv 英雄等级 * @param pool_lv 卡池等级(历史遗留) */ private async handleSingleSummon(uuid: number, hero_lv: number, pool_lv: number = 1) { this.addHero(uuid, hero_lv, pool_lv); } /** ECS 组件移除时触发(当前不销毁节点,保留引用) */ reset() { // this.node.destroy(); } }