import type { QTEDefinition } from '../types' type QTEUpdateCallback = (remaining: number, total: number) => void type QTEResultCallback = (success: boolean) => void export class QTESystem { // QTE (Quick Time Event / 快速反应事件): // 视频播放到特定时间点时弹出按键提示,玩家在倒计时内按下指定按键, // 成功/失败/超时分别导向不同剧情分支,并应用对应的 effects 效果。 private timerId: ReturnType | null = null private timeoutId: ReturnType | null = null private keyHandler: ((e: KeyboardEvent) => void) | null = null private tickMs = 50 private active = false trigger( qte: QTEDefinition, onUpdate: QTEUpdateCallback, onResult: QTEResultCallback, ) { if (this.active) return this.active = true const startTime = Date.now() const total = qte.timeLimit * 1000 this.keyHandler = (e: KeyboardEvent) => { if (!this.active) return const matched = qte.keys.some( (k) => k.toLowerCase() === e.key.toLowerCase() ) if (matched) { this.clear() onResult(true) } } document.addEventListener('keydown', this.keyHandler) this.timerId = setInterval(() => { const elapsed = Date.now() - startTime const remaining = Math.max(0, total - elapsed) onUpdate(remaining / 1000, qte.timeLimit) if (remaining <= 0) { this.clear() onResult(false) } }, this.tickMs) this.timeoutId = setTimeout(() => { this.clear() onResult(false) }, total) } cancel() { this.clear() } private clear() { this.active = false if (this.timerId !== null) { clearInterval(this.timerId) this.timerId = null } if (this.timeoutId !== null) { clearTimeout(this.timeoutId) this.timeoutId = null } if (this.keyHandler !== null) { document.removeEventListener('keydown', this.keyHandler) this.keyHandler = null } } destroy() { this.clear() } }