feat: P2 - QTE system, subtitles, save thumbnails

- QTESystem: trigger detection via timeupdate, multi-key matching, timeout handling
- QTEOverlay: SVG countdown ring + key prompts + success/fail animation
- Engine: integrate QTE (timeupdate check, conditional branching, effect application)
- Subtitles: WebVTT parsing + synchronized subtitle rendering
- GamePlayer: overlay QTE and subtitle components
- SaveSystem: DB v2 with thumbnail field, canvas snapshot at 320x180 JPEG
- SaveLoadMenu: thumbnail preview for save slots
- VideoManager: getActiveVideoElement() for canvas capture
- App.vue: QTE/subtitle integration, thumbnail capture on save
- stores: QTE state management, save list with thumbnails
- demo.json: QTE scene (right_door), subtitles, new event types
- ROADMAP: mark P2 as completed
This commit is contained in:
2026-06-07 19:35:14 +08:00
parent c168e30e52
commit 319a379921
18 changed files with 625 additions and 53 deletions

View File

@@ -47,6 +47,22 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
store.setGameEnded(true)
engine.choiceSystem.stop()
})
engine.on('qteTrigger', (qte) => {
store.showQTE(qte)
})
engine.on('qteTimer', ({ remaining }) => {
store.updateQTE(remaining)
})
engine.on('qteResult', ({ success }) => {
store.resolveQTE(success)
})
engine.videoManager.onTimeUpdate((t: number) => {
store.setVideoTime(t)
})
}
function start() {
@@ -73,12 +89,31 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
async function saveGame(slot: number) {
const state = engine.stateManager
const currentScene = store.currentScene
// Capture thumbnail from active video
let thumbnail: string | undefined
try {
const video = engine.videoManager.getActiveVideoElement()
if (video && video.readyState >= 2) {
const canvas = document.createElement('canvas')
canvas.width = 320
canvas.height = 180
const ctx = canvas.getContext('2d')
if (ctx) {
ctx.drawImage(video, 0, 0, 320, 180)
thumbnail = canvas.toDataURL('image/jpeg', 0.6)
}
}
} catch { /* ignore canvas errors */ }
await saveSystem.save(slot, {
timestamp: Date.now(),
currentScene: store.currentScene?.id ?? '',
currentScene: currentScene?.id ?? '',
variables: state.variables,
flags: [...state.flags],
history: state.history,
thumbnail,
})
await refreshSaves()
}