/** * @file WaveHudComp.ts * @description 波次进度 HUD 组件(表现层) * * 职责: * 1. 本回合清剿进度:剩余怪数(mon_num + pending_mon_num)+ 进度条。 * 2. 全局 20 回合进度格:静态生成,Boss 回合(wave%5==0)置旗帜样式,当前回合脉动、已过回合置灰。 * * 数据流:NewWave 事件缓存 total/wave;update 0.2s 降频轮询 vmdata(与 syncMonsterSpawnState 同节奏)。 * 使用 oops-framework 模块:oops.message(事件解耦)。 * * 编辑器绑定:waveLab / remainLab / progress / flagsRoot(水平 Layout 容器,20 格代码生成)。 */ import { _decorator, Node, Label, ProgressBar, Sprite, Color, tween, Tween, v3, UITransform } from "cc"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { GameEvent } from "../common/config/GameEvent"; import { smc } from "../common/SingletonModuleComp"; import { getWaveType, WaveType, MAX_WAVE } from "./RogueConfig"; const { ccclass, property } = _decorator; @ccclass('WaveHudComp') export class WaveHudComp extends CCComp { @property({ type: Label, tooltip: "回合文本(第 x/20 回合)" }) waveLab: Label | null = null; @property({ type: Label, tooltip: "剩余怪数文本" }) remainLab: Label | null = null; @property({ type: ProgressBar, tooltip: "本回合清剿进度条" }) progress: ProgressBar | null = null; @property({ type: Node, tooltip: "全局回合旗帜容器(水平 Layout,20 格代码生成)" }) flagsRoot: Node | null = null; /** 本回合怪物总数(NewWave 事件载荷缓存) */ private waveTotal: number = 0; /** HUD 刷新节流计时器 */ private hudTimer: number = 0; /** 20 格旗帜节点缓存(Boss 位为旗帜样式) */ private flagNodes: Node[] = []; onLoad() { // oops.message 全局事件总线:新回合 oops.message.on(GameEvent.NewWave, this.onNewWave, this); this.buildFlags(); } onDestroy() { oops.message.off(GameEvent.NewWave, this.onNewWave, this); } /** 静态生成 20 格回合进度(Boss 位按 getWaveType 静态预知,无需运行数据) */ private buildFlags() { if (!this.flagsRoot) return; for (let i = 1; i <= MAX_WAVE; i++) { const cell = new Node(`flag_${i}`); cell.addComponent(UITransform).setContentSize(14, 14); const sp = cell.addComponent(Sprite); // Boss 位用警示色方块,普通位用圆点色(无美术资源时以颜色区分,后续可换 spriteFrame) const isBoss = getWaveType(i) === WaveType.Pressure; sp.color = isBoss ? new Color(220, 50, 50) : new Color(120, 120, 120); sp.sizeMode = Sprite.SizeMode.CUSTOM; cell.parent = this.flagsRoot; this.flagNodes.push(cell); } } private onNewWave(event: string, data: { wave: number; total: number; bossWave: boolean }) { this.waveTotal = data.total; if (this.waveLab) this.waveLab.string = `第 ${data.wave}/${MAX_WAVE} 回合`; this.refreshFlags(data.wave); } /** 刷新旗帜状态:已过回合置暗,当前回合 Boss 旗脉动 */ private refreshFlags(currentWave: number) { for (let i = 0; i < this.flagNodes.length; i++) { const cell = this.flagNodes[i]; const wave = i + 1; const sp = cell.getComponent(Sprite)!; const isBoss = getWaveType(wave) === WaveType.Pressure; if (wave < currentWave) { sp.color = new Color(70, 70, 70); // 已过回合置暗 cell.setScale(v3(1, 1, 1)); Tween.stopAllByTarget(cell); } else if (wave === currentWave) { sp.color = isBoss ? new Color(255, 80, 80) : new Color(255, 220, 100); if (isBoss) this.playFlagPulse(cell); } else { sp.color = isBoss ? new Color(220, 50, 50) : new Color(120, 120, 120); cell.setScale(v3(1, 1, 1)); Tween.stopAllByTarget(cell); } } } /** 当前回合 Boss 旗缩放脉动 */ private playFlagPulse(cell: Node) { Tween.stopAllByTarget(cell); tween(cell) .to(0.5, { scale: v3(1.4, 1.4, 1) }) .to(0.5, { scale: v3(1, 1, 1) }) .union() .repeatForever() .start(); } protected update(dt: number) { this.hudTimer += dt; if (this.hudTimer < 0.2) return; this.hudTimer = 0; const md = smc.vmdata.mission_data; const remain = (md.mon_num || 0) + (md.pending_mon_num || 0); if (this.remainLab) this.remainLab.string = `剩余 ${remain}`; if (this.progress) { this.progress.progress = this.waveTotal > 0 ? Math.min(1, Math.max(0, (this.waveTotal - remain) / this.waveTotal)) : 0; } } }