Files
pixelheros/assets/script/game/map/BattleBannerComp.ts
pan 409113e269 feat: 完成肉鸽回合制游戏核心玩法迭代
新增连杀系统、金币飞行特效、波次HUD、屏幕震动等表现功能,重构怪物刷出逻辑与难度动态调节,调整回合回血比例,优化游戏节奏与体验。

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

144 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file BattleBannerComp.ts
* @description 战斗横幅统一通道组件(表现层)
*
* 职责:
* 1. 监听清屏WaveClear/ Boss 预警BossWarning/ 连杀ComboReach事件播分级横幅。
* 2. 横幅动画右侧飞入backOut→ 中央停留 → 左侧飞出backIn与 MissionComp.playTooltipAnim 同风格。
* 3. 清屏奖励结算fastClear 金币 / allAlive 刷新石),走 MissionEconomy 静态接口。
* 4. Boss 登场BossSpawn触发震屏 + 音效。
*
* 使用 oops-framework 模块oops.message事件解耦、oops.audio音效
* 编辑器绑定bannerNodeLabel+背景节点,初始 active=false
*/
import { _decorator, Node, Label, tween, Tween, v3, Color } 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 { MissionEconomy } from "./MissionEconomy";
import { ScreenShake } from "../common/ScreenShake";
import { WAVE_DURATION } from "./RogueConfig";
const { ccclass, property } = _decorator;
/** 清屏奖励:迅速清场(< 75% 时长) */
const FAST_CLEAR_COIN = 2;
/** 清屏奖励:极速清场(< 50% 时长) */
const ULTRA_CLEAR_COIN = 5;
/** 连杀分级金币爆发tier 0/1/2 对应 5/10/20 连杀) */
const COMBO_COIN = [2, 5, 10];
@ccclass('BattleBannerComp')
export class BattleBannerComp extends CCComp {
/** 横幅节点Label+背景,初始隐藏),编辑器拖拽绑定 */
@property({ type: Node, tooltip: "横幅节点Label+背景,初始 active=false" })
bannerNode: Node | null = null;
/** 上次横幅播放时间戳去抖0.3s 内的新横幅直接打断旧的,不叠加 tween */
private lastBannerTime: number = 0;
onLoad() {
// oops.message 全局事件总线:清屏 / Boss 预警 / Boss 登场 / 连杀
oops.message.on(GameEvent.WaveClear, this.onWaveClear, this);
oops.message.on(GameEvent.BossWarning, this.onBossWarning, this);
oops.message.on(GameEvent.BossSpawn, this.onBossSpawn, this);
oops.message.on(GameEvent.ComboReach, this.onComboReach, this);
}
onDestroy() {
oops.message.off(GameEvent.WaveClear, this.onWaveClear, this);
oops.message.off(GameEvent.BossWarning, this.onBossWarning, this);
oops.message.off(GameEvent.BossSpawn, this.onBossSpawn, this);
oops.message.off(GameEvent.ComboReach, this.onComboReach, this);
}
// ======================== 事件处理 ========================
/** 清屏庆祝:分级文案 + 奖励结算 */
private onWaveClear(event: string, data: { wave: number; clearTime: number; allAlive: boolean; fastClear: number }) {
if (data.allAlive) {
this.playBanner("Perfect!", new Color(255, 80, 80), 1.2);
MissionEconomy.addRefreshStone(1);
oops.audio.playEffect("music/flash");
} else if (data.fastClear === 2) {
this.playBanner("极速清场!", new Color(255, 170, 0), 1.15);
MissionEconomy.addCoin(ULTRA_CLEAR_COIN);
oops.audio.playEffect("music/flash");
} else if (data.fastClear === 1) {
this.playBanner("迅速清场!", new Color(255, 220, 60), 1.05);
MissionEconomy.addCoin(FAST_CLEAR_COIN);
} else {
this.playBanner("Clear!", new Color(255, 255, 255), 1.0);
}
}
/** Boss 回合预警:红字横幅 */
private onBossWarning(event: string, data: { wave: number; eta: number }) {
this.playBanner("强敌来袭!", new Color(255, 60, 60), 1.3);
oops.audio.playEffect("music/flash");
}
/** Boss 登场:震屏 + 闷响 */
private onBossSpawn() {
ScreenShake.shake(10, 0.4);
oops.audio.playEffect("music/dun");
}
/** 连杀达阈值:分级文案 + 震屏 + 金币爆发 */
private onComboReach(event: string, data: { count: number; tier: number }) {
const tier = Math.min(data.tier, 2);
const colors = [new Color(255, 230, 80), new Color(255, 150, 30), new Color(255, 60, 60)];
const scales = [1.0, 1.2, 1.4];
this.playBanner(`${data.count} 连杀!`, colors[tier], scales[tier]);
if (tier === 1) ScreenShake.shake(4, 0.2);
if (tier === 2) ScreenShake.shake(8, 0.3);
MissionEconomy.addCoin(COMBO_COIN[tier]);
// 占位音效分级(后续可替换为升调 combo 音效)
oops.audio.playEffect(tier === 2 ? "music/Fire" : tier === 1 ? "music/Critical" : "music/Hit");
}
// ======================== 横幅播放 ========================
/**
* 播放横幅:右进 → 中央停留 → 左出
* @param text 文案
* @param color 文字颜色
* @param scale 整体缩放(分级表现力)
*/
private playBanner(text: string, color: Color, scale: number = 1.0) {
if (!this.bannerNode || !this.bannerNode.isValid) return;
// 去抖0.3s 内的新横幅直接打断旧的
const now = Date.now();
if (now - this.lastBannerTime < 300) {
Tween.stopAllByTarget(this.bannerNode);
}
this.lastBannerTime = now;
this.bannerNode.active = true;
this.bannerNode.setScale(v3(scale, scale, 1));
const label = this.bannerNode.getComponentInChildren(Label);
if (label) {
label.string = text;
label.color = color;
label.updateRenderData(true);
}
Tween.stopAllByTarget(this.bannerNode);
const startPos = v3(1200, 0, 0);
const centerPos = v3(0, 0, 0);
const endPos = v3(-1200, 0, 0);
this.bannerNode.setPosition(startPos);
tween(this.bannerNode)
.to(0.4, { position: centerPos }, { easing: "backOut" })
.to(0.8, { position: v3(-40, 0, 0) }, { easing: "sineInOut" })
.to(0.35, { position: endPos }, { easing: "backIn" })
.call(() => {
this.bannerNode!.active = false;
})
.start();
}
}