docs: 为游戏地图模块添加详细的代码注释
为游戏地图模块的脚本文件添加全面的注释,说明每个组件的职责、关键设计、依赖关系和使用方式。注释覆盖了英雄信息面板、技能卡槽位管理器、排行榜弹窗、卡牌控制器、背景滚动组件等核心功能模块,提高了代码的可读性和维护性。 同时修复了英雄预制体的激活状态和技能效果预制体的尺寸参数。
This commit is contained in:
@@ -1,3 +1,33 @@
|
||||
/**
|
||||
* @file MissionMonComp.ts
|
||||
* @description 怪物(Monster)波次刷新管理组件(逻辑层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 管理每一波怪物的 **生成计划**:根据 WaveSlotConfig 分配怪物到固定槽位。
|
||||
* 2. 管理 6 个固定刷怪槽位的占用状态,支持 Boss 占 2 格。
|
||||
* 3. 处理特殊插队刷怪请求(MonQueue),优先于常规刷新。
|
||||
* 4. 自动推进波次:当前波所有怪物被清除后自动进入下一波。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 全场固定 6 个槽位(索引 0-5),每个槽位占固定 X 坐标。
|
||||
* - Boss 占 2 个连续槽位,只能放在 0、2、4 号位。
|
||||
* - slotOccupiedEids 记录每个槽位占用的怪物 ECS 实体 ID。
|
||||
* - resetSlotSpawnData(wave) 在每波开始时读取配置,分配并立即生成所有怪物。
|
||||
* - refreshSlotOccupancy() 定期检查槽位占用的实体是否仍存活,清除已死亡的占用。
|
||||
* - tryAdvanceWave() 在所有怪物死亡后自动推进波次。
|
||||
*
|
||||
* 怪物属性计算公式:
|
||||
* ap = floor((base_ap + stage × grow_ap) × SpawnPowerBias)
|
||||
* hp = floor((base_hp + stage × grow_hp) × SpawnPowerBias)
|
||||
* 其中 stage = currentWave - 1
|
||||
*
|
||||
* 依赖:
|
||||
* - RogueConfig —— 怪物类型、成长值、波次配置
|
||||
* - Monster(hero/Mon.ts)—— 怪物 ECS 实体类
|
||||
* - HeroInfo(heroSet)—— 怪物基础属性配置(与英雄共用配置)
|
||||
* - HeroAttrsComp / MoveComp —— 怪物属性和移动组件
|
||||
* - BoxSet.GAME_LINE —— 地面基准 Y 坐标
|
||||
*/
|
||||
import { _decorator, v3, Vec3 } from "cc";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
@@ -13,34 +43,63 @@ import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/** 视图层对象 */
|
||||
/**
|
||||
* MissionMonCompComp —— 怪物波次刷新管理器
|
||||
*
|
||||
* 每波开始时根据 WaveSlotConfig 配置分配怪物到固定槽位,
|
||||
* 战斗中监控槽位状态,所有怪物消灭后自动推进到下一波。
|
||||
*/
|
||||
@ccclass('MissionMonCompComp')
|
||||
@ecs.register('MissionMonComp', false)
|
||||
export class MissionMonCompComp extends CCComp {
|
||||
// ======================== 常量 ========================
|
||||
|
||||
/** Boss 的渲染优先级偏移(确保 Boss 始终渲染在最前) */
|
||||
private static readonly BOSS_RENDER_PRIORITY = 1000000;
|
||||
/** 第一个槽位的 X 坐标起点 */
|
||||
private static readonly MON_SLOT_START_X = 30;
|
||||
/** 槽位间的 X 间距 */
|
||||
private static readonly MON_SLOT_X_INTERVAL = 60;
|
||||
/** 怪物出生掉落高度 */
|
||||
private static readonly MON_DROP_HEIGHT = 280;
|
||||
/** 最大槽位数 */
|
||||
private static readonly MAX_SLOTS = 6;
|
||||
|
||||
// ======================== 编辑器属性 ========================
|
||||
|
||||
@property({ tooltip: "是否启用调试日志" })
|
||||
private debugMode: boolean = false;
|
||||
|
||||
// 刷怪队列(用于插队生成:比如运营活动怪、技能召唤怪、剧情强制怪)
|
||||
// 约定:队列里的怪会优先于常规刷新处理
|
||||
// ======================== 插队刷怪队列 ========================
|
||||
|
||||
/**
|
||||
* 刷怪队列(优先于常规配置处理):
|
||||
* 用于插队生成(如运营活动怪、技能召唤怪、剧情强制怪)。
|
||||
*/
|
||||
private MonQueue: Array<{
|
||||
/** 怪物 UUID */
|
||||
uuid: number,
|
||||
/** 怪物等级 */
|
||||
level: number,
|
||||
}> = [];
|
||||
|
||||
private static readonly MAX_SLOTS = 6;
|
||||
private slotOccupiedEids: Array<number | null> = Array(6).fill(null);
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 全局生成顺序计数器,用于层级管理(预留) */
|
||||
/** 槽位占用状态:记录每个槽位当前占用的怪物 ECS 实体 ID,null 表示空闲 */
|
||||
private slotOccupiedEids: Array<number | null> = Array(6).fill(null);
|
||||
/** 全局生成顺序计数器(用于渲染层级排序) */
|
||||
private globalSpawnOrder: number = 0;
|
||||
/** 插队刷怪处理计时器 */
|
||||
private queueTimer: number = 0;
|
||||
/** 当前波数 */
|
||||
private currentWave: number = 0;
|
||||
/** 当前波的目标怪物总数 */
|
||||
private waveTargetCount: number = 0;
|
||||
/** 当前波已生成的怪物数量 */
|
||||
private waveSpawnedCount: number = 0;
|
||||
|
||||
// ======================== 生命周期 ========================
|
||||
|
||||
onLoad(){
|
||||
this.on(GameEvent.FightReady,this.fight_ready,this)
|
||||
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
|
||||
@@ -48,8 +107,30 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收特殊刷怪事件并入队
|
||||
* 事件数据最小结构:{ uuid, level }
|
||||
* 帧更新:
|
||||
* 1. 检查游戏是否运行中。
|
||||
* 2. 刷新槽位占用状态(清除已死亡怪物的占用)。
|
||||
* 3. 尝试推进波次(所有怪物清除后自动进入下一波)。
|
||||
* 4. 处理插队刷怪队列。
|
||||
*/
|
||||
protected update(dt: number): void {
|
||||
if(!smc.mission.play) return
|
||||
if(smc.mission.pause) return
|
||||
if(smc.mission.stop_mon_action) return;
|
||||
if(!smc.mission.in_fight) return;
|
||||
this.refreshSlotOccupancy();
|
||||
this.tryAdvanceWave();
|
||||
if(!smc.mission.in_fight) return;
|
||||
if(smc.mission.stop_spawn_mon) return;
|
||||
this.updateSpecialQueue(dt);
|
||||
}
|
||||
|
||||
// ======================== 事件处理 ========================
|
||||
|
||||
/**
|
||||
* 接收特殊刷怪事件并入队。
|
||||
* @param event 事件名
|
||||
* @param args { uuid: number, level: number }
|
||||
*/
|
||||
private onSpawnSpecialMonster(event: string, args: any) {
|
||||
if (!args) return;
|
||||
@@ -58,13 +139,16 @@ export class MissionMonCompComp extends CCComp {
|
||||
uuid: args.uuid,
|
||||
level: args.level,
|
||||
});
|
||||
// 让队列在下一帧附近尽快消费,提升事件响应感
|
||||
// 加速队列消费
|
||||
this.queueTimer = 1.0;
|
||||
}
|
||||
|
||||
start() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗准备:重置所有运行时状态并开始第一波。
|
||||
*/
|
||||
fight_ready(){
|
||||
smc.vmdata.mission_data.mon_num=0
|
||||
smc.mission.stop_spawn_mon = false
|
||||
@@ -78,18 +162,14 @@ export class MissionMonCompComp extends CCComp {
|
||||
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
|
||||
}
|
||||
|
||||
protected update(dt: number): void {
|
||||
if(!smc.mission.play) return
|
||||
if(smc.mission.pause) return
|
||||
if(smc.mission.stop_mon_action) return;
|
||||
if(!smc.mission.in_fight) return;
|
||||
this.refreshSlotOccupancy();
|
||||
this.tryAdvanceWave();
|
||||
if(!smc.mission.in_fight) return;
|
||||
if(smc.mission.stop_spawn_mon) return;
|
||||
this.updateSpecialQueue(dt);
|
||||
}
|
||||
// ======================== 插队刷怪 ========================
|
||||
|
||||
/**
|
||||
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
|
||||
* 1. 判断怪物是否为 Boss(决定占用 1 格还是 2 格)。
|
||||
* 2. 在空闲槽位中查找合适位置。
|
||||
* 3. 找到后从队列中移除并生成怪物。
|
||||
*/
|
||||
private updateSpecialQueue(dt: number) {
|
||||
if (this.MonQueue.length <= 0) return;
|
||||
this.queueTimer += dt;
|
||||
@@ -99,9 +179,10 @@ export class MissionMonCompComp extends CCComp {
|
||||
const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) || MonList[MonType.LongBoss].includes(item.uuid);
|
||||
const slotsPerMon = isBoss ? 2 : 1;
|
||||
|
||||
// 查找空闲槽位
|
||||
let slotIndex = -1;
|
||||
if (slotsPerMon === 2) {
|
||||
// Boss 只能放在 0, 2, 4
|
||||
// Boss 只能放在 0, 2, 4(需要连续 2 格空闲)
|
||||
for (const idx of [0, 2, 4]) {
|
||||
if (!this.slotOccupiedEids[idx] && !this.slotOccupiedEids[idx + 1]) {
|
||||
slotIndex = idx;
|
||||
@@ -109,6 +190,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 普通怪找第一个空闲格
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (!this.slotOccupiedEids[i]) {
|
||||
slotIndex = i;
|
||||
@@ -125,11 +207,20 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 波次管理 ========================
|
||||
|
||||
/**
|
||||
* 开始下一波:
|
||||
* 1. 波数 +1 并更新全局数据。
|
||||
* 2. 重置槽位并根据配置生成本波所有怪物。
|
||||
* 3. 分发 NewWave 事件。
|
||||
*/
|
||||
private startNextWave() {
|
||||
this.currentWave += 1;
|
||||
smc.vmdata.mission_data.level = this.currentWave;
|
||||
this.resetSlotSpawnData(this.currentWave);
|
||||
|
||||
// 检查本波是否有 Boss
|
||||
let hasBoss = false;
|
||||
const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot;
|
||||
for (const slot of config) {
|
||||
@@ -145,6 +236,10 @@ export class MissionMonCompComp extends CCComp {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试推进波次:
|
||||
* 条件:队列为空 + 所有槽位无活怪 + 全局怪物数为 0。
|
||||
*/
|
||||
private tryAdvanceWave() {
|
||||
if (this.MonQueue.length > 0) return;
|
||||
if (this.hasActiveSlotMonster()) return;
|
||||
@@ -152,16 +247,25 @@ export class MissionMonCompComp extends CCComp {
|
||||
this.startNextWave();
|
||||
}
|
||||
|
||||
/** 获取当前阶段(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;
|
||||
@@ -169,19 +273,38 @@ export class MissionMonCompComp extends CCComp {
|
||||
return pool[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算怪物属性成长值对。
|
||||
* Boss 在普通成长基础上叠加 StageBossGrow。
|
||||
*
|
||||
* @param upType 成长类型
|
||||
* @param isBoss 是否为 Boss
|
||||
* @returns [AP 成长值, HP 成长值]
|
||||
*/
|
||||
private resolveGrowPair(upType: UpType, isBoss: boolean): [number, number] {
|
||||
// 普通怪基础成长:StageGrow
|
||||
const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1];
|
||||
if (!isBoss) return [grow[0], grow[1]];
|
||||
// Boss 额外成长:StageBossGrow(在普通成长上叠加)
|
||||
const bossGrow = StageBossGrow[upType] || StageBossGrow[UpType.AP1_HP1];
|
||||
return [grow[0] + bossGrow[0], grow[1] + bossGrow[1]];
|
||||
}
|
||||
|
||||
/** 获取全局刷怪强度系数 */
|
||||
private getSpawnPowerBias(): number {
|
||||
return SpawnPowerBias;
|
||||
}
|
||||
|
||||
// ======================== 槽位管理 ========================
|
||||
|
||||
/**
|
||||
* 重置槽位并生成本波所有怪物:
|
||||
* 1. 读取波次配置(WaveSlotConfig 或 DefaultWaveSlot)。
|
||||
* 2. 将 Boss 和普通怪分类。
|
||||
* 3. Boss 优先分配到 0, 2, 4 号位(占 2 格)。
|
||||
* 4. 普通怪填充剩余空闲格。
|
||||
* 5. 立即实例化所有怪物。
|
||||
*
|
||||
* @param wave 当前波数
|
||||
*/
|
||||
private resetSlotSpawnData(wave: number = 1) {
|
||||
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
|
||||
this.slotOccupiedEids = Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
@@ -189,6 +312,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
let bosses: any[] = [];
|
||||
let normals: any[] = [];
|
||||
|
||||
// 按类型分类
|
||||
for (const slot of config) {
|
||||
const slotsPerMon = slot.slotsPerMon || 1;
|
||||
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss;
|
||||
@@ -207,7 +331,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
this.waveTargetCount = bosses.length + normals.length;
|
||||
this.waveSpawnedCount = 0;
|
||||
|
||||
// Boss 只能放在 0, 2, 4 (即 1, 3, 5 号位)
|
||||
// Boss 优先分配(只能放在 0, 2, 4)
|
||||
let bossAllowedIndices = [0, 2, 4];
|
||||
let assignedSlots = new Array(MissionMonCompComp.MAX_SLOTS).fill(null);
|
||||
|
||||
@@ -216,7 +340,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
for (const idx of bossAllowedIndices) {
|
||||
if (!assignedSlots[idx] && !assignedSlots[idx + 1]) {
|
||||
assignedSlots[idx] = boss;
|
||||
assignedSlots[idx + 1] = "occupied"; // 占位
|
||||
assignedSlots[idx + 1] = "occupied"; // 占位标记
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
@@ -226,6 +350,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// 普通怪填充剩余空位
|
||||
for (const normal of normals) {
|
||||
let placed = false;
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
@@ -249,6 +374,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查是否有任何槽位仍被活着的怪物占用 */
|
||||
private hasActiveSlotMonster() {
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
if (this.slotOccupiedEids[i]) return true;
|
||||
@@ -256,6 +382,11 @@ export class MissionMonCompComp extends CCComp {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新所有槽位的占用状态:
|
||||
* 检查每个占用的 ECS 实体是否仍存在且 HP > 0,
|
||||
* 已失效的清除占用标记。
|
||||
*/
|
||||
private refreshSlotOccupancy() {
|
||||
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
|
||||
const eid = this.slotOccupiedEids[i];
|
||||
@@ -272,6 +403,23 @@ export class MissionMonCompComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 怪物生成 ========================
|
||||
|
||||
/**
|
||||
* 在指定槽位生成一个怪物:
|
||||
* 1. 计算出生坐标(多格时居中)。
|
||||
* 2. 创建 Monster ECS 实体。
|
||||
* 3. 标记槽位占用。
|
||||
* 4. 设置渲染排序(Boss 优先级更高)。
|
||||
* 5. 根据阶段和成长类型计算最终 AP / HP。
|
||||
*
|
||||
* @param slotIndex 槽位索引(0-5)
|
||||
* @param uuid 怪物 UUID
|
||||
* @param isBoss 是否为 Boss
|
||||
* @param upType 属性成长类型
|
||||
* @param monLv 怪物等级
|
||||
* @param slotsPerMon 占用格数
|
||||
*/
|
||||
private addMonsterBySlot(
|
||||
slotIndex: number,
|
||||
uuid: number = 1001,
|
||||
@@ -282,7 +430,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
) {
|
||||
let mon = ecs.getEntity<Monster>(Monster);
|
||||
let scale = -1;
|
||||
// 如果占用了多个格子,出生坐标居中处理
|
||||
// 多格占用时居中出生点
|
||||
const centerXOffset = (slotsPerMon - 1) * MissionMonCompComp.MON_SLOT_X_INTERVAL / 2;
|
||||
const spawnX = MissionMonCompComp.MON_SLOT_START_X + slotIndex * MissionMonCompComp.MON_SLOT_X_INTERVAL + centerXOffset;
|
||||
const landingY = BoxSet.GAME_LINE + (isBoss ? 6 : 0);
|
||||
@@ -291,18 +439,22 @@ export class MissionMonCompComp extends CCComp {
|
||||
|
||||
mon.load(spawnPos, scale, uuid, isBoss, landingY, monLv);
|
||||
|
||||
// 将它占用的所有格子都标记为这个 eid
|
||||
// 标记槽位占用
|
||||
for (let j = 0; j < slotsPerMon; j++) {
|
||||
if (slotIndex + j < MissionMonCompComp.MAX_SLOTS) {
|
||||
this.slotOccupiedEids[slotIndex + j] = mon.eid;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置渲染排序
|
||||
const move = mon.get(MoveComp);
|
||||
if (move) {
|
||||
move.spawnOrder = isBoss
|
||||
? MissionMonCompComp.BOSS_RENDER_PRIORITY + this.globalSpawnOrder
|
||||
: this.globalSpawnOrder;
|
||||
}
|
||||
|
||||
// 计算最终属性
|
||||
const model = mon.get(HeroAttrsComp);
|
||||
const base = HeroInfo[uuid];
|
||||
if (!model || !base) return;
|
||||
@@ -314,7 +466,7 @@ export class MissionMonCompComp extends CCComp {
|
||||
model.hp = model.hp_max;
|
||||
}
|
||||
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
/** ECS 组件移除时触发(当前不销毁节点) */
|
||||
reset() {
|
||||
// this.node.destroy();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user