refactor: 重构英雄养成与卡牌系统,移除旧合成机制

1.  移除三合一英雄合成与链式合成逻辑,统一使用升级卡进行英雄等级提升
2.  重构卡池系统:移除卡池等级机制,所有英雄卡牌统一为LV1
3.  重构升级逻辑:改为按UUID精确升级场上英雄,动态生成对应升级卡
4.  更新配置常量:拆分并重构成长倍率、英雄上限等战斗配置
5.  简化抽卡逻辑:不再按卡池等级分发卡牌,改为动态混合基础卡与升级卡
6.  清理废弃代码:移除卡池升级相关的UI、逻辑与配置
This commit is contained in:
pan
2026-07-20 17:26:50 +08:00
parent f0952ef82c
commit 15ab2f7b0f
10 changed files with 729 additions and 1059 deletions

View File

@@ -1,29 +1,24 @@
/**
* @file MissionHeroComp.ts
* @description 英雄召唤与合成管理组件(逻辑层 + 视图层)
* @description 英雄召唤管理组件(逻辑层 + 视图层)
*
* 职责:
* 1. 处理 **英雄召唤**:接收 CallHero 事件 → 通过串行队列执行召唤。
* 2. 处理 **英雄合成**:检测同 UUID 同等级英雄是否达到合成条件 →
* 执行合成动画 → 销毁素材 → 生成高一级英雄。
* 3. 支持 **链式合成**:合成完成后自动检测更高等级是否也满足合成条件。
* 4. 管理英雄的出生点和掉落动画。
* 2. 管理英雄的出生点和掉落动画。
*
* 关键设计:
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**
* 避免同帧并发导致合成判断错误
* - handleSingleSummon() 在每次召唤后检测是否触发合成。
* - mergeGroupHeroes() 执行完整合成流程
* 聚合属性 → 向出生点汇聚动画 → 爆点特效 → 生成高级英雄
* - merge_need_count 控制合成所需数量2 合 1 或 3 合 1
* - merge_max_lv 控制合成上限等级。
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**
* - handleSingleSummon() 仅负责生成英雄英雄升级由升级卡系统MissionCardComp独立处理
*
* 历史
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除
* 英雄等级提升现在仅通过升级卡SpecialUpgrade实现
*
* 依赖:
* - Herohero/Hero.ts—— 英雄 ECS 实体类
* - HeroAttrsComp —— 英雄属性组件
* - HeroInfo / HeroPos / HTypeheroSet—— 英雄静态配置
* - FightSet —— 战斗常量MERGE_NEED / MERGE_MAX
* - oneCom —— 一次性特效组件(控制爆点特效生命周期)
* - FightSet —— 战斗常量
*/
import { _decorator, instantiate, Prefab, v3, Vec3, BoxCollider2D } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
@@ -36,17 +31,16 @@ 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 { oneCom } from "../skill/oncend";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
import { MoveComp } from "../hero/MoveComp";
const { ccclass } = _decorator;
/**
* MissionHeroComp —— 英雄召唤与合成管理器
* MissionHeroComp —— 英雄召唤管理器
*
* 管理英雄的召唤请求队列出生动画和合成系统
* 合成支持 2 合 1 或 3 合 1且可链式合成至上限等级
* 管理英雄的召唤请求队列出生动画。
* 英雄升级由 MissionCardComp 的升级卡系统统一处理
*/
@ccclass('MissionHeroComp')
@ecs.register('MissionHeroComp', false)
@@ -76,15 +70,9 @@ export class MissionHeroComp extends CCComp {
current_hero_uuid:number=0
/** 当前英雄数量缓存 */
current_hero_num:number=-1
/** 合成规则需要几个同级英雄才能合成2 或 3 */
merge_need_count:number=FightSet.MERGE_NEED
/** 允许合成的最高等级(合成产物不超过此等级) */
merge_max_lv:number=FightSet.MERGE_MAX
/** 是否正在执行一次合成流程(防止并发) */
is_merging:boolean=false
/** 是否正在消费召唤队列(防止并发) */
is_processing_queue:boolean=false
/** 召唤请求队列:保证召唤与合成按顺序串行执行 */
/** 召唤请求队列:保证召唤按顺序串行执行 */
summon_queue:{ uuid: number; hero_lv: number; pool_lv: number }[]=[]
/** 预留英雄列表 */
heros:any=[]
@@ -134,8 +122,7 @@ export class MissionHeroComp extends CCComp {
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;
@@ -172,7 +159,7 @@ export class MissionHeroComp extends CCComp {
/**
* 动态分配英雄上场的位置
* @param excludeEids 排除计算的实体ID数组避免复活或合成时把自己算成占据的位置)
* @param excludeEids 排除计算的实体ID数组避免复活时把自己算成占据的位置
*/
private pickPositionIndexForHero(excludeEids: number[] = []): number {
const heroes = this.getAllHeroes().filter(h => {
@@ -205,11 +192,10 @@ export class MissionHeroComp extends CCComp {
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param pool_lv 卡池等级
* @param pool_lv 卡池等级(历史遗留,新机制下不再使用)
* @returns 创建的 Hero 实体
*/
private addHero(uuid:number=1001,hero_lv:number=1, pool_lv:number=1) {
console.log("addHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const posIndex = this.pickPositionIndexForHero();
@@ -229,56 +215,6 @@ export class MissionHeroComp extends CCComp {
return hero;
}
/**
* 生成合成后的高级英雄,并覆盖为聚合后的属性。
*
* @param uuid 英雄 UUID
* @param hero_lv 合成后等级
* @param pool_lv 卡池等级
* @param ap 聚合后攻击力
* @param hp_max 聚合后最大生命值
* @param targetPos 指定生成位置
* @returns 实际生成的英雄等级
*/
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetPosIndex?: number, targetPos?: Vec3): number {
console.log("addMergedHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
let posIndex = targetPosIndex;
let landingPos = targetPos;
if (posIndex === undefined || posIndex < 0 || !landingPos) {
posIndex = this.pickPositionIndexForHero();
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) {
model.ap = Math.max(0, ap);
model.hp_max = Math.max(1, hp_max);
model.hp = model.hp_max;
model.dirty_hp = true;
// 获取视图组件触发升级特效(包含描边更新)
const view = hero.get(HeroViewComp);
if (view && typeof view['lv_up'] === 'function') {
view['lv_up']();
}
oops.message.dispatchEvent(GameEvent.MasterCalled, {
eid: hero.eid,
model: model
});
return model.lv;
}
return hero_lv;
}
// ======================== 英雄查询 ========================
/** 获取当前全部友方英雄 ECS 实体列表(包括存活和墓地) */
@@ -293,62 +229,6 @@ export class MissionHeroComp extends CCComp {
return heroes;
}
/**
* 从存活英雄中挑选可参与本次合成的英雄组。
*
* @param aliveHeroes 存活英雄列表
* @param uuid 目标英雄 UUID
* @param hero_lv 目标等级
* @param needCount 合成需要数量
* @returns 匹配的英雄数组(长度 = needCount 或不足)
*/
private pickMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number, needCount: number = 3): Hero[] {
const mergeHeroes: Hero[] = [];
for (let i = 0; i < aliveHeroes.length; i++) {
const model = aliveHeroes[i].get(HeroAttrsComp);
if (!model) continue;
if (model.hero_uuid !== uuid) continue;
if (model.lv !== hero_lv) continue;
mergeHeroes.push(aliveHeroes[i]);
if (mergeHeroes.length === needCount) break;
}
return mergeHeroes;
}
/** 统计满足同 UUID 同等级的可合成英雄数量 */
private countMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number): number {
let count = 0;
for (let i = 0; i < aliveHeroes.length; i++) {
const model = aliveHeroes[i].get(HeroAttrsComp);
if (!model) continue;
if (model.hero_uuid !== uuid) continue;
if (model.lv !== hero_lv) continue;
count += 1;
}
return count;
}
// ======================== 合成规则 ========================
/**
* 读取合成所需数量(仅支持 2 或 3
* 由 FightSet.MERGE_NEED 配置。
*/
private getMergeNeedCount(): number {
return this.merge_need_count === 2 ? 2 : 3;
}
/**
* 判断该等级是否还能继续向上合成。
* @param hero_lv 当前等级
* @returns true = 可以合成(未达上限)
*/
private canMergeLevel(hero_lv: number): boolean {
return hero_lv < Math.max(1, this.merge_max_lv);
}
// ======================== 召唤队列 ========================
/**
@@ -371,159 +251,16 @@ export class MissionHeroComp extends CCComp {
}
/**
* 处理单次召唤:
* 1. 生成英雄。
* 2. 检测是否满足合成条件。
* 3. 满足则执行合成 + 链式合成。
* 处理单次召唤:仅生成英雄,不再触发合成。
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param pool_lv 卡池等级
* @param pool_lv 卡池等级(历史遗留)
*/
private async handleSingleSummon(uuid: number, hero_lv: number, pool_lv: number = 1) {
this.addHero(uuid, hero_lv, pool_lv);
if (!this.canMergeLevel(hero_lv)) return;
const needCount = this.getMergeNeedCount();
const aliveHeroes = this.getAllHeroes();
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, hero_lv, needCount);
if (mergeHeroes.length !== needCount) return;
this.is_merging = true;
try {
const mergedLv = await this.mergeGroupHeroes(mergeHeroes, uuid, hero_lv, pool_lv);
await this.tryChainMerge(uuid, mergedLv, pool_lv);
} finally {
this.is_merging = false;
}
}
// ======================== 合成动画 ========================
/**
* 将一组合成素材英雄向出生点汇聚并销毁。
* 所有素材动画完成后 Promise resolve。
*
* @param mergeHeroes 合成素材英雄数组
* @param spawnPos 汇聚目标位置
*/
private mergeDestroyAtBirth(mergeHeroes: Hero[], spawnPos: Vec3): Promise<void> {
return new Promise((resolve) => {
let doneCount = 0;
const total = mergeHeroes.length;
if (total <= 0) {
resolve();
return;
}
const onDone = () => {
doneCount += 1;
if (doneCount >= total) {
resolve();
}
};
for (let i = 0; i < mergeHeroes.length; i++) {
mergeHeroes[i].mergeToBirthAndDestroy(spawnPos, onDone);
}
});
}
/**
* 播放合成爆点特效(使用 oneCom 控制生命周期)。
* 延迟 0.4 秒后 resolve。
*
* @param worldPos 特效播放位置
*/
private playMergeBoomFx(worldPos: Vec3): Promise<void> {
return new Promise((resolve) => {
const scene = smc.map?.MapView?.scene;
const layer = scene?.entityLayer?.node;
if (!layer || !layer.isValid) {
resolve();
return;
}
const prefab: Prefab = oops.res.get("game/skill/end/dead", Prefab)!;
if (!prefab) {
resolve();
return;
}
const fx = instantiate(prefab);
if (!fx || !fx.isValid) {
resolve();
return;
}
fx.parent = layer;
fx.setPosition(worldPos);
fx.getComponent(oneCom) || fx.addComponent(oneCom);
this.scheduleOnce(() => resolve(), 0.4);
});
}
/**
* 执行一次完整合成流程:
* 1. 聚合素材的 AP 和 HP。
* 2. 将素材向出生点汇聚并销毁。
* 3. 播放爆点特效。
* 4. 生成高一级英雄(属性为聚合值)。
*
* @param mergeHeroes 合成素材
* @param uuid 英雄 UUID
* @param hero_lv 素材等级
* @param pool_lv 卡池等级
* @returns 合成产物的实际等级
*/
private async mergeGroupHeroes(mergeHeroes: Hero[], uuid: number, hero_lv: number, pool_lv: number): Promise<number> {
// 聚合属性
let sumAp = 0;
let sumHpMax = 0;
const mergeEids = [];
for (let i = 0; i < mergeHeroes.length; i++) {
const model = mergeHeroes[i].get(HeroAttrsComp);
mergeEids.push(mergeHeroes[i].eid);
if (!model) continue;
sumAp += model.ap;
sumHpMax += model.hp_max;
}
const posIndex = this.pickPositionIndexForHero(mergeEids);
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
// 汇聚 → 特效 → 生成
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
await this.playMergeBoomFx(spawnPos);
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, posIndex, landingPos);
}
/**
* 链式合成:合成完成后继续检测更高等级是否也满足条件。
* 最多循环 20 次作为安全上限。
*
* @param uuid 英雄 UUID
* @param startLv 起始检测等级
* @param pool_lv 卡池等级
*/
private async tryChainMerge(uuid: number, startLv: number, pool_lv: number) {
let checkLv = Math.max(1, startLv);
const needCount = this.getMergeNeedCount();
let guard = 0;
while (guard < 20) {
guard += 1;
if (!this.canMergeLevel(checkLv)) {
break;
}
const aliveHeroes = this.getAllHeroes();
const sameCount = this.countMergeHeroes(aliveHeroes, uuid, checkLv);
if (sameCount < needCount) {
break;
}
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, checkLv, needCount);
if (mergeHeroes.length < needCount) {
break;
}
checkLv = await this.mergeGroupHeroes(mergeHeroes, uuid, checkLv, pool_lv);
}
}
/** ECS 组件移除时触发(当前不销毁节点,保留引用) */
reset() {