refactor(map): 重构关卡刷怪与计时逻辑,适配30波新流程
1. 替换原固定战斗倒计时为正向计时clearTime 2. 移除旧波次配置,改用spawningEngine生成自适应怪物 3. 将波次上限从20调整为30,更新对应判断逻辑 4. 实现增量分批刷怪和自适应难度调整 5. 重置战斗状态时重置新引擎实例
This commit is contained in:
@@ -46,6 +46,7 @@ import { Tooltip } from "../skill/Tooltip";
|
|||||||
import { CardInitCoins } from "../common/config/CardSet";
|
import { CardInitCoins } from "../common/config/CardSet";
|
||||||
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||||||
import { FieldSkillType } from "../common/config/SkillSet";
|
import { FieldSkillType } from "../common/config/SkillSet";
|
||||||
|
import { spawningEngine } from "./RogueConfig";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/** 任务(关卡)生命周期阶段 */
|
/** 任务(关卡)生命周期阶段 */
|
||||||
@@ -118,8 +119,8 @@ export class MissionComp extends CCComp {
|
|||||||
|
|
||||||
// ======================== 运行时状态 ========================
|
// ======================== 运行时状态 ========================
|
||||||
|
|
||||||
/** 战斗倒计时(秒) */
|
/** 战斗已耗时(秒),正向计时 */
|
||||||
FightTime:number = FightSet.FiIGHT_TIME
|
clearTime:number = 0
|
||||||
/** 剩余复活次数 */
|
/** 剩余复活次数 */
|
||||||
revive_times: number = 1;
|
revive_times: number = 1;
|
||||||
/** 掉落奖励列表 */
|
/** 掉落奖励列表 */
|
||||||
@@ -234,14 +235,7 @@ export class MissionComp extends CCComp {
|
|||||||
this.syncMonsterSpawnState(dt)
|
this.syncMonsterSpawnState(dt)
|
||||||
if(smc.mission.stop_mon_action) return
|
if(smc.mission.stop_mon_action) return
|
||||||
smc.vmdata.mission_data.fight_time+=dt
|
smc.vmdata.mission_data.fight_time+=dt
|
||||||
if (!this.isBossWave) {
|
this.clearTime += dt
|
||||||
this.FightTime-=dt
|
|
||||||
if (this.FightTime <= 0) {
|
|
||||||
// 时间到了,自动结束战斗进入准备阶段
|
|
||||||
this.FightTime = FightSet.FiIGHT_TIME;
|
|
||||||
oops.message.dispatchEvent("TimeUpAdvanceWave");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.update_time();
|
this.update_time();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,29 +244,11 @@ export class MissionComp extends CCComp {
|
|||||||
|
|
||||||
/** 更新时间/波数显示(仅在秒数变化时更新以减少 Label 操作) */
|
/** 更新时间/波数显示(仅在秒数变化时更新以减少 Label 操作) */
|
||||||
update_time(){
|
update_time(){
|
||||||
if (this.isBossWave) {
|
const remainSecond = Math.floor(this.clearTime);
|
||||||
const str = "∞";
|
|
||||||
if (str != this.lastTimeStr) {
|
|
||||||
if (this.time_node && this.time_node.isValid) {
|
|
||||||
const timeChild = this.time_node.getChildByName("time");
|
|
||||||
if (timeChild) {
|
|
||||||
const label = timeChild.getComponent(Label);
|
|
||||||
if (label) label.string = str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.lastTimeStr = str;
|
|
||||||
this.lastTimeSecond = -1;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = Math.max(0, this.FightTime);
|
|
||||||
const remainSecond = Math.floor(time);
|
|
||||||
if (remainSecond === this.lastTimeSecond) return;
|
if (remainSecond === this.lastTimeSecond) return;
|
||||||
this.lastTimeSecond = remainSecond;
|
this.lastTimeSecond = remainSecond;
|
||||||
let m = Math.floor(remainSecond / 60);
|
let m = Math.floor(remainSecond / 60);
|
||||||
let s = remainSecond % 60;
|
let s = remainSecond % 60;
|
||||||
const wave = Math.max(1, this.currentWave || smc.vmdata.mission_data.level || 1);
|
|
||||||
let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||||
if(str != this.lastTimeStr){
|
if(str != this.lastTimeStr){
|
||||||
if (this.time_node && this.time_node.isValid) {
|
if (this.time_node && this.time_node.isValid) {
|
||||||
@@ -422,7 +398,7 @@ export class MissionComp extends CCComp {
|
|||||||
const label = phaseNode.getComponent(Label);
|
const label = phaseNode.getComponent(Label);
|
||||||
if (label) {
|
if (label) {
|
||||||
const wave = Math.max(1, this.currentWave || (smc.vmdata && smc.vmdata.mission_data ? smc.vmdata.mission_data.level : 1) || 1);
|
const wave = Math.max(1, this.currentWave || (smc.vmdata && smc.vmdata.mission_data ? smc.vmdata.mission_data.level : 1) || 1);
|
||||||
label.string = `第 ${wave}/20 波`;
|
label.string = `第 ${wave}/30 波`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阶段切换动感表现:只在进入战斗阶段跳动一下,让流程充满心流体验
|
// 阶段切换动感表现:只在进入战斗阶段跳动一下,让流程充满心流体验
|
||||||
@@ -497,8 +473,8 @@ export class MissionComp extends CCComp {
|
|||||||
smc.vmdata.scores.wave_all_alive_count++;
|
smc.vmdata.scores.wave_all_alive_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 【评分系统 - 战绩分】判断是否通过最后一关(第20回合)
|
// 【评分系统 - 战绩分】判断是否通过最后一关(第30回合)
|
||||||
if (this.currentWave === 20) {
|
if (this.currentWave === 30) {
|
||||||
smc.vmdata.scores.passed_wave_20 = true;
|
smc.vmdata.scores.passed_wave_20 = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -721,13 +697,13 @@ export class MissionComp extends CCComp {
|
|||||||
smc.mission.stop_spawn_mon = false;
|
smc.mission.stop_spawn_mon = false;
|
||||||
smc.vmdata.mission_data.in_fight=false
|
smc.vmdata.mission_data.in_fight=false
|
||||||
smc.vmdata.mission_data.fight_time=0
|
smc.vmdata.mission_data.fight_time=0
|
||||||
|
this.clearTime = 0
|
||||||
smc.vmdata.mission_data.mon_num=0
|
smc.vmdata.mission_data.mon_num=0
|
||||||
smc.vmdata.mission_data.level = 1
|
smc.vmdata.mission_data.level = 1
|
||||||
smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount))
|
smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount))
|
||||||
this.currentPhase = MissionPhase.None;
|
this.currentPhase = MissionPhase.None;
|
||||||
this.currentWave = 1;
|
this.currentWave = 1;
|
||||||
this.isBossWave = false;
|
this.isBossWave = false;
|
||||||
this.FightTime=FightSet.FiIGHT_TIME
|
|
||||||
this.rewards=[]
|
this.rewards=[]
|
||||||
this.revive_times = 1;
|
this.revive_times = 1;
|
||||||
this.lastTimeStr = "";
|
this.lastTimeStr = "";
|
||||||
@@ -744,6 +720,8 @@ export class MissionComp extends CCComp {
|
|||||||
this.monsterCountSyncTimer = 0;
|
this.monsterCountSyncTimer = 0;
|
||||||
this.lastPrepareCoinWave = 0;
|
this.lastPrepareCoinWave = 0;
|
||||||
|
|
||||||
|
spawningEngine.reset();
|
||||||
|
|
||||||
// 重置所有的战局得分数据,防止上一局的数据污染
|
// 重置所有的战局得分数据,防止上一局的数据污染
|
||||||
smc.resetScores();
|
smc.resetScores();
|
||||||
|
|
||||||
@@ -786,7 +764,7 @@ export class MissionComp extends CCComp {
|
|||||||
smc.vmdata.mission_data.level = wave;
|
smc.vmdata.mission_data.level = wave;
|
||||||
this.grantPrepareCoinByWave(wave);
|
this.grantPrepareCoinByWave(wave);
|
||||||
this.lastTimeSecond = -1;
|
this.lastTimeSecond = -1;
|
||||||
this.FightTime = FightSet.FiIGHT_TIME;
|
this.clearTime = 0;
|
||||||
this.update_time();
|
this.update_time();
|
||||||
|
|
||||||
// 检查并推送卡池升级事件
|
// 检查并推送卡池升级事件
|
||||||
@@ -877,8 +855,17 @@ export class MissionComp extends CCComp {
|
|||||||
|
|
||||||
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,直接结束战斗进入下一波的准备阶段
|
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,直接结束战斗进入下一波的准备阶段
|
||||||
if (monsterCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) {
|
if (monsterCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) {
|
||||||
this.FightTime = FightSet.FiIGHT_TIME;
|
let heroesAliveRatio = heroCount / 6.0; // 假设最大 6 个站位,或者直接基于存活数算比例
|
||||||
|
// 如果能获取当前已部署英雄数最好,这里简化处理,大于 4 个就算高存活
|
||||||
|
heroesAliveRatio = Math.min(1.0, heroCount / 4.0);
|
||||||
|
spawningEngine.updateAdaptive(heroesAliveRatio, this.clearTime);
|
||||||
|
|
||||||
|
if (this.currentWave >= 30) {
|
||||||
|
// 30 波通关
|
||||||
|
this.open_Victory(null, false);
|
||||||
|
} else {
|
||||||
oops.message.dispatchEvent("TimeUpAdvanceWave");
|
oops.message.dispatchEvent("TimeUpAdvanceWave");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { HeroInfo, HType } from "../common/config/heroSet";
|
|||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
import {BoxSet, FacSet } from "../common/config/GameSet";
|
import {BoxSet, FacSet } from "../common/config/GameSet";
|
||||||
import { MonList, MonType, SpawnPowerBias, StageBossGrow, StageGrow, UpType, WaveSlotConfig, DefaultWaveSlot, IWaveSlot } from "./RogueConfig";
|
import { spawningEngine, GeneratedMonster, AffixType, MonType, MonList } from "./RogueConfig";
|
||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
import { MonMoveComp } from "../hero/MonMoveComp";
|
import { MonMoveComp } from "../hero/MonMoveComp";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
@@ -94,6 +94,10 @@ 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 spawnTimer: number = 0;
|
||||||
|
|
||||||
// ======================== 生命周期 ========================
|
// ======================== 生命周期 ========================
|
||||||
|
|
||||||
@@ -108,14 +112,35 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
* 帧更新:
|
* 帧更新:
|
||||||
* 1. 检查游戏是否运行中。
|
* 1. 检查游戏是否运行中。
|
||||||
* 2. 处理插队刷怪队列。
|
* 2. 处理插队刷怪队列。
|
||||||
|
* 3. 逐步从 pendingMonsters 队列中生成怪物(受 stop_spawn_mon 限制)。
|
||||||
*/
|
*/
|
||||||
protected update(dt: number): void {
|
protected update(dt: number): void {
|
||||||
if(!smc.mission.play) return
|
if(!smc.mission.play) return
|
||||||
if(smc.mission.pause) return
|
if(smc.mission.pause) return
|
||||||
if(smc.mission.stop_mon_action) return;
|
if(smc.mission.stop_mon_action) return;
|
||||||
if(!smc.mission.in_fight) return;
|
if(!smc.mission.in_fight) return;
|
||||||
if(smc.mission.stop_spawn_mon) return;
|
|
||||||
this.updateSpecialQueue(dt);
|
this.updateSpecialQueue(dt);
|
||||||
|
|
||||||
|
if(smc.mission.stop_spawn_mon) return;
|
||||||
|
|
||||||
|
// 逐步刷怪逻辑
|
||||||
|
if (this.pendingMonsters.length > 0) {
|
||||||
|
this.spawnTimer += dt;
|
||||||
|
// 控制刷怪速率:例如每 0.2 秒刷 1-2 只
|
||||||
|
if (this.spawnTimer > 0.2) {
|
||||||
|
this.spawnTimer = 0;
|
||||||
|
// 一次出 2 只,加快进度
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
if (this.pendingMonsters.length === 0) break;
|
||||||
|
const monData = this.pendingMonsters.shift()!;
|
||||||
|
const lane = this.pickBalancedLane();
|
||||||
|
this.addMonsterAt(lane, this.laneIndices[lane], monData);
|
||||||
|
this.laneIndices[lane]++;
|
||||||
|
this.waveSpawnedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 事件处理 ========================
|
// ======================== 事件处理 ========================
|
||||||
@@ -152,15 +177,16 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
this.waveTargetCount = 0
|
this.waveTargetCount = 0
|
||||||
this.waveSpawnedCount = 0
|
this.waveSpawnedCount = 0
|
||||||
this.MonQueue = []
|
this.MonQueue = []
|
||||||
|
this.pendingMonsters = []
|
||||||
|
this.spawnTimer = 0
|
||||||
this.laneIndices = [0, 0, 0];
|
this.laneIndices = [0, 0, 0];
|
||||||
|
|
||||||
let hasBoss = false;
|
// 预生成第一波数据以获取数量和 Boss 信息
|
||||||
const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot;
|
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||||
for (const slot of config) {
|
this.pendingMonsters = monsters;
|
||||||
if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss) {
|
this.waveTargetCount = monsters.length;
|
||||||
hasBoss = true;
|
let hasBoss = monsters.some(m => m.isBoss);
|
||||||
}
|
|
||||||
}
|
|
||||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||||
wave: this.currentWave,
|
wave: this.currentWave,
|
||||||
total: this.waveTargetCount,
|
total: this.waveTargetCount,
|
||||||
@@ -199,13 +225,22 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
this.queueTimer = 0;
|
this.queueTimer = 0;
|
||||||
|
|
||||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) ||
|
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) ||
|
||||||
MonList[MonType.LongBoss].includes(item.uuid) ||
|
MonList[MonType.LongBoss].includes(item.uuid);
|
||||||
(MonList[MonType.FlyBoss] && MonList[MonType.FlyBoss].includes(item.uuid));
|
|
||||||
|
|
||||||
const upType = this.getRandomUpType();
|
|
||||||
const lane = item.flyLane !== undefined && item.flyLane >= 0 && item.flyLane <= 2 ? item.flyLane : this.pickBalancedLane();
|
const lane = item.flyLane !== undefined && item.flyLane >= 0 && item.flyLane <= 2 ? item.flyLane : this.pickBalancedLane();
|
||||||
|
|
||||||
this.addMonsterAt(lane, this.laneIndices[lane], item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)));
|
// 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAt
|
||||||
|
const base = HeroInfo[item.uuid];
|
||||||
|
const monData: GeneratedMonster = {
|
||||||
|
uuid: item.uuid,
|
||||||
|
type: MonType.Melee, // 简化的兜底,真实逻辑依赖 heroSet 配置
|
||||||
|
hp: base ? base.hp : 100,
|
||||||
|
ap: base ? base.ap : 10,
|
||||||
|
affixes: [],
|
||||||
|
isBoss: isBoss,
|
||||||
|
spawnIndex: 0
|
||||||
|
};
|
||||||
|
this.addMonsterAt(lane, this.laneIndices[lane], monData, item.level);
|
||||||
this.laneIndices[lane]++;
|
this.laneIndices[lane]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,25 +249,21 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
/**
|
/**
|
||||||
* 开始下一波:
|
* 开始下一波:
|
||||||
* 1. 波数 +1 并更新全局数据。
|
* 1. 波数 +1 并更新全局数据。
|
||||||
* 2. 重置槽位并根据配置生成本波所有怪物。
|
* 2. 分发 NewWave 事件(实际的生成在 resetSlotSpawnData 中触发)。
|
||||||
* 3. 分发 NewWave 事件。
|
|
||||||
*/
|
*/
|
||||||
private onTimeUpAdvanceWave() {
|
private onTimeUpAdvanceWave() {
|
||||||
this.currentWave += 1;
|
this.currentWave += 1;
|
||||||
smc.vmdata.mission_data.level = this.currentWave;
|
smc.vmdata.mission_data.level = this.currentWave;
|
||||||
|
|
||||||
// 检查本波是否有 Boss
|
// 预生成新一波数据以获取数量和 Boss 信息
|
||||||
let hasBoss = false;
|
const monsters = spawningEngine.generateWave(this.currentWave);
|
||||||
const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot;
|
this.pendingMonsters = monsters;
|
||||||
for (const slot of config) {
|
this.waveTargetCount = monsters.length;
|
||||||
if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss) {
|
let hasBoss = monsters.some(m => m.isBoss);
|
||||||
hasBoss = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oops.message.dispatchEvent(GameEvent.NewWave, {
|
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||||
wave: this.currentWave,
|
wave: this.currentWave,
|
||||||
total: this.waveTargetCount, // 此时还是上一波的怪物数量,但可以不传或后续修正
|
total: this.waveTargetCount,
|
||||||
bossWave: hasBoss,
|
bossWave: hasBoss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -241,60 +272,12 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
this.resetSlotSpawnData(this.currentWave);
|
this.resetSlotSpawnData(this.currentWave);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取当前阶段(stage = wave - 1,用于属性成长计算) */
|
|
||||||
private getCurrentStage(): number {
|
|
||||||
return Math.max(0, this.currentWave - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== 随机选取 ========================
|
|
||||||
|
|
||||||
/** 随机选取一种成长类型 */
|
|
||||||
private getRandomUpType(): UpType {
|
|
||||||
const keys = Object.keys(StageGrow).map(v => Number(v) as UpType);
|
|
||||||
const index = Math.floor(Math.random() * keys.length);
|
|
||||||
return keys[index] ?? UpType.AP1_HP1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据怪物类型从对应池中随机选取 UUID。
|
|
||||||
* @param monType 怪物类型(MonType 枚举值)
|
|
||||||
* @returns 怪物 UUID
|
|
||||||
*/
|
|
||||||
private getRandomUuidByType(monType: number): number {
|
|
||||||
const pool = (MonList as any)[monType] || MonList[MonType.Melee];
|
|
||||||
if (!pool || pool.length === 0) return 6001;
|
|
||||||
const index = Math.floor(Math.random() * pool.length);
|
|
||||||
return pool[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算怪物属性成长值对。
|
|
||||||
* Boss 在普通成长基础上叠加 StageBossGrow。
|
|
||||||
*
|
|
||||||
* @param upType 成长类型
|
|
||||||
* @param isBoss 是否为 Boss
|
|
||||||
* @returns [AP 成长值, HP 成长值]
|
|
||||||
*/
|
|
||||||
private resolveGrowPair(upType: UpType, isBoss: boolean): [number, number] {
|
|
||||||
const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1];
|
|
||||||
if (!isBoss) return [grow[0], grow[1]];
|
|
||||||
const bossGrow = StageBossGrow[upType] || StageBossGrow[UpType.AP1_HP1];
|
|
||||||
return [grow[0] + bossGrow[0], grow[1] + bossGrow[1]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 获取全局刷怪强度系数 */
|
|
||||||
private getSpawnPowerBias(): number {
|
|
||||||
return SpawnPowerBias;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ======================== 槽位管理 ========================
|
// ======================== 槽位管理 ========================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新分配并生成本波所有怪物:
|
* 重新分配本波所有怪物状态:
|
||||||
* 1. 清理上一波残留怪物。
|
* 1. 清理上一波残留怪物。
|
||||||
* 2. 读取波次配置。
|
* 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好,只需重置 laneIndices 即可。
|
||||||
* 3. 依据配置和 flyLane 属性,为每只怪物分配自增索引。
|
|
||||||
* 4. 立即实例化所有怪物。
|
|
||||||
*
|
*
|
||||||
* @param wave 当前波数
|
* @param wave 当前波数
|
||||||
*/
|
*/
|
||||||
@@ -307,43 +290,9 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 读取波次配置
|
// 2. 重置排号索引
|
||||||
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
|
||||||
|
|
||||||
// 3. 重置排号索引
|
|
||||||
this.laneIndices = [0, 0, 0];
|
this.laneIndices = [0, 0, 0];
|
||||||
|
|
||||||
let allMons: any[] = [];
|
|
||||||
|
|
||||||
// 解析配置
|
|
||||||
for (const slot of config) {
|
|
||||||
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss;
|
|
||||||
|
|
||||||
for (let i = 0; i < slot.count; i++) {
|
|
||||||
const uuid = this.getRandomUuidByType(slot.type);
|
|
||||||
const upType = this.getRandomUpType();
|
|
||||||
// 优先使用配置的 lane,否则均衡分配
|
|
||||||
let lane = slot.flyLane !== undefined ? slot.flyLane : this.pickBalancedLane();
|
|
||||||
lane = Math.max(0, Math.min(2, lane));
|
|
||||||
|
|
||||||
const req = { uuid, isBoss, upType, monLv: wave, lane };
|
|
||||||
allMons.push(req);
|
|
||||||
// 提前累加 laneIndices,以便本波内的均衡分配能正确计算
|
|
||||||
this.laneIndices[lane]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.waveTargetCount = allMons.length;
|
|
||||||
this.waveSpawnedCount = 0;
|
this.waveSpawnedCount = 0;
|
||||||
|
|
||||||
// 由于上面循环中已经累加了 laneIndices,这里需要重置,以便下面真正生成时再累加(或者直接利用 allMons 生成)
|
|
||||||
this.laneIndices = [0, 0, 0];
|
|
||||||
|
|
||||||
// 4. 立即生成本波所有怪物
|
|
||||||
for (const req of allMons) {
|
|
||||||
this.addMonsterAt(req.lane, this.laneIndices[req.lane], req.uuid, req.isBoss, req.upType, req.monLv);
|
|
||||||
this.laneIndices[req.lane]++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 怪物生成 ========================
|
// ======================== 怪物生成 ========================
|
||||||
@@ -353,17 +302,13 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
*
|
*
|
||||||
* @param laneIndex 三路索引 (0 上, 1 中, 2 下)
|
* @param laneIndex 三路索引 (0 上, 1 中, 2 下)
|
||||||
* @param monIndex 该路级的第几个怪 (0, 1, 2...)
|
* @param monIndex 该路级的第几个怪 (0, 1, 2...)
|
||||||
* @param uuid 怪物 UUID
|
* @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等)
|
||||||
* @param isBoss 是否为 Boss
|
* @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定)
|
||||||
* @param upType 属性成长类型
|
|
||||||
* @param monLv 怪物等级
|
|
||||||
*/
|
*/
|
||||||
private addMonsterAt(
|
private addMonsterAt(
|
||||||
laneIndex: number,
|
laneIndex: number,
|
||||||
monIndex: number,
|
monIndex: number,
|
||||||
uuid: number = 1001,
|
monData: GeneratedMonster,
|
||||||
isBoss: boolean = false,
|
|
||||||
upType: UpType = UpType.AP1_HP1,
|
|
||||||
monLv: number = 1
|
monLv: number = 1
|
||||||
) {
|
) {
|
||||||
let mon = ecs.getEntity<Monster>(Monster);
|
let mon = ecs.getEntity<Monster>(Monster);
|
||||||
@@ -371,11 +316,11 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
|
|
||||||
// 计算坐标
|
// 计算坐标
|
||||||
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X;
|
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X;
|
||||||
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + (isBoss ? 6 : 0);
|
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + (monData.isBoss ? 6 : 0);
|
||||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||||
|
|
||||||
mon.load(spawnPos, scale, uuid, isBoss, landingY, monLv, laneIndex);
|
mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, laneIndex);
|
||||||
|
|
||||||
// 设置渲染排序
|
// 设置渲染排序
|
||||||
const move = mon.get(MonMoveComp);
|
const move = mon.get(MonMoveComp);
|
||||||
@@ -383,16 +328,15 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
move.spawnOrder = this.globalSpawnOrder;
|
move.spawnOrder = this.globalSpawnOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算最终属性
|
// 应用新引擎计算好的最终属性和词缀
|
||||||
const model = mon.get(HeroAttrsComp);
|
const model = mon.get(HeroAttrsComp);
|
||||||
const base = HeroInfo[uuid];
|
if (!model) return;
|
||||||
if (!model || !base) return;
|
model.ap = monData.ap;
|
||||||
const stage = this.getCurrentStage();
|
model.hp_max = monData.hp;
|
||||||
const grow = this.resolveGrowPair(upType, isBoss);
|
|
||||||
const bias = Math.max(0.1, this.getSpawnPowerBias());
|
|
||||||
model.ap = Math.max(1, Math.floor((base.ap + stage * grow[0]) * bias));
|
|
||||||
model.hp_max = Math.max(1, Math.floor((base.hp + stage * grow[1]) * bias));
|
|
||||||
model.hp = model.hp_max;
|
model.hp = model.hp_max;
|
||||||
|
|
||||||
|
// 将词缀记录到属性组件上,供战斗层使用
|
||||||
|
(model as any).affixes = monData.affixes || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ECS 组件移除时触发(当前不销毁节点) */
|
/** ECS 组件移除时触发(当前不销毁节点) */
|
||||||
|
|||||||
Reference in New Issue
Block a user