feat(关卡): 实现基于波次的怪物生成系统
- 将时间轴刷怪改为波次制,每波生成固定数量普通怪 - 每若干波生成一个Boss,Boss波次可配置 - 在界面时间显示前添加当前波次信息 - 添加新波次开始时的事件通知机制 - 调整卡片预制件的Y坐标以适应新布局
This commit is contained in:
@@ -2294,7 +2294,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -108.057,
|
"y": -136.25,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
@@ -2592,7 +2592,7 @@
|
|||||||
"_lpos": {
|
"_lpos": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -108.057,
|
"y": -136.25,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"_lrot": {
|
"_lrot": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,8 @@ export class MissionCardComp extends CCComp {
|
|||||||
private readonly buttonClickScale: number = 1.06;
|
private readonly buttonClickScale: number = 1.06;
|
||||||
/** 四个插卡槽位(固定顺序分发:1~4) */
|
/** 四个插卡槽位(固定顺序分发:1~4) */
|
||||||
@property(Node)
|
@property(Node)
|
||||||
|
cards_node:Node = null!
|
||||||
|
@property(Node)
|
||||||
card1:Node = null!
|
card1:Node = null!
|
||||||
@property(Node)
|
@property(Node)
|
||||||
card2:Node = null!
|
card2:Node = null!
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export class MissionComp extends CCComp {
|
|||||||
private heapTrendTimer: number = 0;
|
private heapTrendTimer: number = 0;
|
||||||
private heapTrendBaseMB: number = -1;
|
private heapTrendBaseMB: number = -1;
|
||||||
private monsterCountSyncTimer: number = 0;
|
private monsterCountSyncTimer: number = 0;
|
||||||
|
private currentWave: number = 0;
|
||||||
private readonly heroViewMatcher = ecs.allOf(HeroViewComp);
|
private readonly heroViewMatcher = ecs.allOf(HeroViewComp);
|
||||||
private readonly skillViewMatcher = ecs.allOf(SkillView);
|
private readonly skillViewMatcher = ecs.allOf(SkillView);
|
||||||
private readonly heroAttrsMatcher = ecs.allOf(HeroAttrsComp);
|
private readonly heroAttrsMatcher = ecs.allOf(HeroAttrsComp);
|
||||||
@@ -72,6 +73,7 @@ export class MissionComp extends CCComp {
|
|||||||
// this.on(GameEvent.HeroDead,this.do_hero_dead,this)
|
// this.on(GameEvent.HeroDead,this.do_hero_dead,this)
|
||||||
// this.on(GameEvent.FightEnd,this.fight_end,this)
|
// this.on(GameEvent.FightEnd,this.fight_end,this)
|
||||||
this.on(GameEvent.MissionEnd,this.mission_end,this)
|
this.on(GameEvent.MissionEnd,this.mission_end,this)
|
||||||
|
this.on(GameEvent.NewWave,this.onNewWave,this)
|
||||||
this.on(GameEvent.DO_AD_BACK,this.do_ad,this)
|
this.on(GameEvent.DO_AD_BACK,this.do_ad,this)
|
||||||
this.removeMemoryPanel()
|
this.removeMemoryPanel()
|
||||||
}
|
}
|
||||||
@@ -94,7 +96,8 @@ export class MissionComp extends CCComp {
|
|||||||
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;
|
||||||
let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
const wave = Math.max(1, this.currentWave || smc.vmdata.mission_data.level || 1);
|
||||||
|
let str = `W${wave.toString().padStart(2, '0')} ${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||||
if(str != this.lastTimeStr){
|
if(str != this.lastTimeStr){
|
||||||
this.time_node.getChildByName("time").getComponent(Label).string = str;
|
this.time_node.getChildByName("time").getComponent(Label).string = str;
|
||||||
this.lastTimeStr = str;
|
this.lastTimeStr = str;
|
||||||
@@ -125,10 +128,9 @@ export class MissionComp extends CCComp {
|
|||||||
this.unscheduleAllCallbacks();
|
this.unscheduleAllCallbacks();
|
||||||
// 确保清理上一局的残留实体
|
// 确保清理上一局的残留实体
|
||||||
this.cleanComponents();
|
this.cleanComponents();
|
||||||
|
|
||||||
oops.message.dispatchEvent(GameEvent.FightReady)
|
|
||||||
this.node.active=true
|
this.node.active=true
|
||||||
this.data_init()
|
this.data_init()
|
||||||
|
oops.message.dispatchEvent(GameEvent.FightReady)
|
||||||
let loading=this.node.parent.getChildByName("loading")
|
let loading=this.node.parent.getChildByName("loading")
|
||||||
loading.active=true
|
loading.active=true
|
||||||
this.scheduleOnce(()=>{
|
this.scheduleOnce(()=>{
|
||||||
@@ -197,6 +199,7 @@ export class MissionComp extends CCComp {
|
|||||||
smc.vmdata.mission_data.mon_num=0
|
smc.vmdata.mission_data.mon_num=0
|
||||||
smc.vmdata.mission_data.level=0
|
smc.vmdata.mission_data.level=0
|
||||||
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.currentWave = 0;
|
||||||
this.FightTime=FightSet.FiIGHT_TIME
|
this.FightTime=FightSet.FiIGHT_TIME
|
||||||
this.rewards=[] // 改为数组,用于存储掉落物品列表
|
this.rewards=[] // 改为数组,用于存储掉落物品列表
|
||||||
this.revive_times = 1; // 每次任务开始重置复活次数
|
this.revive_times = 1; // 每次任务开始重置复活次数
|
||||||
@@ -221,6 +224,15 @@ export class MissionComp extends CCComp {
|
|||||||
// mLogger.log(this.debugMode, 'MissionComp', "局内数据初始化",smc.vmdata.mission_data)
|
// mLogger.log(this.debugMode, 'MissionComp', "局内数据初始化",smc.vmdata.mission_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onNewWave(event: string, data: any) {
|
||||||
|
const wave = Number(data?.wave ?? 0);
|
||||||
|
if (wave <= 0) return;
|
||||||
|
this.currentWave = wave;
|
||||||
|
smc.vmdata.mission_data.level = wave;
|
||||||
|
this.lastTimeSecond = -1;
|
||||||
|
this.update_time();
|
||||||
|
}
|
||||||
|
|
||||||
private getMonsterThresholds(): { max: number; resume: number } {
|
private getMonsterThresholds(): { max: number; resume: number } {
|
||||||
const max = Math.max(1, Math.floor(this.maxMonsterCount));
|
const max = Math.max(1, Math.floor(this.maxMonsterCount));
|
||||||
const resume = Math.min(max - 1, Math.max(0, Math.floor(this.resumeMonsterCount)));
|
const resume = Math.min(max - 1, Math.max(0, Math.floor(this.resumeMonsterCount)));
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { _decorator, v3, Vec3 } from "cc";
|
|||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
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 { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { Monster } from "../hero/Mon";
|
import { Monster } from "../hero/Mon";
|
||||||
import { HeroInfo, MonStart } from "../common/config/heroSet";
|
import { HeroInfo, MonStart } 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 } from "../common/config/GameSet";
|
import {BoxSet } from "../common/config/GameSet";
|
||||||
import { BossList, BossSpawnTimeline, MonList, MonType, SpawnBaseCd, SpawnMinCd, SpawnPowerBias, SpawnStageReduce, StageBossGrow, StageDuration, StageGrow, UpType } from "./RogueConfig";
|
import { BossList, MonList, MonType, SpawnPowerBias, StageBossGrow, StageGrow, UpType } from "./RogueConfig";
|
||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
import { MoveComp } from "../hero/MoveComp";
|
import { MoveComp } from "../hero/MoveComp";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
@@ -20,6 +21,14 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private static readonly MON_DROP_HEIGHT = 300;
|
private static readonly MON_DROP_HEIGHT = 300;
|
||||||
@property({ tooltip: "是否启用调试日志" })
|
@property({ tooltip: "是否启用调试日志" })
|
||||||
private debugMode: boolean = false;
|
private debugMode: boolean = false;
|
||||||
|
@property({ tooltip: "每波基础普通怪数量" })
|
||||||
|
private baseMonstersPerWave: number = 5;
|
||||||
|
@property({ tooltip: "每波额外增加普通怪数量" })
|
||||||
|
private waveMonsterGrowth: number = 1;
|
||||||
|
@property({ tooltip: "多少波刷新一次 Boss" })
|
||||||
|
private bossWaveInterval: number = 5;
|
||||||
|
@property({ tooltip: "同一波内刷怪间隔(秒)" })
|
||||||
|
private waveSpawnCd: number = 0.35;
|
||||||
|
|
||||||
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
|
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
|
||||||
// 约定:队列里的怪会优先于常规刷新处理
|
// 约定:队列里的怪会优先于常规刷新处理
|
||||||
@@ -31,18 +40,15 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private spawnCount: number = 0; // 总生成计数,用于控制横向分布位置
|
private spawnCount: number = 0; // 总生成计数,用于控制横向分布位置
|
||||||
/** 全局生成顺序计数器,用于层级管理(预留) */
|
/** 全局生成顺序计数器,用于层级管理(预留) */
|
||||||
private globalSpawnOrder: number = 0;
|
private globalSpawnOrder: number = 0;
|
||||||
/** 局内战斗运行时间(秒) */
|
|
||||||
private gameTime: number = 0;
|
|
||||||
/** 普通怪刷新计时器 */
|
|
||||||
private waveTimer: number = 0;
|
|
||||||
/** 插队刷怪处理计时器 */
|
/** 插队刷怪处理计时器 */
|
||||||
private queueTimer: number = 0;
|
private queueTimer: number = 0;
|
||||||
private nextBossSpawnIndex: number = 0;
|
private waveSpawnTimer: number = 0;
|
||||||
|
private currentWave: number = 0;
|
||||||
|
private waveTargetCount: number = 0;
|
||||||
|
private waveSpawnedCount: number = 0;
|
||||||
|
private bossSpawnedInWave: boolean = false;
|
||||||
onLoad(){
|
onLoad(){
|
||||||
// 关卡准备、切换波次时重置刷怪状态
|
|
||||||
this.on(GameEvent.FightReady,this.fight_ready,this)
|
this.on(GameEvent.FightReady,this.fight_ready,this)
|
||||||
this.on(GameEvent.NewWave,this.fight_ready,this)
|
|
||||||
// 支持外部模块主动推入特殊怪(优先级高于常规刷新)
|
|
||||||
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
|
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,34 +71,30 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fight_ready(){
|
fight_ready(){
|
||||||
// 仅重置“本组件”刷怪状态,不处理其他系统状态
|
|
||||||
smc.vmdata.mission_data.mon_num=0
|
smc.vmdata.mission_data.mon_num=0
|
||||||
smc.mission.stop_spawn_mon = false
|
smc.mission.stop_spawn_mon = false
|
||||||
this.globalSpawnOrder = 0
|
this.globalSpawnOrder = 0
|
||||||
this.gameTime = 0
|
|
||||||
this.waveTimer = 0
|
|
||||||
this.queueTimer = 0
|
this.queueTimer = 0
|
||||||
this.nextBossSpawnIndex = 0
|
this.waveSpawnTimer = 0
|
||||||
|
this.currentWave = 0
|
||||||
|
this.waveTargetCount = 0
|
||||||
|
this.waveSpawnedCount = 0
|
||||||
|
this.bossSpawnedInWave = false
|
||||||
this.MonQueue = []
|
this.MonQueue = []
|
||||||
this.spawnCount = 0
|
this.spawnCount = 0
|
||||||
|
this.startNextWave()
|
||||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System (15-min Cycle)");
|
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
this.tryAdvanceWave();
|
||||||
// 计时推进:所有“按时间驱动”的曲线都依赖 gameTime
|
|
||||||
this.gameTime += dt;
|
|
||||||
this.updateBossSpawn();
|
|
||||||
if(smc.mission.stop_spawn_mon) return;
|
if(smc.mission.stop_spawn_mon) return;
|
||||||
this.updateSpecialQueue(dt);
|
this.updateSpecialQueue(dt);
|
||||||
this.updateNormalSpawn(dt);
|
this.updateWaveSpawn(dt);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSpecialQueue(dt: number) {
|
private updateSpecialQueue(dt: number) {
|
||||||
@@ -109,43 +111,55 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
this.spawnCount += 1;
|
this.spawnCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateNormalSpawn(dt: number) {
|
private updateWaveSpawn(dt: number) {
|
||||||
this.waveTimer += dt;
|
this.waveSpawnTimer += dt;
|
||||||
// 普通怪刷新间隔会随 stage 增长而缩短
|
if (this.waveSpawnTimer < this.waveSpawnCd) return;
|
||||||
const spawnCd = this.getCurrentSpawnCd();
|
this.waveSpawnTimer = 0;
|
||||||
if (this.waveTimer < spawnCd) return;
|
if (this.waveSpawnedCount >= this.waveTargetCount) {
|
||||||
this.waveTimer = 0;
|
if (this.isBossWave() && !this.bossSpawnedInWave) {
|
||||||
// 先随机怪物类型,再在该类型池内随机具体 uuid
|
const bossUuid = this.getRandomBossUuid();
|
||||||
|
const bossUpType = this.getRandomUpType();
|
||||||
|
this.addMonster(bossUuid, this.spawnCount, true, bossUpType);
|
||||||
|
this.spawnCount += 1;
|
||||||
|
this.bossSpawnedInWave = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const uuid = this.getRandomNormalMonsterUuid();
|
const uuid = this.getRandomNormalMonsterUuid();
|
||||||
// 每只怪独立抽取成长方案,使同时间段怪群有轻微离散性
|
|
||||||
const upType = this.getRandomUpType();
|
const upType = this.getRandomUpType();
|
||||||
this.addMonster(uuid, this.spawnCount, false, upType);
|
this.addMonster(uuid, this.spawnCount, false, upType);
|
||||||
|
this.waveSpawnedCount += 1;
|
||||||
this.spawnCount += 1;
|
this.spawnCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateBossSpawn() {
|
private startNextWave() {
|
||||||
while (
|
this.currentWave += 1;
|
||||||
this.nextBossSpawnIndex < BossSpawnTimeline.length &&
|
smc.vmdata.mission_data.level = this.currentWave;
|
||||||
this.gameTime >= BossSpawnTimeline[this.nextBossSpawnIndex]
|
this.waveTargetCount = Math.max(1, this.baseMonstersPerWave + (this.currentWave - 1) * this.waveMonsterGrowth);
|
||||||
) {
|
this.waveSpawnedCount = 0;
|
||||||
const uuid = this.getRandomBossUuid();
|
this.bossSpawnedInWave = false;
|
||||||
const upType = this.getRandomUpType();
|
this.waveSpawnTimer = this.waveSpawnCd;
|
||||||
this.addMonster(uuid, this.spawnCount, true, upType);
|
oops.message.dispatchEvent(GameEvent.NewWave, {
|
||||||
this.spawnCount += 1;
|
wave: this.currentWave,
|
||||||
this.nextBossSpawnIndex += 1;
|
total: this.waveTargetCount,
|
||||||
}
|
bossWave: this.isBossWave(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryAdvanceWave() {
|
||||||
|
if (this.waveSpawnedCount < this.waveTargetCount) return;
|
||||||
|
if (this.isBossWave() && !this.bossSpawnedInWave) return;
|
||||||
|
if (smc.vmdata.mission_data.mon_num > 0) return;
|
||||||
|
this.startNextWave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private isBossWave() {
|
||||||
|
const interval = Math.max(1, Math.floor(this.bossWaveInterval));
|
||||||
|
return this.currentWave > 0 && this.currentWave % interval === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentStage(): number {
|
private getCurrentStage(): number {
|
||||||
// 每 30 秒提升 1 阶(由配置 StageDuration 控制)
|
return Math.max(0, this.currentWave - 1);
|
||||||
return Math.floor(this.gameTime / StageDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCurrentSpawnCd(): number {
|
|
||||||
const stage = this.getCurrentStage();
|
|
||||||
const cd = SpawnBaseCd - stage * SpawnStageReduce;
|
|
||||||
// 保护下限,避免后期刷新间隔过小打爆性能
|
|
||||||
return Math.max(SpawnMinCd, cd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRandomUpType(): UpType {
|
private getRandomUpType(): UpType {
|
||||||
|
|||||||
Reference in New Issue
Block a user