Files
pixelheros/assets/script/game/map/MissionHeroComp.ts
pan 401b02be96 feat: 新增装备商店系统,调整英雄站位与抽卡逻辑
1.  重构抽卡函数,新增排除装备卡参数,装备卡改为商店直购
2.  添加装备商店面板,支持装备浏览与购买,跨波次保留购买记录
3.  调整英雄出场点位坐标,优化战场布局
4.  扩展技能卡槽位,新增两行技能槽位置
5.  重构EquipListComp组件,适配装备商店UI逻辑
6.  删除废弃的melist预制件与相关元文件
7.  调整hnode预制件布局参数
2026-07-22 10:44:01 +08:00

294 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file MissionHeroComp.ts
* @description 英雄召唤管理组件(逻辑层 + 视图层)
*
* 职责:
* 1. 处理 **英雄召唤**:接收 CallHero 事件 → 通过串行队列执行召唤。
* 2. 管理英雄的出生点和掉落动画。
*
* 关键设计:
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
* - handleSingleSummon() 仅负责生成英雄英雄升级由升级卡系统MissionCardComp独立处理。
*
* 历史:
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。
* 英雄等级提升现在仅通过升级卡SpecialUpgrade实现。
*
* 依赖:
* - Herohero/Hero.ts—— 英雄 ECS 实体类
* - HeroAttrsComp —— 英雄属性组件
* - HeroInfo / HeroPos / HTypeheroSet—— 英雄静态配置
* - 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(-220, BoxSet.GAME_LINE, 0), // index 0 (node_index 1): Top Front 第二
v3(-140, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front 第一
v3(-300, BoxSet.GAME_LINE, 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放入串行队列。
* 防御:已召唤的英雄 uuid 拒绝重复召唤(只能通过升级卡升级)。
*
* @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));
// 防御:场上已有同 uuid 的存活英雄时,拒绝重复召唤
if (this.isHeroAlreadySummoned(uuid)) {
oops.gui.toast(`该英雄已召唤,只能通过升级卡升级`);
return;
}
this.summon_queue.push({ uuid, hero_lv, pool_lv });
this.processSummonQueue();
}
/**
* 检查场上是否已存在指定 uuid 的存活英雄。
* @param uuid 英雄模板 uuid
* @returns true 表示场上已有该英雄,不可重复召唤
*/
private isHeroAlreadySummoned(uuid: number): boolean {
let exists = false;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
if (exists) return;
const model = entity.get(HeroAttrsComp);
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
if (model.hero_uuid === uuid) exists = true;
});
return exists;
}
// ======================== 英雄生成 ========================
/**
* 动态分配英雄上场的位置
* @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<number>();
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>(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();
}
}