feat: chapter select system, multi-chapter support, scene manager refactor, and docs update

This commit is contained in:
2026-06-09 11:35:11 +08:00
parent 655b9a23d0
commit ace5ed1fb3
14 changed files with 413 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import type { SceneNode, Choice, EngineEvent, Hotspot } from '../types'
import type { SceneNode, Choice, EngineEvent, Hotspot, ChapterInfo } from '../types'
import { SceneManager } from './SceneManager'
import { VideoManager } from './VideoManager'
import { StateManager } from './StateManager'
@@ -24,6 +24,11 @@ export class Engine {
private qteResolved = false
private justCameFromImage = false
private loopActive = false
private onUnlockChapter: ((chapterId: string) => void) | null = null
setChapterUnlockHandler(handler: (chapterId: string) => void) {
this.onUnlockChapter = handler
}
constructor() {
this.sceneManager = new SceneManager()
@@ -62,6 +67,12 @@ export class Engine {
this.qteResolved = false
this.loopActive = false
const chapter = this.sceneManager.getChapterBySceneId(scene.id)
if (chapter) {
this.onUnlockChapter?.(chapter.id)
this.emit('chapterUnlock', chapter)
}
if (scene.onEnter) {
this.stateManager.apply(scene.onEnter)
}
@@ -333,6 +344,29 @@ export class Engine {
this.emit('gameEnd')
}
startChapter(chapterId: string) {
const chapter = this.sceneManager.getChapter(chapterId)
if (!chapter) return
const scene = this.sceneManager.getScene(chapter.startScene)
if (!scene) return
const defaultVars = chapter.defaultVariables
if (defaultVars) {
this.stateManager.variables = { ...defaultVars }
} else {
this.stateManager.init(this.sceneManager.chapters.length > 0
? {} // from chapters, use the chapter's defaultVariables or empty
: {})
}
this.stateManager.flags = new Set()
this.stateManager.history = []
this.ended = false
this.isInitialScene = false
this.goToScene(scene)
}
resumeScene(sceneId: string, savedState: { variables: Record<string, number>; flags: string[]; history: any[] }) {
this.stateManager.variables = { ...savedState.variables }
this.stateManager.flags = new Set(savedState.flags)

View File

@@ -1,12 +1,14 @@
import type { GameData, SceneNode, Choice, Condition } from '../types'
import type { GameData, SceneNode, ChapterInfo, Choice, Condition } from '../types'
export class SceneManager {
private scenes: Record<string, SceneNode> = {}
private startScene: string = ''
chapters: ChapterInfo[] = []
load(data: GameData) {
this.scenes = data.scenes
this.startScene = data.startScene
this.chapters = data.chapters || []
}
getScene(id: string): SceneNode | undefined {
@@ -23,6 +25,14 @@ export class SceneManager {
return Object.keys(this.scenes)
}
getChapterBySceneId(sceneId: string): ChapterInfo | undefined {
return this.chapters.find((ch) => ch.startScene === sceneId)
}
getChapter(chapterId: string): ChapterInfo | undefined {
return this.chapters.find((ch) => ch.id === chapterId)
}
getCandidateTargetIds(scene: SceneNode, evaluateCondition: (conds?: Condition[]) => boolean): string[] {
const targets: string[] = []