refactor: 重构英雄养成与卡牌系统,移除旧合成机制
1. 移除三合一英雄合成与链式合成逻辑,统一使用升级卡进行英雄等级提升 2. 重构卡池系统:移除卡池等级机制,所有英雄卡牌统一为LV1 3. 重构升级逻辑:改为按UUID精确升级场上英雄,动态生成对应升级卡 4. 更新配置常量:拆分并重构成长倍率、英雄上限等战斗配置 5. 简化抽卡逻辑:不再按卡池等级分发卡牌,改为动态混合基础卡与升级卡 6. 清理废弃代码:移除卡池升级相关的UI、逻辑与配置
This commit is contained in:
@@ -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)实现。
|
||||
*
|
||||
* 依赖:
|
||||
* - Hero(hero/Hero.ts)—— 英雄 ECS 实体类
|
||||
* - HeroAttrsComp —— 英雄属性组件
|
||||
* - HeroInfo / HeroPos / HType(heroSet)—— 英雄静态配置
|
||||
* - 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() {
|
||||
|
||||
Reference in New Issue
Block a user