feat: video loop support for hotspot scenes, demo updates, docs, and engine fixes
This commit is contained in:
@@ -21,6 +21,7 @@ export class Engine {
|
||||
private qteTriggered = false
|
||||
private qteResolved = false
|
||||
private justCameFromImage = false
|
||||
private loopActive = false
|
||||
|
||||
constructor() {
|
||||
this.sceneManager = new SceneManager()
|
||||
@@ -29,7 +30,7 @@ export class Engine {
|
||||
this.choiceSystem = new ChoiceSystem()
|
||||
this.qteSystem = new QTESystem()
|
||||
|
||||
this.videoManager.onTimeUpdate(this.checkQTE)
|
||||
this.videoManager.onTimeUpdate(this.onTimeUpdate)
|
||||
}
|
||||
|
||||
on(event: EngineEvent, handler: EventHandler) {
|
||||
@@ -56,6 +57,7 @@ export class Engine {
|
||||
this.currentScene = scene
|
||||
this.qteTriggered = false
|
||||
this.qteResolved = false
|
||||
this.loopActive = false
|
||||
|
||||
if (scene.onEnter) {
|
||||
this.stateManager.apply(scene.onEnter)
|
||||
@@ -106,12 +108,14 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
private checkQTE = (time: number) => {
|
||||
private onTimeUpdate = (time: number) => {
|
||||
const scene = this.currentScene
|
||||
if (!scene) return
|
||||
|
||||
this.checkHotspotTime(scene, time)
|
||||
this.checkLoop(time)
|
||||
|
||||
// QTE check after loop check, so loop doesn't interfere with QTE
|
||||
if (!scene.qte || this.qteTriggered) return
|
||||
if (time >= scene.qte.triggerTime) {
|
||||
this.qteTriggered = true
|
||||
@@ -154,6 +158,33 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
private checkLoop(time: number) {
|
||||
const scene = this.currentScene
|
||||
if (!scene?.loopStart) return
|
||||
|
||||
if (!this.loopActive && time >= scene.loopStart) {
|
||||
this.loopActive = true
|
||||
const validChoices = this.getValidChoices(scene)
|
||||
if (validChoices.length > 0) {
|
||||
this.emit('choiceRequest', validChoices)
|
||||
this.choiceSystem.start(
|
||||
validChoices,
|
||||
(timerState) => {
|
||||
this.emit('choiceTimer', timerState)
|
||||
},
|
||||
(defaultChoice) => {
|
||||
this.emit('choiceTimeout', defaultChoice)
|
||||
this.makeChoice(defaultChoice)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.loopActive && scene.loopEnd && time >= scene.loopEnd) {
|
||||
this.videoManager.seekTo(scene.loopStart)
|
||||
}
|
||||
}
|
||||
|
||||
private checkHotspotTime(scene: SceneNode, time: number) {
|
||||
if (!scene.hotspots || scene.hotspots.length === 0) return
|
||||
|
||||
@@ -197,6 +228,8 @@ export class Engine {
|
||||
}
|
||||
|
||||
private onVideoEnd(scene: SceneNode) {
|
||||
if (this.loopActive) return
|
||||
|
||||
const validChoices = this.getValidChoices(scene)
|
||||
|
||||
if (validChoices.length > 0) {
|
||||
@@ -220,7 +253,6 @@ export class Engine {
|
||||
this.endGame()
|
||||
}
|
||||
} else if (scene.hotspots?.length) {
|
||||
// hotspot-only scene: wait for user to click a hotspot
|
||||
return
|
||||
} else {
|
||||
this.endGame()
|
||||
@@ -257,6 +289,7 @@ export class Engine {
|
||||
|
||||
endGame() {
|
||||
this.ended = true
|
||||
this.loopActive = false
|
||||
this.qteSystem.cancel()
|
||||
this.emit('gameEnd')
|
||||
}
|
||||
|
||||
@@ -149,6 +149,11 @@ export class VideoManager {
|
||||
return this.active ?? null
|
||||
}
|
||||
|
||||
seekTo(time: number) {
|
||||
if (!this.active) return
|
||||
this.active.currentTime = time
|
||||
}
|
||||
|
||||
onEnd(cb: VideoEndCallback) {
|
||||
this.onEndCallback = cb
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export interface SceneNode {
|
||||
qte?: QTEDefinition
|
||||
nextScene?: string
|
||||
onEnter?: Effect[]
|
||||
loopStart?: number
|
||||
loopEnd?: number
|
||||
}
|
||||
|
||||
export interface Choice {
|
||||
|
||||
Reference in New Issue
Block a user