3 Commits

Author SHA1 Message Date
panFD
8818ec9c3f refactor(card): 升级动态升级卡的英雄绑定逻辑
将动态升级卡从绑定英雄UUID改为绑定实体EID,适配ECS实体管理模式:
1. 更新CardConfig定义,替换target_hero_uuid为target_hero_eid
2. 新增EID反查UUID的工具方法,适配渲染和交互逻辑
3. 修改抽卡、卡牌点击和使用的全流程逻辑
4. 优化升级卡生成和匹配的稳定性
2026-07-21 00:03:44 +08:00
panFD
48e0ab06c5 refactor(monster&cast): 重构怪物移动与AOE技能目标逻辑
1. 移除固定网格站位,怪物从右侧统一出生并向左推进
2. 修改AOE技能索敌逻辑,直接使用最近敌人真实位置
3. 新增怪物自动推进与战斗停船逻辑,优化战斗体验
2026-07-20 23:20:24 +08:00
panFD
05dc771d4e fix: 调整游戏战斗站位与UI显示参数
1. 修正游戏地平线GAME_LINE数值,调整英雄和怪物的出生站位
2. 修改shadow预制体的缩放、尺寸和填充模式参数
3. 调整地图预制体的元素位置和尺寸
4. 优化技能动画的缩放参数和编辑器冗余配置
2026-07-20 22:52:43 +08:00
12 changed files with 216 additions and 184 deletions

View File

@@ -52,8 +52,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 1, "x": 1.6,
"y": 0.3, "y": 0.2,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,
@@ -130,8 +130,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 284, "width": 200,
"height": 39 "height": 20
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -172,7 +172,7 @@
}, },
"_type": 0, "_type": 0,
"_fillType": 0, "_fillType": 0,
"_sizeMode": 1, "_sizeMode": 0,
"_fillCenter": { "_fillCenter": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
@@ -241,8 +241,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.3, "x": 0.2,
"y": 0.3, "y": 0.2,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,

View File

@@ -2480,7 +2480,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": 180, "y": 183.281,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -2521,7 +2521,7 @@
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 960, "width": 960,
"height": 200 "height": 270
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -2596,7 +2596,7 @@
"_left": 0, "_left": 0,
"_right": 0, "_right": 0,
"_top": 0, "_top": 0,
"_bottom": 230, "_bottom": 233.281,
"_horizontalCenter": 0, "_horizontalCenter": 0,
"_verticalCenter": 0, "_verticalCenter": 0,
"_isAbsLeft": true, "_isAbsLeft": true,
@@ -11766,6 +11766,8 @@
"__id__": 0 "__id__": 0
}, },
"fileId": "8aR4+WXvRJrJfyR9Sp7SqP", "fileId": "8aR4+WXvRJrJfyR9Sp7SqP",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
}, },
{ {
@@ -11908,7 +11910,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": 180, "y": 183.281,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -11949,7 +11951,7 @@
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 960, "width": 960,
"height": 200 "height": 270
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -12024,7 +12026,7 @@
"_left": 0, "_left": 0,
"_right": 0, "_right": 0,
"_top": 0, "_top": 0,
"_bottom": 230, "_bottom": 233.281,
"_horizontalCenter": 0, "_horizontalCenter": 0,
"_verticalCenter": 0, "_verticalCenter": 0,
"_isAbsLeft": true, "_isAbsLeft": true,
@@ -12188,6 +12190,8 @@
"__id__": 0 "__id__": 0
}, },
"fileId": "20WNwIV7RJjrst2VhRbe8x", "fileId": "20WNwIV7RJjrst2VhRbe8x",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
}, },
{ {

View File

@@ -392,43 +392,37 @@
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.30000001192092896, "value": 0.20000000298023224,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.4000000059604645, "value": 0.25,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.30000001192092896, "value": 0.20000000298023224,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
} }
], ],
"preExtrapolation": 1, "preExtrapolation": 1,
@@ -452,43 +446,37 @@
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.30000001192092896, "value": 0.20000000298023224,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.4000000059604645, "value": 0.25,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
"interpolationMode": 0, "interpolationMode": 0,
"tangentWeightMode": 0, "tangentWeightMode": 0,
"value": 0.30000001192092896, "value": 0.20000000298023224,
"rightTangent": 0, "rightTangent": 0,
"rightTangentWeight": 1, "rightTangentWeight": 1,
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
} }
], ],
"preExtrapolation": 1, "preExtrapolation": 1,
@@ -518,9 +506,7 @@
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
@@ -532,9 +518,7 @@
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
}, },
{ {
"__type__": "cc.RealKeyframeValue", "__type__": "cc.RealKeyframeValue",
@@ -546,9 +530,7 @@
"leftTangent": 0, "leftTangent": 0,
"leftTangentWeight": 1, "leftTangentWeight": 1,
"easingMethod": 0, "easingMethod": 0,
"__editorExtras__": { "__editorExtras__": null
"tangentMode": 0
}
} }
], ],
"preExtrapolation": 1, "preExtrapolation": 1,

