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:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user