refactor(mission): 移除英雄二合一合成逻辑,改为允许同英雄同场
1. 删除Hero类中的mergeToBirthAndDestroy方法 2. 移除MissionHeroComp中的合成相关校验与处理逻辑 3. 简化英雄召唤流程,不再限制同uuid英雄重复召唤 4. 更新注释文档,调整相关导入依赖
This commit is contained in:
@@ -268,36 +268,6 @@ export class Hero extends ecs.Entity {
|
|||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeToBirthAndDestroy(birthPos: Vec3, onDone?: () => void) {
|
|
||||||
const view = this.get(HeroViewComp);
|
|
||||||
const node = view?.node;
|
|
||||||
if (!node || !node.isValid) {
|
|
||||||
this.destroy();
|
|
||||||
if (onDone) onDone();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const collider = node.getComponent(BoxCollider2D);
|
|
||||||
if (collider) {
|
|
||||||
collider.enabled = false;
|
|
||||||
}
|
|
||||||
const body = node.getComponent(RigidBody2D);
|
|
||||||
if (body) {
|
|
||||||
body.enabled = false;
|
|
||||||
}
|
|
||||||
const currentPos = node.getPosition();
|
|
||||||
const targetPos = v3(birthPos.x, birthPos.y, 0);
|
|
||||||
const moveDistance = Vec3.distance(currentPos, targetPos);
|
|
||||||
const moveDuration = Math.max(0.12, Math.min(0.3, moveDistance / 1200));
|
|
||||||
Tween.stopAllByTarget(node);
|
|
||||||
tween(node)
|
|
||||||
.to(moveDuration, { position: targetPos })
|
|
||||||
.call(() => {
|
|
||||||
this.destroy();
|
|
||||||
if (onDone) onDone();
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置入口:复用 destroy 的释放流程 */
|
/** 重置入口:复用 destroy 的释放流程 */
|
||||||
reset() {
|
reset() {
|
||||||
super.destroy();
|
super.destroy();
|
||||||
|
|||||||
@@ -8,12 +8,8 @@
|
|||||||
*
|
*
|
||||||
* 关键设计:
|
* 关键设计:
|
||||||
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
|
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
|
||||||
* - handleSingleSummon() 负责生成英雄;同 uuid 重复召唤走二合一合成升级。
|
* - spawnHeroAt 负责生成英雄;允许同 uuid 重复召唤,多个相同英雄可同时站场。
|
||||||
*
|
* - 英雄可通过升级卡(SpecialUpgrade)升级。
|
||||||
* 合成规则:
|
|
||||||
* 场上存在同 uuid 英雄时再次召唤触发二合一:
|
|
||||||
* 旧英雄移动合并销毁,新英雄以 **两者最高等级 + 1** 落地(封顶 HERO_MAX_LV),
|
|
||||||
* 等级已达上限时拒绝召唤。英雄也可通过升级卡(SpecialUpgrade)升级。
|
|
||||||
*
|
*
|
||||||
* 死亡机制:
|
* 死亡机制:
|
||||||
* 英雄为真实死亡(HeroAtkSystem.doDead → 快照至 smc.mission.dead_heroes → 实体销毁),
|
* 英雄为真实死亡(HeroAtkSystem.doDead → 快照至 smc.mission.dead_heroes → 实体销毁),
|
||||||
@@ -29,10 +25,10 @@
|
|||||||
* 依赖:
|
* 依赖:
|
||||||
* - Hero(hero/Hero.ts)—— 英雄 ECS 实体类
|
* - Hero(hero/Hero.ts)—— 英雄 ECS 实体类
|
||||||
* - HeroAttrsComp —— 英雄属性组件
|
* - HeroAttrsComp —— 英雄属性组件
|
||||||
* - HeroInfo / HeroPos / HType / HRole(heroSet)—— 英雄静态配置
|
* - HeroInfo / HType(heroSet)—— 英雄静态配置
|
||||||
* - FightSet —— 战斗常量
|
* - FightSet —— 战斗常量
|
||||||
*/
|
*/
|
||||||
import { _decorator, instantiate, Prefab, v3, Vec3, BoxCollider2D } from "cc";
|
import { _decorator, v3, Vec3 } from "cc";
|
||||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
import { Hero } from "../hero/Hero";
|
import { Hero } from "../hero/Hero";
|
||||||
@@ -42,10 +38,8 @@ import { GameEvent } from "../common/config/GameEvent";
|
|||||||
import { HeroInfo, HType } from "../common/config/heroSet";
|
import { HeroInfo, HType } from "../common/config/heroSet";
|
||||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
import { FacSet, FightSet, BoxSet } from "../common/config/GameSet";
|
import { FacSet, BoxSet } from "../common/config/GameSet";
|
||||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||||
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
|
|
||||||
import { MoveComp } from "../hero/MoveComp";
|
|
||||||
const { ccclass } = _decorator;
|
const { ccclass } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,45 +180,10 @@ export class MissionHeroComp extends CCComp {
|
|||||||
const hero_lv = Math.max(outer_lv, Math.max(1, Number(payload?.hero_lv ?? 1)));
|
const hero_lv = Math.max(outer_lv, Math.max(1, Number(payload?.hero_lv ?? 1)));
|
||||||
const card_lv = Math.max(1, Number(payload?.card_lv ?? 1));
|
const card_lv = Math.max(1, Number(payload?.card_lv ?? 1));
|
||||||
|
|
||||||
// 二合一前置校验:同 uuid 已存在且合成后等级会超上限时,直接拒绝
|
|
||||||
if (this.isHeroAlreadySummoned(uuid)) {
|
|
||||||
const existing = this.findHeroByUuid(uuid);
|
|
||||||
const existingLv = existing ? (existing.get(HeroAttrsComp)?.lv ?? 1) : 1;
|
|
||||||
if (Math.max(existingLv, hero_lv) + 1 > FightSet.HERO_MAX_LV) {
|
|
||||||
oops.gui.toast(`该英雄已达等级上限,无法继续合成`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.summon_queue.push({ uuid, hero_lv, card_lv });
|
this.summon_queue.push({ uuid, hero_lv, card_lv });
|
||||||
this.processSummonQueue();
|
this.processSummonQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查场上是否已存在指定 uuid 的存活英雄。
|
|
||||||
* @param uuid 英雄模板 uuid
|
|
||||||
* @returns true 表示场上已有该英雄,不可重复召唤
|
|
||||||
*/
|
|
||||||
private isHeroAlreadySummoned(uuid: number): boolean {
|
|
||||||
return this.findHeroByUuid(uuid) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查找场上指定 uuid 的存活英雄实体。
|
|
||||||
* @param uuid 英雄模板 uuid
|
|
||||||
* @returns 命中的 Hero 实体,未命中返回 null
|
|
||||||
*/
|
|
||||||
private findHeroByUuid(uuid: number): Hero | null {
|
|
||||||
let found: Hero | null = null;
|
|
||||||
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
|
|
||||||
if (found) return;
|
|
||||||
const model = entity.get(HeroAttrsComp);
|
|
||||||
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
|
|
||||||
if (model.hero_uuid === uuid) found = entity as Hero;
|
|
||||||
});
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== 英雄生成 ========================
|
// ======================== 英雄生成 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,49 +234,27 @@ export class MissionHeroComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成一个英雄 ECS 实体:
|
* 生成一个英雄:
|
||||||
* - 计算出生点(空中)和落点(地面)。
|
* - 按攻击类型自动分配三排槽位并展开落点。
|
||||||
* - 调用 hero.load() 初始化并播放掉落动画。
|
|
||||||
*
|
|
||||||
* @param uuid 英雄 UUID
|
|
||||||
* @param hero_lv 英雄等级
|
|
||||||
* @param card_lv 卡牌等级(驱动 bg_node 颜色)
|
|
||||||
* @returns 创建的 Hero 实体
|
|
||||||
*/
|
|
||||||
private addHero(uuid: number = 1001, hero_lv: number = 1, card_lv: number = 1) {
|
|
||||||
return this.spawnHeroAt(uuid, hero_lv, card_lv, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在指定站位生成英雄:
|
|
||||||
* - posIndex < 0 时按职业自动分配空位(三排基准点 ±20 展开)。
|
|
||||||
* - 计算出生点(空中)和落点(地面),播放掉落动画。
|
* - 计算出生点(空中)和落点(地面),播放掉落动画。
|
||||||
*
|
*
|
||||||
* @param uuid 英雄 UUID
|
* @param uuid 英雄 UUID
|
||||||
* @param hero_lv 英雄等级
|
* @param hero_lv 英雄等级
|
||||||
* @param card_lv 卡牌等级(驱动 bg_node 颜色)
|
* @param card_lv 卡牌等级(驱动 bg_node 颜色)
|
||||||
* @param posIndex 指定站位索引,-1 表示自动分配
|
|
||||||
* @returns 创建的 Hero 实体
|
* @returns 创建的 Hero 实体
|
||||||
*/
|
*/
|
||||||
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number, posIndex: number, inheritPos: Vec3 | null = null) {
|
private spawnHeroAt(uuid: number, hero_lv: number, card_lv: number) {
|
||||||
let hero = ecs.getEntity<Hero>(Hero);
|
let hero = ecs.getEntity<Hero>(Hero);
|
||||||
let scale = 1
|
let scale = 1
|
||||||
const type = HeroInfo[uuid]?.type ?? HType.Mid;
|
const type = HeroInfo[uuid]?.type ?? HType.Mid;
|
||||||
|
|
||||||
let finalPosIndex = posIndex;
|
const heroes = this.getAllHeroes().filter(h => {
|
||||||
let landingPos: Vec3;
|
const m = h.get(HeroAttrsComp);
|
||||||
if (inheritPos) {
|
return !!m && !m.is_dead;
|
||||||
// 合成继承:沿用旧英雄节点位置,posIndex 透传登记网格
|
});
|
||||||
landingPos = v3(inheritPos.x, MissionHeroComp.TYPE_ROW[type]?.y ?? BoxSet.GAME_LINE, 0);
|
const slot = this.pickSpawnSlotForHero(type, heroes);
|
||||||
} else {
|
const finalPosIndex = slot.posIndex;
|
||||||
const heroes = this.getAllHeroes().filter(h => {
|
const landingPos = slot.landingPos;
|
||||||
const m = h.get(HeroAttrsComp);
|
|
||||||
return !!m && !m.is_dead;
|
|
||||||
});
|
|
||||||
const slot = this.pickSpawnSlotForHero(type, heroes);
|
|
||||||
finalPosIndex = slot.posIndex;
|
|
||||||
landingPos = slot.landingPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
let spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
let spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||||
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, finalPosIndex);
|
hero.load(spawnPos, scale, uuid, landingPos.y, hero_lv, card_lv, finalPosIndex);
|
||||||
@@ -362,66 +299,13 @@ export class MissionHeroComp extends CCComp {
|
|||||||
while (this.summon_queue.length > 0) {
|
while (this.summon_queue.length > 0) {
|
||||||
const payload = this.summon_queue.shift();
|
const payload = this.summon_queue.shift();
|
||||||
if (!payload) continue;
|
if (!payload) continue;
|
||||||
await this.handleSingleSummon(payload.uuid, payload.hero_lv, payload.card_lv);
|
this.spawnHeroAt(payload.uuid, payload.hero_lv, payload.card_lv);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.is_processing_queue = false;
|
this.is_processing_queue = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理单次召唤:
|
|
||||||
* - 场上无同 uuid 英雄:直接生成新英雄。
|
|
||||||
* - 场上已有同 uuid 英雄:触发二合一,旧英雄合并销毁后生成等级 +1 的新英雄。
|
|
||||||
*
|
|
||||||
* @param uuid 英雄 UUID
|
|
||||||
* @param hero_lv 英雄等级
|
|
||||||
* @param card_lv 卡牌等级
|
|
||||||
*/
|
|
||||||
private async handleSingleSummon(uuid: number, hero_lv: number, card_lv: number = 1) {
|
|
||||||
const existing = this.findHeroByUuid(uuid);
|
|
||||||
if (!existing) {
|
|
||||||
this.addHero(uuid, hero_lv, card_lv);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldModel = existing.get(HeroAttrsComp);
|
|
||||||
const oldLv = oldModel?.lv ?? 1;
|
|
||||||
// 合成后等级 = 两者最高等级 + 1,封顶 HERO_MAX_LV
|
|
||||||
const mergedLv = Math.min(FightSet.HERO_MAX_LV, Math.max(oldLv, hero_lv) + 1);
|
|
||||||
// 继承旧英雄站位,避免新英雄重新抢位导致阵型跳动
|
|
||||||
const posIndex = oldModel?.posIndex ?? -1;
|
|
||||||
|
|
||||||
await this.mergeHeroAndRespawn(existing, uuid, mergedLv, card_lv, posIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 二合一合成流程:
|
|
||||||
* 1. 旧英雄播放移动合并动画并销毁(释放站位与实体)。
|
|
||||||
* 2. 在原站位生成 mergedLv 级新英雄并播放入场动画。
|
|
||||||
*
|
|
||||||
* @param oldHero 场上已有的同 uuid 英雄
|
|
||||||
* @param uuid 英雄 UUID
|
|
||||||
* @param mergedLv 合成后的英雄等级
|
|
||||||
* @param card_lv 卡牌等级
|
|
||||||
* @param posIndex 继承的站位索引(-1 表示重新分配)
|
|
||||||
*/
|
|
||||||
private mergeHeroAndRespawn(oldHero: Hero, uuid: number, mergedLv: number, card_lv: number, posIndex: number): Promise<void> {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
// 以旧英雄节点位置作为合并目标点,保证动画收拢到原站位
|
|
||||||
const oldView = oldHero.get(HeroViewComp);
|
|
||||||
const oldPos = oldView?.node?.isValid ? oldView.node.getPosition() : null;
|
|
||||||
const birthPos = oldPos ?? MissionHeroComp.HERO_ROW_MID;
|
|
||||||
|
|
||||||
oldHero.mergeToBirthAndDestroy(birthPos, () => {
|
|
||||||
// 旧实体销毁后沿用其站位与 posIndex 生成新英雄,避免阵型跳动
|
|
||||||
this.spawnHeroAt(uuid, mergedLv, card_lv, posIndex, oldPos);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** ECS 组件移除时触发(当前不销毁节点,保留引用) */
|
/** ECS 组件移除时触发(当前不销毁节点,保留引用) */
|
||||||
reset() {
|
reset() {
|
||||||
// this.node.destroy();
|
// this.node.destroy();
|
||||||
|
|||||||
Reference in New Issue
Block a user