View File

@@ -110,11 +110,11 @@ export interface CardConfig {
trigger_limit?: number; trigger_limit?: number;
/** /**
* 动态升级卡专属:目标英雄 UUID * 动态升级卡专属:目标英雄实体 eid运行时唯一
* 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。 * 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。
* 使用该卡时精确升级场上对应 UUID 的英雄。 * 使用该卡时精确升级场上对应 eid 的英雄实体
*/ */
target_hero_uuid?: number; target_hero_eid?: number;
} }
/** 升级卡折扣表(已废弃,保留以兼容历史引用) */ /** 升级卡折扣表(已废弃,保留以兼容历史引用) */
@@ -274,7 +274,7 @@ export enum SpecialRefreshHeroType {
export interface SpecialUpgradeCardConfig extends CardConfig { export interface SpecialUpgradeCardConfig extends CardConfig {
name: string name: string
info: string info: string
/** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_uuid 对应的英雄决定) */ /** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_eid 对应的英雄决定) */
currentLv: number currentLv: number
/** 模板字段targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1 */ /** 模板字段targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1 */
targetLv: number targetLv: number
@@ -292,7 +292,7 @@ export interface SpecialRefreshCardConfig extends CardConfig {
* 升级卡模板。 * 升级卡模板。
* 仅有 7001 一条记录作为动态升级卡的基础配置cost/weight/name/info * 仅有 7001 一条记录作为动态升级卡的基础配置cost/weight/name/info
* 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig * 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig
* 通过 target_hero_uuid 字段绑定具体英雄。 * 通过 target_hero_eid 字段绑定具体英雄实体
*/ */
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = { export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: { 7001: {

View File

@@ -13,7 +13,7 @@ export enum BoxSet {
LETF_END = -360, LETF_END = -360,
RIGHT_END = 360, RIGHT_END = 360,
//游戏地平线 //游戏地平线
GAME_LINE = -90, GAME_LINE = 80,
} }
export enum FacSet { export enum FacSet {

View File

@@ -2,8 +2,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { HeroViewComp } from "./HeroViewComp"; import { HeroViewComp } from "./HeroViewComp";
import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroAttrsComp } from "./HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { FacSet } from "../common/config/GameSet"; import { BoxSet, FacSet } from "../common/config/GameSet";
import { HeroDisVal, HType } from "../common/config/heroSet";
import { Node } from "cc"; import { Node } from "cc";
@ecs.register('MonMoveComp') @ecs.register('MonMoveComp')
@@ -30,6 +29,8 @@ export class MonMoveComp extends ecs.Comp {
@ecs.register('MonMoveSystem') @ecs.register('MonMoveSystem')
export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
/** 怪物接近英雄后的停船距离:小于该阈值即停下交战 */
private readonly combatHaltDistance = 80;
/** 渲染层级重排节流,避免每帧排序 */ /** 渲染层级重排节流,避免每帧排序 */
private readonly renderSortInterval = 0.05; private readonly renderSortInterval = 0.05;
private lastRenderSortAt = 0; private lastRenderSortAt = 0;
@@ -81,25 +82,22 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
} }
/** 所有移动都锁定在 baseY避免出现“漂移” */ /** 所有移动都锁定在 baseY避免出现“漂移” */
// 注意:不再在 MonMoveComp 强行重置 X 轴坐标,避免与其他表现逻辑冲突
if (view.node.position.y !== move.baseY) { if (view.node.position.y !== move.baseY) {
view.node.setPosition(view.node.position.x, move.baseY, 0); view.node.setPosition(view.node.position.x, move.baseY, 0);
} }
// 渲染层级统交由 MoveSystem 统一处理,避免两个 System 争抢 setSiblingIndex // 渲染层级统交由 MoveSystem 统一处理,避免两个 System 争抢 setSiblingIndex
// 仅在战斗中才处理索敌 // 仅在战斗中才处理索敌;非战斗阶段也允许向左推进,让怪物自然逼近英雄阵地
if (!smc.mission.in_fight) return; const nearestEnemy = smc.mission.in_fight ? this.findNearestEnemy(e) : null;
const nearestEnemy = this.findNearestEnemy(e);
if (nearestEnemy) { if (nearestEnemy) {
/** 有敌人:进入固定位置攻击逻辑 */ /** 有敌人:边移动边攻击 */
this.processCombatLogic(e, move, view, model, nearestEnemy); this.processCombatLogic(e, move, view, model, nearestEnemy);
this.syncCombatTarget(model, view, nearestEnemy); this.syncCombatTarget(model, view, nearestEnemy);
} else { } else {
/** 无敌人:原地待机 */ /** 无敌人:清目标并继续向左推进 */
this.clearCombatTarget(model); this.clearCombatTarget(model);
model.is_atking = false; model.is_atking = false;
view.status_change("idle"); this.moveLeft(view, move, model);
} }
} }
@@ -131,17 +129,44 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) { private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
const selfX = view.node.position.x; const selfX = view.node.position.x;
const enemyX = enemy.node.position.x; const enemyX = enemy.node.position.x;
const inRange = this.isEnemyInAttackRange(model, selfX, enemyX); // 停船判定使用固定阈值80不依赖技能攻击范围
const inRange = Math.abs(selfX - enemyX) <= this.combatHaltDistance;
// 攻击判定:怪物在固定位置,如果在攻击范围内则攻击,否则原地待机
if (inRange) { if (inRange) {
// 接触到英雄方:停止移动,进入攻击状态,由 SCastSystem 负责技能释放
model.is_atking = true; model.is_atking = true;
// 确保朝向敌人
const dir = enemyX > selfX ? 1 : -1; const dir = enemyX > selfX ? 1 : -1;
view.scale = dir; view.scale = dir;
if (view.status === "move") {
view.status_change("idle");
}
} else { } else {
// 未接触:继续向左推进
model.is_atking = false; model.is_atking = false;
view.status_change("idle"); this.moveLeft(view, move, model);
}
}
/**
* 持续向左推进:怪物不再固定站位,而是以 speed/3 的速度向左移动,
* 直到抵达左边界(基地位置)后转入待机,由 SCastSystem 继续负责技能释放。
*/
private moveLeft(view: HeroViewComp, move: MonMoveComp, model: HeroAttrsComp) {
const currentX = view.node.position.x;
if (currentX <= BoxSet.LETF_END) {
if (view.status !== "atk") {
view.status_change("idle");
}
return;
}
const dir = -1;
move.direction = dir;
const speed = model.speed / 3;
const delta = speed * this.dt * dir;
const newX = Math.max(BoxSet.LETF_END, currentX + delta);
if (Math.abs(newX - currentX) >= 0.01) {
view.node.setPosition(newX, view.node.position.y, 0);
view.status_change("move");
} }
} }

View File

@@ -13,7 +13,6 @@ import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType } from "../common/config/heroSet"; import { SkillTriggerType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper"; import { SkillTriggerHelper } from "./SkillTriggerHelper";
import { MissionEconomy } from "../map/MissionEconomy"; import { MissionEconomy } from "../map/MissionEconomy";
import { MissionMonCompComp } from "../map/MissionMonComp";
import { MissionHeroComp } from "../map/MissionHeroComp"; import { MissionHeroComp } from "../map/MissionHeroComp";
/** /**
@@ -695,25 +694,13 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (config.DTType === DTType.aoe_grid) { if (config.DTType === DTType.aoe_grid) {
const isHero = heroAttrs.fac === FacSet.HERO; const isHero = heroAttrs.fac === FacSet.HERO;
if (isHero) { if (isHero) {
// 英雄打怪物目标为怪物网格的第2列(idx 1)中路(row 1)或第3列(idx 2)中路 // 怪物已改为从 X=400 出生后动态向左推进,不再有固定列阵型。
// MissionMonComp.MON_POSITIONS 顺序: // aoe_grid 直接以最近敌人的真实位置作为目标点,
// 列1: 0,1,2 (X=60) // 由技能自身的范围半径负责命中附近的怪物集群。
// 列2: 3,4,5 (X=140) -> 第2列中路是 index 4 if (nearestEnemy?.node) {
// 列3: 6,7,8 (X=220) -> 第3列中路是 index 7 return nearestEnemy.node.position.clone();
// 简单起见可以固定取第2列中路(index 4)或第3列中路(index 7)
// 若最近的敌人在更后排,也可以取那个敌人的列。
// 需求:三类 需要以中路 第 2、 3列为目标。
// 我们直接返回第2列中路坐标(X=140, Y=GAME_LINE)或者根据最近敌人判断。
// 需求说明为第 2 或 第 3 列这里我们可以根据目标位置如果目标在第3列及以后打第3列否则打第2列。
let targetCol = 1; // 0-based, so 1 is 2nd col
if (nearestEnemy && nearestEnemy.node) {
// 判断目标所在的列
const ex = nearestEnemy.node.position.x;
// X=60(列1), X=140(列2), X=220(列3), X=300(列4)
if (ex >= 200) targetCol = 2; // 第3列
} }
const targetIdx = targetCol * 3 + 1; // 1是中路 return null;
return MissionMonCompComp.MON_POSITIONS[targetIdx].clone();
} else { } else {
// 怪物打英雄,英雄网格: // 怪物打英雄,英雄网格:
// 列1: 0,1,2 (X=-210) // 列1: 0,1,2 (X=-210)

View File

@@ -526,9 +526,12 @@ export class CardComp extends CCComp {
this.showCallBtn(); this.showCallBtn();
oops.message.dispatchEvent(GameEvent.CardSelected, this); oops.message.dispatchEvent(GameEvent.CardSelected, this);
// 英雄卡 / 升级卡打开信息面板(点击替代长按) // 英雄卡 / 升级卡打开信息面板(点击替代长按)
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_uuid; const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_eid;
if (this.card_type === CardType.Hero || isUpgradeCard) { if (this.card_type === CardType.Hero || isUpgradeCard) {
const heroUuid = isUpgradeCard ? (this.cardData!.target_hero_uuid as number) : this.card_uuid; // 升级卡通过 eid 反查实体得到 hero_uuid若实体已不存在则降级为本卡 uuid
const heroUuid = isUpgradeCard
? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid)
: this.card_uuid;
const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1); const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1);
const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1); const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1);
oops.gui.remove(UIID.HInfo); oops.gui.remove(UIID.HInfo);
@@ -536,6 +539,20 @@ export class CardComp extends CCComp {
} }
} }
/**
* 通过 eid 反查场上英雄实体的 hero_uuid。
* 用于动态升级卡渲染:升级卡绑定的是 eid但显示需要 HeroInfo[uuid]。
* @returns 找到则返回 hero_uuid找不到实体已销毁等返回 null
*/
private queryHeroUuidByEid(eid: number): number | null {
if (!eid) return null;
const entity = ecs.getEntityByEid(eid);
if (!entity) return null;
const model = entity.get(HeroAttrsComp);
if (!model) return null;
return model.hero_uuid ?? null;
}
/** /**
* 其他卡牌被点击时联动隐藏本卡 call_btn。 * 其他卡牌被点击时联动隐藏本卡 call_btn。
* @param event 事件名ListenerFunc 约定的第一个参数) * @param event 事件名ListenerFunc 约定的第一个参数)
@@ -639,11 +656,14 @@ export class CardComp extends CCComp {
this.node.setPosition(this.restPosition.x, this.restPosition.y, this.restPosition.z); this.node.setPosition(this.restPosition.x, this.restPosition.y, this.restPosition.z);
// 渲染参数动态升级卡SpecialUpgrade + target_hero_uuid按英雄卡样式渲染 // 渲染参数动态升级卡SpecialUpgrade + target_hero_eid按英雄卡样式渲染
// 使用 target_hero_uuid 对应的英雄信息(名称/AP/HP/动画),等级显示为目标等级 // 通过 target_hero_eid 反查实体得到 hero_uuid再用 HeroInfo 渲染名称/AP/HP/动画
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_uuid; const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_eid;
const renderType: CardType = isUpgradeCard ? CardType.Hero : this.card_type; const renderType: CardType = isUpgradeCard ? CardType.Hero : this.card_type;
const renderUuid: number = isUpgradeCard ? (this.cardData.target_hero_uuid as number) : this.card_uuid; // 升级卡通过 eid 反查实体得到 hero_uuid若实体已不存在则降级为本卡 uuid
const renderUuid: number = isUpgradeCard
? (this.queryHeroUuidByEid(this.cardData.target_hero_eid as number) ?? this.card_uuid)
: this.card_uuid;
const renderKind: CKind = isUpgradeCard ? CKind.Hero : this.cardData.kind; const renderKind: CKind = isUpgradeCard ? CKind.Hero : this.cardData.kind;
// ---- 卡牌种类标识(近战 / 远程 / 辅助等) ---- // ---- 卡牌种类标识(近战 / 远程 / 辅助等) ----

View File

@@ -246,9 +246,10 @@ export class CardLiteComp extends CCComp {
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`); this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
} else { } else {
if (this.lvl_node) this.lvl_node.node.active = false; if (this.lvl_node) this.lvl_node.node.active = false;
// 动态升级卡:显示对应英雄名 + "升级"后缀 // 动态升级卡:显示对应英雄名 + "升级"后缀(注意:升级卡绑定的是 target_hero_eid 而非 uuid
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) { // 此轻量列表场景通常不展示升级卡;为保持渲染健壮性,回退到本卡 uuid 查 HeroInfo
const targetHero = HeroInfo[this.cardData.target_hero_uuid]; if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_eid) {
const targetHero = HeroInfo[this.card_uuid];
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`); this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
} else { } else {
const specialCard = this.card_type === CardType.SpecialUpgrade const specialCard = this.card_type === CardType.SpecialUpgrade

View File

@@ -540,8 +540,8 @@ export class MissionCardComp extends CCComp {
/** /**
* 使用特殊卡事件回调: * 使用特殊卡事件回调:
* - SpecialUpgrade按卡牌携带的 target_hero_uuid 精确升级场上对应英雄。 * - SpecialUpgrade按卡牌携带的 target_hero_eid 精确升级场上对应英雄实体
* target_hero_uuid 缺失时回退为随机升级一个可升级的英雄 * target_hero_eid 缺失或实体不存在时升级失败
* - SpecialRefresh按英雄类型重新抽取英雄卡。 * - SpecialRefresh按英雄类型重新抽取英雄卡。
*/ */
private onUseSpecialCard(event: string, args: any) { private onUseSpecialCard(event: string, args: any) {
@@ -553,8 +553,8 @@ export class MissionCardComp extends CCComp {
if (type === CardType.SpecialUpgrade) { if (type === CardType.SpecialUpgrade) {
const template = SpecialUpgradeCardList[uuid]; const template = SpecialUpgradeCardList[uuid];
if (!template) return; if (!template) return;
const targetHeroUuid = Number(payload?.target_hero_uuid ?? 0); const targetHeroEid = Number(payload?.target_hero_eid ?? 0);
success = this.tryUpgradeHeroByUuid(targetHeroUuid); success = this.tryUpgradeHeroByEid(targetHeroEid);
if (!success) { if (!success) {
oops.gui.toast(`场上没有可升级的英雄`); oops.gui.toast(`场上没有可升级的英雄`);
} }
@@ -735,7 +735,7 @@ export class MissionCardComp extends CCComp {
* *
* 抽卡规则: * 抽卡规则:
* 1. **必出规则**:当场上有可升级英雄时,每次刷新必出 1 张升级卡,并放在返回数组首位(对应卡池最左侧槽位)。 * 1. **必出规则**:当场上有可升级英雄时,每次刷新必出 1 张升级卡,并放在返回数组首位(对应卡池最左侧槽位)。
* 多次刷新时,按场上英雄 target_hero_uuid 升序循环切换升级卡upgradeRotationIndex * 多次刷新时,按场上英雄 target_hero_eid 升序循环切换升级卡upgradeRotationIndex
* 2. 其余 2 张从混合池(剩余升级卡 + 基础英雄卡)按权重抽取。 * 2. 其余 2 张从混合池(剩余升级卡 + 基础英雄卡)按权重抽取。
* *
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,全部出升级卡(若全部已满级则降级回混合池)。 * 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,全部出升级卡(若全部已满级则降级回混合池)。
@@ -772,15 +772,15 @@ export class MissionCardComp extends CCComp {
this.upgradeRotationIndex = (this.upgradeRotationIndex + 1) % upgradeCards.length; this.upgradeRotationIndex = (this.upgradeRotationIndex + 1) % upgradeCards.length;
const mandatory = upgradeCards[idx]; const mandatory = upgradeCards[idx];
result.push(mandatory); result.push(mandatory);
usedUpgradeTargets.add(mandatory.target_hero_uuid ?? 0); usedUpgradeTargets.add(mandatory.target_hero_eid ?? 0);
} }
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_uuid // 剩余张数从混合池抽取,排除已选升级卡 target_hero_eid
const remainingCount = 3 - result.length; const remainingCount = 3 - result.length;
if (remainingCount > 0) { if (remainingCount > 0) {
const remainingPool = mixedPool.filter(c => { const remainingPool = mixedPool.filter(c => {
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) { if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
return !usedUpgradeTargets.has(c.target_hero_uuid); return !usedUpgradeTargets.has(c.target_hero_eid);
} }
return true; return true;
}); });
@@ -798,7 +798,7 @@ export class MissionCardComp extends CCComp {
/** /**
* 从混合池中按权重抽取 n 张卡。 * 从混合池中按权重抽取 n 张卡。
* 升级卡之间强制 unique同一个 target_hero_uuid 只能出现一次)。 * 升级卡之间强制 unique同一个 target_hero_eid 只能出现一次)。
*/ */
private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] { private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] {
if (pool.length === 0 || count <= 0) return []; if (pool.length === 0 || count <= 0) return [];
@@ -807,8 +807,8 @@ export class MissionCardComp extends CCComp {
while (selected.length < count) { while (selected.length < count) {
const available = pool.filter(c => { const available = pool.filter(c => {
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) { if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
return !usedUpgradeTargets.has(c.target_hero_uuid); return !usedUpgradeTargets.has(c.target_hero_eid);
} }
return true; return true;
}); });
@@ -816,8 +816,8 @@ export class MissionCardComp extends CCComp {
const pick = this.weightedPick(available); const pick = this.weightedPick(available);
if (!pick) break; if (!pick) break;
selected.push(pick); selected.push(pick);
if (pick.type === CardType.SpecialUpgrade && pick.target_hero_uuid) { if (pick.type === CardType.SpecialUpgrade && pick.target_hero_eid) {
usedUpgradeTargets.add(pick.target_hero_uuid); usedUpgradeTargets.add(pick.target_hero_eid);
} }
} }
return selected; return selected;
@@ -836,9 +836,9 @@ export class MissionCardComp extends CCComp {
} }
/** /**
* 扫描场上存活英雄,为每个 UUID 至多生成一张升级卡 * 扫描场上存活英雄,为每个英雄实体生成一张升级卡(按 eid 绑定)
* - 已达 HERO_MAX_LV 的英雄不出卡。 * - 已达 HERO_MAX_LV 的英雄不出卡。
* - 同 UUID 多个英雄只生成一张(取等级最低的那个作为升级目标)。 * - 同 eid 只生成一张eid 本就唯一,不会重复)。
* - cost = 模板 cost + (当前等级 - 1) * BASE_COST等级越高升级越贵。 * - cost = 模板 cost + (当前等级 - 1) * BASE_COST等级越高升级越贵。
*/ */
private buildHeroUpgradeCards(): CardConfig[] { private buildHeroUpgradeCards(): CardConfig[] {
@@ -848,31 +848,22 @@ export class MissionCardComp extends CCComp {
const actors = this.queryAliveHeroActors(); const actors = this.queryAliveHeroActors();
if (actors.length === 0) return []; if (actors.length === 0) return [];
// 同 UUID 取等级最低的英雄
const heroMap = new Map<number, { uuid: number, lv: number }>();
for (const actor of actors) {
const uuid = actor.model.hero_uuid;
const lv = actor.model.lv;
const existing = heroMap.get(uuid);
if (!existing || lv < existing.lv) {
heroMap.set(uuid, { uuid, lv });
}
}
const result: CardConfig[] = []; const result: CardConfig[] = [];
heroMap.forEach(({ uuid, lv }) => { for (const actor of actors) {
if (lv >= FightSet.HERO_MAX_LV) return; // 已达上限不再出升级卡 const eid = actor.eid;
const lv = actor.model.lv;
if (lv >= FightSet.HERO_MAX_LV) continue; // 已达上限不再出升级卡
const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST; const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST;
result.push({ result.push({
...template, ...template,
cost: dynamicCost, cost: dynamicCost,
weight: template.weight, weight: template.weight,
target_hero_uuid: uuid, target_hero_eid: eid,
hero_lv: lv + 1, hero_lv: lv + 1,
}); });
}); }
// 按 target_hero_uuid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询) // 按 target_hero_eid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
result.sort((a, b) => (a.target_hero_uuid ?? 0) - (b.target_hero_uuid ?? 0)); result.sort((a, b) => (a.target_hero_eid ?? 0) - (b.target_hero_eid ?? 0));
return result; return result;
} }
@@ -1077,39 +1068,34 @@ export class MissionCardComp extends CCComp {
return count; return count;
} }
private queryAliveHeroActors(): Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> { private queryAliveHeroActors(): Array<{ eid: number, model: HeroAttrsComp, view: HeroViewComp | null }> {
const actors: Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> = []; const actors: Array<{ eid: number, model: HeroAttrsComp, view: HeroViewComp | null }> = [];
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => { ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp); const model = entity.get(HeroAttrsComp);
if (!model) return; if (!model) return;
if (model.fac !== FacSet.HERO) return; if (model.fac !== FacSet.HERO) return;
if (model.is_dead) return; if (model.is_dead) return;
const view = entity.get(HeroViewComp); const view = entity.get(HeroViewComp);
actors.push({ model, view }); actors.push({ eid: entity.eid, model, view });
}); });
return actors; return actors;
} }
/** /**
* 按英雄 UUID 精确升级一个存活英雄 * 按 eid 精确升级场上对应英雄实体
* 若同 UUID 多个英雄,取等级最低且未达上限的;若都达上限则失败。
* *
* @param heroUuid 要升级的英雄 UUID * @param heroEid 要升级的英雄实体 eid
* @returns true = 升级成功 * @returns true = 升级成功
*/ */
private tryUpgradeHeroByUuid(heroUuid: number): boolean { private tryUpgradeHeroByEid(heroEid: number): boolean {
if (!heroUuid) return false; if (!heroEid) return false;
const candidates = this.queryAliveHeroActors().filter(item => const actor = this.queryAliveHeroActors().find(item => item.eid === heroEid);
item.model.hero_uuid === heroUuid && item.model.lv < FightSet.HERO_MAX_LV if (!actor) return false;
); if (actor.model.lv >= FightSet.HERO_MAX_LV) return false;
if (candidates.length === 0) return false; const nextLv = Math.min(FightSet.HERO_MAX_LV, actor.model.lv + 1);
// 取等级最低的(先升级低等级英雄) this.applyHeroLevel(actor.model, nextLv);
candidates.sort((a, b) => a.model.lv - b.model.lv); if (actor.view) {
const target = candidates[0]; actor.view.playBuff("buff_lvup");
const nextLv = Math.min(FightSet.HERO_MAX_LV, target.model.lv + 1);
this.applyHeroLevel(target.model, nextLv);
if (target.view) {
target.view.playBuff("buff_lvup");
} }
return true; return true;
} }

View File

@@ -49,12 +49,12 @@ export class MissionHeroComp extends CCComp {
/** 硬编码的6个英雄占位点 */ /** 硬编码的6个英雄占位点 */
public static readonly HERO_POSITIONS: Vec3[] = [ public static readonly HERO_POSITIONS: Vec3[] = [
v3(-175, BoxSet.GAME_LINE + 100, 0), // index 0 (node_index 1): Top Front v3(-180, BoxSet.GAME_LINE, 0), // index 0 (node_index 1): Top Front 第二
v3(-170, BoxSet.GAME_LINE, 0), // index 1 (node_index 2): Mid Front v3(-80, 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, 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 + 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, 0), // index 4 (node_index 5): Mid Back
v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back // v3(-280, BoxSet.GAME_LINE - 100, 0), // index 5 (node_index 6): Bot Back
]; ];
/** 英雄出生时的掉落高度(从空中落到地面的像素差) */ /** 英雄出生时的掉落高度(从空中落到地面的像素差) */

View File

@@ -4,11 +4,12 @@
* *
* 职责: * 职责:
* 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。 * 1. 管理每一波怪物的生成计划:根据 RogueConfig 生成怪物。
* 2. 自动推进波次在准备阶段结束时PhasePrepareEnd统一刷出怪物 * 2. 自动推进波次在准备阶段结束时PhasePrepareEnd把怪物转入逐个刷出队列
* *
* 关键设计: * 关键设计:
* - 采用 12 个硬编码的网格位置点 (MON_POSITIONS3行x4列) * - 所有怪物统一从右侧 X=400 出生点逐个刷出MON_SPAWN_INTERVAL 节奏控制)。
* - 每次生成最多 12 个怪物,固定在位置点。 * - 实际阵型与推进由 MonMoveComp 在战斗中向左移动自然形成,不再使用固定网格点。
* - 槽位索引3行×4列仅用于 monGrid 寻路与 SCastSystem 索敌定位。
* - 上一波残留怪在波次结束/开始时统一清理。 * - 上一波残留怪在波次结束/开始时统一清理。
*/ */
import { _decorator, v3, Vec3 } from "cc"; import { _decorator, v3, Vec3 } from "cc";
@@ -35,25 +36,29 @@ export class MissionMonCompComp extends CCComp {
private static readonly MAX_MONSTERS = 12; private static readonly MAX_MONSTERS = 12;
/** 怪物出生掉落高度 */ /** 怪物出生掉落高度 */
private static readonly MON_DROP_HEIGHT = 0; private static readonly MON_DROP_HEIGHT = 0;
/** 怪物统一从右侧 X=400 出生点逐个刷出,随后向左推进 */
private static readonly MON_SPAWN_X = 400;
/** 逐个刷怪间隔(秒):保证怪物排成纵队,避免堆叠 */
private static readonly MON_SPAWN_INTERVAL = 0.3;
/** 硬编码的 12 个怪物占位点 (3行4列) */ /**
* 12 个怪物占位点:所有槽位统一以右侧出生点 (X=400) 作为坐标,
* 实际阵型由 MonMoveComp 在战斗中向左推进时自然拉开。
* 槽位索引仍保留 3 行 × 4 列结构,供 monGrid 寻路与 SCastSystem 索敌使用。
*/
public static readonly MON_POSITIONS: Vec3[] = [ public static readonly MON_POSITIONS: Vec3[] = [
// 第 1 列 (X=60) v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 0: Col1-Top
v3(0, BoxSet.GAME_LINE + 100, 0), // index 0: Top v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 1: Col1-Mid
v3(0, BoxSet.GAME_LINE, 0), // index 1: Mid v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 2: Col1-Bot
v3(0, BoxSet.GAME_LINE - 100, 0), // index 2: Bot v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 3: Col2-Top
// 第 2 列 (X=140) v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 4: Col2-Mid
v3(90, BoxSet.GAME_LINE + 100, 0), // index 3: Top v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 5: Col2-Bot
v3(90, BoxSet.GAME_LINE, 0), // index 4: Mid v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 6: Col3-Top
v3(90, BoxSet.GAME_LINE - 100, 0), // index 5: Bot v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 7: Col3-Mid
// 第 3 列 (X=220) v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 8: Col3-Bot
v3(180, BoxSet.GAME_LINE + 100, 0), // index 6: Top v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 9: Col4-Top
v3(180, BoxSet.GAME_LINE, 0), // index 7: Mid v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 10: Col4-Mid
v3(180, BoxSet.GAME_LINE - 100, 0), // index 8: Bot v3(MissionMonCompComp.MON_SPAWN_X, BoxSet.GAME_LINE, 0), // index 11: Col4-Bot
// 第 4 列 (X=300)
v3(270, BoxSet.GAME_LINE + 100, 0), // index 9: Top
v3(270, BoxSet.GAME_LINE, 0), // index 10: Mid
v3(270, BoxSet.GAME_LINE - 100, 0), // index 11: Bot
]; ];
// ======================== 编辑器属性 ======================== // ======================== 编辑器属性 ========================
@@ -71,8 +76,12 @@ export class MissionMonCompComp extends CCComp {
private waveTargetCount: number = 0; private waveTargetCount: number = 0;
/** 当前波已生成的怪物数量 */ /** 当前波已生成的怪物数量 */
private waveSpawnedCount: number = 0; private waveSpawnedCount: number = 0;
/** 等待生成的怪物队列 */ /** 等待生成的怪物队列(波次总池) */
private pendingMonsters: GeneratedMonster[] = []; private pendingMonsters: GeneratedMonster[] = [];
/** 逐个刷怪队列:从 pendingMonsters 转入,按节奏在 update 中释放 */
private spawnQueue: GeneratedMonster[] = [];
/** 逐个刷怪累计计时(秒) */
private spawnTimer: number = 0;
// ======================== 生命周期 ======================== // ======================== 生命周期 ========================
@@ -83,7 +92,20 @@ export class MissionMonCompComp extends CCComp {
} }
protected update(dt: number): void { protected update(dt: number): void {
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length; // pending_mon_num 同时统计未刷出的总池与正在释放的队列,确保 UI 与全灭判定一致
smc.vmdata.mission_data.pending_mon_num = this.pendingMonsters.length + this.spawnQueue.length;
// 逐个刷怪:按 MON_SPAWN_INTERVAL 节奏从队列释放
if (this.spawnQueue.length > 0) {
this.spawnTimer += dt;
if (this.spawnTimer >= MissionMonCompComp.MON_SPAWN_INTERVAL) {
this.spawnTimer = 0;
const monData = this.spawnQueue.shift()!;
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
this.waveSpawnedCount++;
}
}
} }
start() {} start() {}
@@ -115,6 +137,8 @@ export class MissionMonCompComp extends CCComp {
this.waveTargetCount = 0; this.waveTargetCount = 0;
this.waveSpawnedCount = 0; this.waveSpawnedCount = 0;
this.pendingMonsters = []; this.pendingMonsters = [];
this.spawnQueue = [];
this.spawnTimer = 0;
// 预生成第一波数据以获取数量和 Boss 信息 // 预生成第一波数据以获取数量和 Boss 信息
const monsters = spawningEngine.generateWave(this.currentWave); const monsters = spawningEngine.generateWave(this.currentWave);
@@ -143,23 +167,23 @@ export class MissionMonCompComp extends CCComp {
private onPhasePrepareEnd() { private onPhasePrepareEnd() {
this.resetSlotSpawnData(); this.resetSlotSpawnData();
// 准备结束阶段,立即刷出本波所有怪物 // 准备结束阶段:把本波待刷怪物转入逐个释放队列,
// 实际生成时机由 update() 按 MON_SPAWN_INTERVAL 节奏触发,
// 让怪物从 X=400 出生点排成纵队依次向左推进。
if (this.pendingMonsters.length > 0) { if (this.pendingMonsters.length > 0) {
let count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS); const count = Math.min(this.pendingMonsters.length, MissionMonCompComp.MAX_MONSTERS);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const monData = this.pendingMonsters.shift()!; this.spawnQueue.push(this.pendingMonsters.shift()!);
const targetPosIndex = this.waveSpawnedCount % MissionMonCompComp.MAX_MONSTERS;
this.addMonsterAtGrid(targetPosIndex, monData, this.currentWave);
this.waveSpawnedCount++;
} }
this.pendingMonsters = []; // 让首个怪物在下一帧立即刷出,避免额外延迟
this.spawnTimer = MissionMonCompComp.MON_SPAWN_INTERVAL;
} }
} }
// ======================== 槽位管理 ======================== // ======================== 槽位管理 ========================
/** /**
* 清理上一波残留怪物,并重置生成计数 * 清理上一波残留怪物,并重置生成计数与逐个刷怪队列
*/ */
private resetSlotSpawnData() { private resetSlotSpawnData() {
ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => { ecs.query(ecs.allOf(HeroAttrsComp)).forEach(e => {
@@ -170,6 +194,9 @@ export class MissionMonCompComp extends CCComp {
}); });
this.waveSpawnedCount = 0; this.waveSpawnedCount = 0;
// 同步丢弃上一波未释放的队列,避免与新一波混合
this.spawnQueue = [];
this.spawnTimer = 0;
} }
// ======================== 怪物生成 ======================== // ======================== 怪物生成 ========================