import * as Phaser from "phaser"; import { DigitRenderer } from "./DigitRenderer"; /** * 倒计时显示组件(LED / 七段数码管风格)。 * 同时处理视觉渲染和计时器逻辑(启动、停止、经过时间追踪)。 */ export class CountdownDisplay { private scene: Phaser.Scene; private container: Phaser.GameObjects.Container; private digitGraphics: Phaser.GameObjects.Graphics[] = []; private digitWidth: number; private digitHeight: number; private digitSpacing: number; private colonWidth: number; // 计时器逻辑(使用毫秒以避免浮点精度漂移) private elapsedMs = 0; private totalMs = 0; private lastTimerUpdateMs: number | undefined; private timerRunning = false; private timeoutTriggered = false; private onTimeoutCallback: (() => void) | undefined; constructor( scene: Phaser.Scene, parent: Phaser.GameObjects.Container, options: { xLeft: number; y: number; digitWidth: number; digitHeight: number; digitSpacing: number; colonWidth: number; totalSeconds: number; onTimeout?: () => void; }, ) { this.scene = scene; this.digitWidth = options.digitWidth; this.digitHeight = options.digitHeight; this.digitSpacing = options.digitSpacing; this.colonWidth = options.colonWidth; this.totalMs = Math.max(0, Math.floor(options.totalSeconds * 1000)); this.onTimeoutCallback = options.onTimeout; const totalWidth = this.digitWidth * 4 + this.colonWidth + this.digitSpacing * 4; this.container = scene.add.container( options.xLeft + totalWidth / 2, options.y, ); this.container.setSize(totalWidth, this.digitHeight); parent.add(this.container); // 创建5个图形对象:分钟十位、分钟个位、冒号、秒钟十位、秒钟个位 let offsetX = -totalWidth / 2; for (let i = 0; i < 5; i++) { const g = scene.add.graphics(); const isColon = i === 2; const w = isColon ? this.colonWidth : this.digitWidth; g.setPosition(offsetX + w / 2, 0); offsetX += w; // 仅在元素之间添加间距(共4个间隔),最后一个元素后不加 if (i !== 4) { offsetX += this.digitSpacing; } this.digitGraphics.push(g); this.container.add(g); } } /** 更新倒计时的视觉显示 */ public updateDisplay(remainingOverride?: number): void { if (!this.digitGraphics || this.digitGraphics.length < 5) { return; } const remainingMsRaw = remainingOverride !== undefined ? remainingOverride : Math.max(this.totalMs - this.elapsedMs, 0); // 以向上取整到整秒来渲染,使显示每秒跳动一次 const remainingSeconds = Math.max(0, Math.ceil(remainingMsRaw / 1000)); const mins = Math.floor(remainingSeconds / 60); const secs = remainingSeconds % 60; // 颜色:正常灰色,剩余 ≤ 10秒时显示警告红色 const color = remainingSeconds <= 10 ? 0xff3333 : 0x50536b; const digitWidth = this.digitWidth || 20; const digitHeight = this.digitHeight || 36; // 分钟十位 DigitRenderer.drawDigit( this.digitGraphics[0], Math.floor(mins / 10), color, digitWidth, digitHeight, ); // 分钟个位 DigitRenderer.drawDigit( this.digitGraphics[1], mins % 10, color, digitWidth, digitHeight, ); // 冒号 DigitRenderer.drawColon(this.digitGraphics[2], color, digitWidth); // 秒钟十位 DigitRenderer.drawDigit( this.digitGraphics[3], Math.floor(secs / 10), color, digitWidth, digitHeight, ); // 秒钟个位 DigitRenderer.drawDigit( this.digitGraphics[4], secs % 10, color, digitWidth, digitHeight, ); } /** 每帧调用以推进计时器 */ public updateTimer(deltaMs: number, nowMs?: number): void { const prevRemainingMs = Math.max(this.totalMs - this.elapsedMs, 0); if (this.timerRunning && prevRemainingMs > 0) { // 优先使用绝对时间戳以避免帧增量平滑漂移 let usedDeltaMs = deltaMs; if (nowMs !== undefined) { if (this.lastTimerUpdateMs === undefined) { this.lastTimerUpdateMs = nowMs; usedDeltaMs = 0; } else { usedDeltaMs = nowMs - this.lastTimerUpdateMs; this.lastTimerUpdateMs = nowMs; } } if (Number.isFinite(usedDeltaMs) && usedDeltaMs > 0) { this.elapsedMs += usedDeltaMs; } } else { // 未运行时同步时间戳,避免恢复时出现跳跃 if (nowMs !== undefined) { this.lastTimerUpdateMs = nowMs; } else { this.lastTimerUpdateMs = undefined; } } const remainingMs = Math.max(this.totalMs - this.elapsedMs, 0); if ( this.timerRunning && !this.timeoutTriggered && prevRemainingMs > 0 && remainingMs <= 0 ) { this.timeoutTriggered = true; if (this.onTimeoutCallback) { this.onTimeoutCallback(); } } this.updateDisplay(remainingMs); } /** * 为倒计时添加额外时间。 * @param seconds 要添加的秒数 */ public addTime(seconds: number): void { if (seconds <= 0) return; this.totalMs += seconds * 1000; this.timeoutTriggered = false; const remainingMs = Math.max(this.totalMs - this.elapsedMs, 0); this.updateDisplay(remainingMs); } /** 启动计时器 */ public startTimer(): void { this.timerRunning = true; this.lastTimerUpdateMs = undefined; } /** 停止计时器 */ public stopTimer(): void { this.timerRunning = false; this.lastTimerUpdateMs = undefined; } /** 获取已经过的秒数 */ public getElapsedSeconds(): number { return Math.floor(this.elapsedMs / 1000); } }