feat(game): 添加全套音效系统(背景BGM+7种攻击音+波次/Boss/死亡/建塔音效),纯Web Audio API合成
This commit is contained in:
289
game/AudioEngine.ts
Normal file
289
game/AudioEngine.ts
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
/**
|
||||||
|
* AudioEngine — 纯 Web Audio API 程序化合成,零音频文件依赖
|
||||||
|
*
|
||||||
|
* 使用单例模式,所有音效通过 AudioEngine.getInstance().play('soundName') 调用
|
||||||
|
* 背景音乐通过 startBGM() / stopBGM() 控制
|
||||||
|
*/
|
||||||
|
export class AudioEngine {
|
||||||
|
private static _instance: AudioEngine | null = null
|
||||||
|
private ctx: AudioContext | null = null
|
||||||
|
private bgmNodes: AudioNode[] = []
|
||||||
|
private bgmRunning = false
|
||||||
|
private muted = false
|
||||||
|
private masterGain!: GainNode
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
static getInstance(): AudioEngine {
|
||||||
|
if (!AudioEngine._instance) AudioEngine._instance = new AudioEngine()
|
||||||
|
return AudioEngine._instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 首次用户交互后调用,初始化 AudioContext */
|
||||||
|
init(): void {
|
||||||
|
if (this.ctx) return
|
||||||
|
try {
|
||||||
|
this.ctx = new AudioContext()
|
||||||
|
this.masterGain = this.ctx.createGain()
|
||||||
|
this.masterGain.gain.value = 0.55
|
||||||
|
this.masterGain.connect(this.ctx.destination)
|
||||||
|
} catch {
|
||||||
|
// 浏览器不支持 Web Audio,静默失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMuted(v: boolean): void {
|
||||||
|
this.muted = v
|
||||||
|
if (this.masterGain) this.masterGain.gain.value = v ? 0 : 0.55
|
||||||
|
}
|
||||||
|
isMuted(): boolean { return this.muted }
|
||||||
|
|
||||||
|
private get ac(): AudioContext | null { return this.ctx }
|
||||||
|
|
||||||
|
// ─── 工具方法 ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private osc(
|
||||||
|
type: OscillatorType,
|
||||||
|
freq: number,
|
||||||
|
startTime: number,
|
||||||
|
endTime: number,
|
||||||
|
gainStart = 0.3,
|
||||||
|
gainEnd = 0,
|
||||||
|
destination: AudioNode | null = null
|
||||||
|
): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const g = this.ac.createGain()
|
||||||
|
g.gain.setValueAtTime(gainStart, startTime)
|
||||||
|
g.gain.exponentialRampToValueAtTime(Math.max(gainEnd, 0.001), endTime)
|
||||||
|
g.connect(destination ?? this.masterGain)
|
||||||
|
const o = this.ac.createOscillator()
|
||||||
|
o.type = type
|
||||||
|
o.frequency.setValueAtTime(freq, startTime)
|
||||||
|
o.connect(g)
|
||||||
|
o.start(startTime)
|
||||||
|
o.stop(endTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private noise(
|
||||||
|
startTime: number,
|
||||||
|
duration: number,
|
||||||
|
gainVal = 0.15,
|
||||||
|
hipass = 0,
|
||||||
|
destination: AudioNode | null = null
|
||||||
|
): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const bufLen = this.ac.sampleRate * duration
|
||||||
|
const buf = this.ac.createBuffer(1, bufLen, this.ac.sampleRate)
|
||||||
|
const data = buf.getChannelData(0)
|
||||||
|
for (let i = 0; i < bufLen; i++) data[i] = Math.random() * 2 - 1
|
||||||
|
const src = this.ac.createBufferSource()
|
||||||
|
src.buffer = buf
|
||||||
|
const g = this.ac.createGain()
|
||||||
|
g.gain.setValueAtTime(gainVal, startTime)
|
||||||
|
g.gain.exponentialRampToValueAtTime(0.001, startTime + duration)
|
||||||
|
if (hipass > 0) {
|
||||||
|
const f = this.ac.createBiquadFilter()
|
||||||
|
f.type = 'highpass'
|
||||||
|
f.frequency.value = hipass
|
||||||
|
src.connect(f)
|
||||||
|
f.connect(g)
|
||||||
|
} else {
|
||||||
|
src.connect(g)
|
||||||
|
}
|
||||||
|
g.connect(destination ?? this.masterGain)
|
||||||
|
src.start(startTime)
|
||||||
|
src.stop(startTime + duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 音效库 ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** 钉钉消息通知音:建塔 / 波次开始 */
|
||||||
|
playDingTalk(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
// 叮 — 高频正弦
|
||||||
|
this.osc('sine', 1047, t, t + 0.18, 0.4, 0.001)
|
||||||
|
// 咚 — 低频跟随
|
||||||
|
this.osc('sine', 523, t + 0.1, t + 0.35, 0.25, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 钉钉消息发送音(更轻):波次开始 */
|
||||||
|
playNotify(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('sine', 880, t, t + 0.12, 0.3, 0.001)
|
||||||
|
this.osc('sine', 1320, t + 0.07, t + 0.2, 0.2, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拳击音:实习生近战 */
|
||||||
|
playPunch(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.noise(t, 0.06, 0.3, 200)
|
||||||
|
this.osc('sine', 120, t, t + 0.06, 0.35, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 键盘敲击音:资深开发射击 */
|
||||||
|
playKeyboard(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.noise(t, 0.04, 0.18, 1200)
|
||||||
|
this.osc('square', 440, t, t + 0.03, 0.12, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** PPT 大师 AOE — 空洞回声 */
|
||||||
|
playPPTBlast(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('sawtooth', 180, t, t + 0.4, 0.2, 0.001)
|
||||||
|
this.osc('sine', 90, t, t + 0.5, 0.15, 0.001)
|
||||||
|
this.noise(t, 0.3, 0.08, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 产品经理 — 需求变更铃 */
|
||||||
|
playPMAttack(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('triangle', 660, t, t + 0.15, 0.25, 0.001)
|
||||||
|
this.osc('triangle', 440, t + 0.08, t + 0.25, 0.2, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 运营专员 — 数据上涨 */
|
||||||
|
playOpsAttack(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('sine', 523, t, t + 0.08, 0.2, 0.001)
|
||||||
|
this.osc('sine', 659, t + 0.05, t + 0.15, 0.2, 0.001)
|
||||||
|
this.osc('sine', 784, t + 0.1, t + 0.22, 0.2, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 外包程序员 — 闷声丢出 */
|
||||||
|
playOutsourceAttack(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.noise(t, 0.08, 0.2, 300)
|
||||||
|
this.osc('sawtooth', 200, t, t + 0.08, 0.15, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 怪物死亡 — 碎纸机 */
|
||||||
|
playEnemyDeath(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.noise(t, 0.12, 0.22, 800)
|
||||||
|
this.noise(t + 0.05, 0.08, 0.15, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Boss 登场 — 警报 + 低沉冲击 */
|
||||||
|
playBossAppear(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
// 低沉轰鸣
|
||||||
|
this.osc('sawtooth', 55, t, t + 1.2, 0.5, 0.001)
|
||||||
|
this.osc('sawtooth', 60, t + 0.1, t + 1.0, 0.3, 0.001)
|
||||||
|
// 警报扫频
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const st = t + i * 0.25
|
||||||
|
this.osc('square', 440, st, st + 0.1, 0.25, 0.001)
|
||||||
|
this.osc('square', 660, st + 0.12, st + 0.22, 0.2, 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Boss 技能触发 — 电话挂断 */
|
||||||
|
playBossSkill(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('square', 1480, t, t + 0.06, 0.3, 0.001)
|
||||||
|
this.osc('square', 1109, t + 0.07, t + 0.13, 0.3, 0.001)
|
||||||
|
this.osc('square', 740, t + 0.14, t + 0.22, 0.3, 0.001)
|
||||||
|
this.noise(t, 0.25, 0.1, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 塔被摧毁 — 电话挂断 */
|
||||||
|
playTowerDestroyed(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('square', 440, t, t + 0.1, 0.35, 0.001)
|
||||||
|
this.osc('square', 350, t + 0.1, t + 0.25, 0.35, 0.001)
|
||||||
|
this.osc('square', 220, t + 0.2, t + 0.4, 0.35, 0.001)
|
||||||
|
this.noise(t, 0.4, 0.12, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新波次开始 — 钉钉来电铃声 */
|
||||||
|
playWaveStart(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
const pattern = [0, 0.18, 0.36, 0.64, 0.82, 1.0]
|
||||||
|
const freqs = [784, 784, 784, 659, 784, 880]
|
||||||
|
pattern.forEach((delay, i) => {
|
||||||
|
this.osc('sine', freqs[i], t + delay, t + delay + 0.14, 0.3, 0.001)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** KPI 告警(KPI < 30%)— 紧张低音 */
|
||||||
|
playKPIWarning(): void {
|
||||||
|
if (!this.ac) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
this.osc('sawtooth', 110, t, t + 0.3, 0.4, 0.001)
|
||||||
|
this.osc('sawtooth', 146, t + 0.1, t + 0.3, 0.3, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 背景音乐 ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动背景音乐(办公室 ambient:低频嗡鸣 + 轻微节奏脉冲)
|
||||||
|
* 完全程序化合成,循环播放
|
||||||
|
*/
|
||||||
|
startBGM(): void {
|
||||||
|
if (!this.ac || this.bgmRunning) return
|
||||||
|
this.bgmRunning = true
|
||||||
|
this._scheduleBGM()
|
||||||
|
}
|
||||||
|
|
||||||
|
stopBGM(): void {
|
||||||
|
this.bgmRunning = false
|
||||||
|
this.bgmNodes.forEach(n => {
|
||||||
|
try { (n as OscillatorNode).stop?.() } catch { /* ignore */ }
|
||||||
|
})
|
||||||
|
this.bgmNodes = []
|
||||||
|
}
|
||||||
|
|
||||||
|
private _scheduleBGM(): void {
|
||||||
|
if (!this.ac || !this.bgmRunning) return
|
||||||
|
const t = this.ac.currentTime
|
||||||
|
|
||||||
|
// 底层办公室嗡鸣(电脑风扇感)
|
||||||
|
const droneGain = this.ac.createGain()
|
||||||
|
droneGain.gain.value = 0.04
|
||||||
|
droneGain.connect(this.masterGain)
|
||||||
|
const drone = this.ac.createOscillator()
|
||||||
|
drone.type = 'sawtooth'
|
||||||
|
drone.frequency.setValueAtTime(55, t)
|
||||||
|
drone.frequency.linearRampToValueAtTime(58, t + 8)
|
||||||
|
drone.frequency.linearRampToValueAtTime(55, t + 16)
|
||||||
|
drone.connect(droneGain)
|
||||||
|
drone.start(t)
|
||||||
|
drone.stop(t + 16)
|
||||||
|
this.bgmNodes.push(drone)
|
||||||
|
|
||||||
|
// 低频键盘噪声节拍(每 0.8s 一拍)
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const beat = t + i * 0.8
|
||||||
|
const ng = this.ac.createGain()
|
||||||
|
ng.gain.setValueAtTime(0.03, beat)
|
||||||
|
ng.gain.exponentialRampToValueAtTime(0.001, beat + 0.15)
|
||||||
|
ng.connect(this.masterGain)
|
||||||
|
const ns = this.ac.createOscillator()
|
||||||
|
ns.type = 'square'
|
||||||
|
ns.frequency.value = i % 4 === 0 ? 130 : i % 2 === 0 ? 110 : 87
|
||||||
|
ns.connect(ng)
|
||||||
|
ns.start(beat)
|
||||||
|
ns.stop(beat + 0.12)
|
||||||
|
this.bgmNodes.push(ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16s 后循环
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.bgmRunning) this._scheduleBGM()
|
||||||
|
}, 15500)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import { HUD } from './ui/HUD'
|
|||||||
import { WeeklyReportModal } from './ui/WeeklyReportModal'
|
import { WeeklyReportModal } from './ui/WeeklyReportModal'
|
||||||
import { MapTransitionModal } from './ui/MapTransitionModal'
|
import { MapTransitionModal } from './ui/MapTransitionModal'
|
||||||
import { ALL_MAPS, type MapConfig } from './data/mapConfigs'
|
import { ALL_MAPS, type MapConfig } from './data/mapConfigs'
|
||||||
|
import { AudioEngine } from './AudioEngine'
|
||||||
|
|
||||||
void MAP_ROWS; void GAME_HEIGHT; void GAME_WIDTH; void BAR_X; void BAR_Y; void BAR_W; void BAR_H
|
void MAP_ROWS; void GAME_HEIGHT; void GAME_WIDTH; void BAR_X; void BAR_Y; void BAR_W; void BAR_H
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.manager = GameManager.getInstance()
|
this.manager = GameManager.getInstance()
|
||||||
this.manager.reset()
|
this.manager.reset()
|
||||||
|
|
||||||
// 读取难度(React 层通过 window.__gameDifficulty 传入)
|
// 读取难度
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const diff = (window as any).__gameDifficulty
|
const diff = (window as any).__gameDifficulty
|
||||||
if (diff === 'easy' || diff === 'normal' || diff === 'hard') {
|
if (diff === 'easy' || diff === 'normal' || diff === 'hard') {
|
||||||
@@ -99,7 +100,8 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.hcText = hudObjs.hcText
|
this.hcText = hudObjs.hcText
|
||||||
|
|
||||||
this.towerManager = new TowerManager(this)
|
this.towerManager = new TowerManager(this)
|
||||||
this.weeklyModal = new WeeklyReportModal({ onBossInspection: () => this.freezeAllTowers(3000),
|
this.weeklyModal = new WeeklyReportModal({
|
||||||
|
onBossInspection: () => this.freezeAllTowers(3000),
|
||||||
onFullStamina: () => this.refillAllStamina(),
|
onFullStamina: () => this.refillAllStamina(),
|
||||||
})
|
})
|
||||||
this.mapTransitionModal = new MapTransitionModal()
|
this.mapTransitionModal = new MapTransitionModal()
|
||||||
@@ -107,6 +109,7 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.hud = new HUD(this)
|
this.hud = new HUD(this)
|
||||||
this.hud.createWaveButton(() => this.onWaveButtonClick())
|
this.hud.createWaveButton(() => this.onWaveButtonClick())
|
||||||
this.loadMap(ALL_MAPS[0])
|
this.loadMap(ALL_MAPS[0])
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
;(window as any).__gameSelectTower = (type: TowerType | null) => {
|
;(window as any).__gameSelectTower = (type: TowerType | null) => {
|
||||||
this.selectedTowerType = type
|
this.selectedTowerType = type
|
||||||
@@ -114,11 +117,34 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化音效引擎(首次点击后激活 AudioContext)
|
||||||
|
const audio = AudioEngine.getInstance()
|
||||||
|
this.input.once('pointerdown', () => {
|
||||||
|
audio.init()
|
||||||
|
audio.startBGM()
|
||||||
|
})
|
||||||
|
// 添加静音切换按钮(右上角)
|
||||||
|
this.createMuteButton(audio)
|
||||||
|
|
||||||
this.setupInteraction()
|
this.setupInteraction()
|
||||||
this.setupManagerCallbacks()
|
this.setupManagerCallbacks()
|
||||||
if (typeof window !== 'undefined') { ;(window as any).__gameReady?.() }
|
if (typeof window !== 'undefined') { ;(window as any).__gameReady?.() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createMuteButton(audio: AudioEngine): void {
|
||||||
|
const btn = this.add.text(GAME_WIDTH - 10, 8, '🔊', {
|
||||||
|
fontSize: '16px', backgroundColor: 'rgba(0,0,0,0.35)',
|
||||||
|
padding: { x: 6, y: 3 },
|
||||||
|
}).setOrigin(1, 0).setDepth(50).setInteractive({ useHandCursor: true })
|
||||||
|
btn.on('pointerdown', () => {
|
||||||
|
const nowMuted = !audio.isMuted()
|
||||||
|
audio.setMuted(nowMuted)
|
||||||
|
btn.setText(nowMuted ? '🔇' : '🔊')
|
||||||
|
if (!nowMuted) audio.startBGM()
|
||||||
|
else audio.stopBGM()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private setupManagerCallbacks(): void {
|
private setupManagerCallbacks(): void {
|
||||||
this.manager.onHCChange.push((hc: number) => {
|
this.manager.onHCChange.push((hc: number) => {
|
||||||
this.hcText.setText(`HC: ${hc}`)
|
this.hcText.setText(`HC: ${hc}`)
|
||||||
@@ -127,6 +153,10 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.manager.onKPIChange.push((kpi: number) => {
|
this.manager.onKPIChange.push((kpi: number) => {
|
||||||
updateKPIBar(this.kpiBar, kpi)
|
updateKPIBar(this.kpiBar, kpi)
|
||||||
this.kpiText.setText(`${kpi}%`)
|
this.kpiText.setText(`${kpi}%`)
|
||||||
|
// KPI 危险时播放警告音(每次低于 30% 时触发一次)
|
||||||
|
if (kpi <= 30 && kpi > 0) {
|
||||||
|
AudioEngine.getInstance().playKPIWarning()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.manager.onGameOver.push(() => { this.hud.showGameOver() })
|
this.manager.onGameOver.push(() => { this.hud.showGameOver() })
|
||||||
this.manager.onVictory.push(() => { this.hud.showVictory() })
|
this.manager.onVictory.push(() => { this.hud.showVictory() })
|
||||||
@@ -205,6 +235,8 @@ export function createGameScene(PhaserLib: typeof Phaser): typeof Phaser.Scene {
|
|||||||
this.waveManager.startNextWave()
|
this.waveManager.startNextWave()
|
||||||
const waveNum = this.waveManager.getCurrentWaveNumber()
|
const waveNum = this.waveManager.getCurrentWaveNumber()
|
||||||
this.hud.showWaveBanner(waveNum, this.waveManager.totalWaves)
|
this.hud.showWaveBanner(waveNum, this.waveManager.totalWaves)
|
||||||
|
// 波次开始音效
|
||||||
|
AudioEngine.getInstance().playWaveStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWeeklyReport(): void {
|
private onWeeklyReport(): void {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { EnemyBase, type PathPoint } from './EnemyBase'
|
import { EnemyBase, type PathPoint } from './EnemyBase'
|
||||||
import { getRandomQuote } from '../data/quotes'
|
import { getRandomQuote } from '../data/quotes'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
|
|
||||||
export class BossVP extends EnemyBase {
|
export class BossVP extends EnemyBase {
|
||||||
private skillTimer: number = 20000
|
private skillTimer: number = 20000
|
||||||
@@ -20,6 +21,8 @@ export class BossVP extends EnemyBase {
|
|||||||
this.imageSprite.setDisplaySize(bossSize, bossSize)
|
this.imageSprite.setDisplaySize(bossSize, bossSize)
|
||||||
this.imageSprite.setDepth(12)
|
this.imageSprite.setDepth(12)
|
||||||
scene.cameras.main.flash(800, 255, 0, 0, false)
|
scene.cameras.main.flash(800, 255, 0, 0, false)
|
||||||
|
// Boss 登场音效
|
||||||
|
AudioEngine.getInstance().playBossAppear()
|
||||||
this.showBossAlert()
|
this.showBossAlert()
|
||||||
this.bossLabel = scene.add.text(this.x, this.y + this.cellH * 0.5, '空降VP', {
|
this.bossLabel = scene.add.text(this.x, this.y + this.cellH * 0.5, '空降VP', {
|
||||||
fontFamily: 'VT323, monospace', fontSize: '14px',
|
fontFamily: 'VT323, monospace', fontSize: '14px',
|
||||||
@@ -55,6 +58,8 @@ export class BossVP extends EnemyBase {
|
|||||||
|
|
||||||
private triggerOrgRestructure(): void {
|
private triggerOrgRestructure(): void {
|
||||||
this.onDestroyTower?.()
|
this.onDestroyTower?.()
|
||||||
|
// Boss 技能音效
|
||||||
|
AudioEngine.getInstance().playBossSkill()
|
||||||
const txt = this.scene.add
|
const txt = this.scene.add
|
||||||
.text(this.x, this.y - 40, '组织架构调整!', {
|
.text(this.x, this.y - 40, '组织架构调整!', {
|
||||||
fontFamily: 'VT323, monospace', fontSize: '18px',
|
fontFamily: 'VT323, monospace', fontSize: '18px',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type Phaser from 'phaser'
|
|||||||
import { GameManager } from '../GameManager'
|
import { GameManager } from '../GameManager'
|
||||||
import { HUD_HEIGHT } from '../constants'
|
import { HUD_HEIGHT } from '../constants'
|
||||||
import { getCellSize } from '../mapRenderer'
|
import { getCellSize } from '../mapRenderer'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import { ALL_MAPS } from '../data/mapConfigs'
|
import { ALL_MAPS } from '../data/mapConfigs'
|
||||||
|
|
||||||
export interface PathPoint { x: number; y: number }
|
export interface PathPoint { x: number; y: number }
|
||||||
@@ -228,6 +229,8 @@ export abstract class EnemyBase {
|
|||||||
this.isDead = true
|
this.isDead = true
|
||||||
const reward = this.hcRewardBonus ? this.hcReward * 2 : this.hcReward
|
const reward = this.hcRewardBonus ? this.hcReward * 2 : this.hcReward
|
||||||
GameManager.getInstance().addHC(reward)
|
GameManager.getInstance().addHC(reward)
|
||||||
|
// 怪物死亡音效(碎纸机)
|
||||||
|
AudioEngine.getInstance().playEnemyDeath()
|
||||||
this.onDeath()
|
this.onDeath()
|
||||||
this.destroy()
|
this.destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
import { GameManager } from '../GameManager'
|
import { GameManager } from '../GameManager'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
export class InternTower extends TowerBase {
|
export class InternTower extends TowerBase {
|
||||||
@@ -32,6 +33,7 @@ export class InternTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attack(target: EnemyBase): void {
|
attack(target: EnemyBase): void {
|
||||||
|
AudioEngine.getInstance().playPunch()
|
||||||
const isInstakill = Math.random() < 0.05 && target.hp < 500
|
const isInstakill = Math.random() < 0.05 && target.hp < 500
|
||||||
if (isInstakill) {
|
if (isInstakill) {
|
||||||
target.takeDamage(9999)
|
target.takeDamage(9999)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,6 +14,7 @@ export class OpsTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attack(target: EnemyBase): void {
|
attack(target: EnemyBase): void {
|
||||||
|
AudioEngine.getInstance().playOpsAttack()
|
||||||
this.fireChart(target)
|
this.fireChart(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,6 +16,7 @@ export class OutsourceTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attack(target: EnemyBase): void {
|
attack(target: EnemyBase): void {
|
||||||
|
AudioEngine.getInstance().playOutsourceAttack()
|
||||||
// 5% 概率 Bug 反弹(精力归零)
|
// 5% 概率 Bug 反弹(精力归零)
|
||||||
if (Math.random() < 0.05) {
|
if (Math.random() < 0.05) {
|
||||||
this.stamina = 0
|
this.stamina = 0
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,6 +16,7 @@ export class PMTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attack(target: EnemyBase): void {
|
attack(target: EnemyBase): void {
|
||||||
|
AudioEngine.getInstance().playPMAttack()
|
||||||
this.firePRD(target)
|
this.firePRD(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
export class PPTMasterTower extends TowerBase {
|
export class PPTMasterTower extends TowerBase {
|
||||||
@@ -12,6 +13,7 @@ export class PPTMasterTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attackAoe(enemies: EnemyBase[]): void {
|
attackAoe(enemies: EnemyBase[]): void {
|
||||||
|
AudioEngine.getInstance().playPPTBlast()
|
||||||
const rangePx = this.attackRange * this.cellW
|
const rangePx = this.attackRange * this.cellW
|
||||||
this.showAoeEffect(rangePx)
|
this.showAoeEffect(rangePx)
|
||||||
for (const e of enemies) {
|
for (const e of enemies) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type Phaser from 'phaser'
|
import type Phaser from 'phaser'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
export class SeniorDevTower extends TowerBase {
|
export class SeniorDevTower extends TowerBase {
|
||||||
@@ -8,6 +9,7 @@ export class SeniorDevTower extends TowerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attack(target: EnemyBase): void {
|
attack(target: EnemyBase): void {
|
||||||
|
AudioEngine.getInstance().playKeyboard()
|
||||||
this.fireCodeBullet(target)
|
this.fireCodeBullet(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type Phaser from 'phaser'
|
|||||||
import { GameManager } from '../GameManager'
|
import { GameManager } from '../GameManager'
|
||||||
import { HUD_HEIGHT, COFFEE_COST, STAMINA_MAX, STAMINA_REGEN } from '../constants'
|
import { HUD_HEIGHT, COFFEE_COST, STAMINA_MAX, STAMINA_REGEN } from '../constants'
|
||||||
import { getCellSize } from '../mapRenderer'
|
import { getCellSize } from '../mapRenderer'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
|
|
||||||
export abstract class TowerBase {
|
export abstract class TowerBase {
|
||||||
@@ -141,6 +142,8 @@ export abstract class TowerBase {
|
|||||||
this.isActive = true
|
this.isActive = true
|
||||||
this.imageSprite.setAlpha(1)
|
this.imageSprite.setAlpha(1)
|
||||||
this.updateStaminaBar()
|
this.updateStaminaBar()
|
||||||
|
// 购买咖啡音效
|
||||||
|
AudioEngine.getInstance().playDingTalk()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type Phaser from 'phaser'
|
|||||||
import { GameManager } from '../GameManager'
|
import { GameManager } from '../GameManager'
|
||||||
import { getCellSize } from '../mapRenderer'
|
import { getCellSize } from '../mapRenderer'
|
||||||
import { COFFEE_COST } from '../constants'
|
import { COFFEE_COST } from '../constants'
|
||||||
|
import { AudioEngine } from '../AudioEngine'
|
||||||
import type { EnemyBase } from '../enemies/EnemyBase'
|
import type { EnemyBase } from '../enemies/EnemyBase'
|
||||||
import { TowerBase } from './TowerBase'
|
import { TowerBase } from './TowerBase'
|
||||||
import { InternTower } from './InternTower'
|
import { InternTower } from './InternTower'
|
||||||
@@ -60,6 +61,8 @@ export class TowerManager {
|
|||||||
const tower = this.createTower(type, gridX, gridY)
|
const tower = this.createTower(type, gridX, gridY)
|
||||||
this.towers.push(tower)
|
this.towers.push(tower)
|
||||||
this.occupiedCells.add(`${gridX},${gridY}`)
|
this.occupiedCells.add(`${gridX},${gridY}`)
|
||||||
|
// 建塔音效(钉钉消息音)
|
||||||
|
AudioEngine.getInstance().playNotify()
|
||||||
|
|
||||||
// 监听实习生自毁事件
|
// 监听实习生自毁事件
|
||||||
if (tower instanceof InternTower) {
|
if (tower instanceof InternTower) {
|
||||||
@@ -226,6 +229,8 @@ export class TowerManager {
|
|||||||
this.showDestroyEffect(tower)
|
this.showDestroyEffect(tower)
|
||||||
tower.destroy()
|
tower.destroy()
|
||||||
this.removeTower(tower)
|
this.removeTower(tower)
|
||||||
|
// 塔被摧毁音效
|
||||||
|
AudioEngine.getInstance().playTowerDestroyed()
|
||||||
}
|
}
|
||||||
|
|
||||||
private showDestroyEffect(tower: TowerBase): void {
|
private showDestroyEffect(tower: TowerBase): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user