feat: 完成肉鸽回合制游戏核心玩法迭代

新增连杀系统、金币飞行特效、波次HUD、屏幕震动等表现功能,重构怪物刷出逻辑与难度动态调节,调整回合回血比例,优化游戏节奏与体验。

主要变更:
1.  调整回合回血比例从0.5到0.4,优化前期节奏
2.  新增连杀计数与奖励系统,支持5/10/20连杀触发对应表现
3.  实现怪物死亡掉落金币的抛物线飞行特效
4.  增加波次进度HUD,显示剩余怪物与全局回合进度
5.  新增屏幕震动工具与战斗横幅统一展示系统
6.  重构怪物刷出逻辑,支持按回合类型调整刷怪间隔,加入清场加速机制
7.  优化动态难度调节算法,增加滞回与指数平滑,避免难度突变
8.  新增Boss预警、登场事件与回合清屏事件,完善事件总线
9.  调整怪物数量阈值与回合倒计时档位,适配新的节奏设计
This commit is contained in:
pan
2026-08-11 19:00:23 +08:00
parent d57f4c14e0
commit 409113e269
12 changed files with 904 additions and 80 deletions

View File

@@ -0,0 +1,123 @@
/**
* @file WaveHudComp.ts
* @description 波次进度 HUD 组件(表现层)
*
* 职责:
* 1. 本回合清剿进度剩余怪数mon_num + pending_mon_num+ 进度条。
* 2. 全局 20 回合进度格静态生成Boss 回合wave%5==0置旗帜样式当前回合脉动、已过回合置灰。
*
* 数据流NewWave 事件缓存 total/waveupdate 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: "全局回合旗帜容器(水平 Layout20 格代码生成)" })
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;
}
}
}