12 KiB
交互式电影游戏引擎 — Roadmap
技术栈
- 框架: Vue 3 (Composition API +
<script setup>) - 构建: Vite
- 状态管理: Pinia
- 可视化编辑器: Vue Flow
- 存储: IndexedDB (Dexie.js)
- BGM: Web Audio API
- 语言: TypeScript
- 视频: 原生
<video>+ A/B 双缓冲
实现路线
P18 视频加载失败恢复(待实现)
目标:视频加载失败时显示错误画面 + 重试/跳过按钮,不再静默黑屏。
engine/core/VideoManager.ts—play/switchTo增加错误回调 + 超时检测(5 秒)+ 重试(最多 3 次,指数退避 1s/2s/4s)src/components/VideoErrorOverlay.vue— 错误画面:图标 + 提示 + [重试] [跳过] 按钮src/stores/gameStore.ts—videoError状态src/App.vue— 整合 VideoErrorOverlay- 验证:断网播放 → 错误画面 → 重试恢复 → 跳过下一场景
P24 多画质视频 — 本地 + CDN 流双模式 ✅ 已完成 2026-06-10
目标:桌面版用本地 videoUrl,Web 版用 CDN streamingUrl(HLS 流)。
Web 版不打包视频文件,用户手动选择超清/高清/标清,系统提示各画质所需网速。
设计决策:
| 决策 | 做法 |
|---|---|
| 环境检测 | Electron preload.js 注入 __ELECTRON__ → VideoManager 判断走本地还是 CDN |
| Web 画质 | 用户从设置面板手动选择(超清/高清/标清),非带宽自适应。localStorage 持久化 |
| Web 打包 | pack:html 跳过 videos/ 目录,音频/图片/字幕保留 |
| HLS 兼容 | Safari 原生播放 .m3u8;Chrome/Edge 按需动态 import('hls.js')(~100KB) |
场景数据设计:
{
"id": "intro",
"videoUrl": "/videos/intro.mp4",
"streamingUrl": {
"超清 (1080P)": "https://cdn.example.com/hls/intro/1080p.m3u8",
"高清 (720P)": "https://cdn.example.com/hls/intro/720p.m3u8",
"标清 (480P)": "https://cdn.example.com/hls/intro/480p.m3u8"
}
}
设置面板画质选项:
| 选项 | 网速提示 |
|---|---|
| 超清 (1080P) | 需要 2.5 Mbps |
| 高清 (720P) | 需要 2 Mbps |
| 标清 (480P) | 需要 0.8 Mbps |
实现清单:
engine/types.ts—SceneNode.streamingUrl?: Record<string, string>engine/core/VideoManager.ts—resolveVideoUrl(scene, quality)+streamingQuality属性engine/core/Engine.ts—goToScene用resolveVideoUrl替代直接scene.videoUrlelectron/preload.js—contextBridge.exposeInMainWorld('__ELECTRON__', true)electron/main.js—webPreferences.preload加载 preload.jssrc/stores/gameStore.ts—preferredQuality+ localStorage 持久化src/components/AccessibilitySettings.vue— Web 模式新增画质下拉(附网速提示)src/App.vue— watchpreferredQuality→ sync 到engine.videoManager.streamingQualityscripts/pack-html.cjs— 跳过videos/目录- 验证:TypeScript + Vite build 通过
- 验证:Electron
window.__ELECTRON__= true,使用本地videoUrl - 验证:浏览器
window.__ELECTRON__= undefined,设置面板显示画质下拉 - 验证:
pack:html产物不包含videos/目录
P25 条件路由 — nextScene 支持条件数组 ✅ 已完成 2026-06-12
目标:nextScene 从单一场景 ID 扩展为条件路由数组。第一个满足条件的场景自动跳转,
否则 fallback 到末尾无条件的默认场景。不局限于 QTE,所有场景均可使用。
场景数据设计:
{
"id": "combat_router",
"nextScene": [
{ "conditions": [{ "variable": "enemy_hp", "op": "<=", "value": 0 }], "targetScene": "victory" },
{ "conditions": [{ "variable": "player_hp", "op": "<=", "value": 0 }], "targetScene": "defeat" },
{ "targetScene": "combat" }
]
}
引擎行为:
onVideoEnd(scene)
├── nextScene 是 string?→ 现存逻辑不变
├── nextScene 是 Choice[]?
│ → 遍历数组,第一个满足 conditions 的 → 跳转到它的 targetScene
│ → 都不满足 → endGame()
└── 无 nextScene → 现有逻辑不变
使用场景:
QTE 成功 → effects: enemy_hp -= 25
→ successScene = "combat_router"
├── enemy_hp <= 0 → victory 场景
├── player_hp <= 0 → defeat 场景
└── 否则 → 回到 QTE 场景(循环)
实现清单:
engine/types.ts—SceneNode.nextScene类型改为string | Choice[]engine/core/Engine.ts—onVideoEnd中加数组判断,遍历 conditions 跳转engine/core/SceneManager.ts—getCandidateTargetIds支持数组 nextScenesrc/components/StoryGallery.vue— BFS 遍历 +buildPlayerTree支持数组 nextScenepublic/scenes/demo.json— 新增combat_router条件路由示例- 验证:TypeScript + Vite build 通过
P26 关键节点过滤 — StoryGallery 只展示剧情分叉点 ✅ 已完成 2026-06-12
目标:StoryGallery 不再展示所有场景,只展示剧情关键节点(章节起始、选择分支点、结局点)。 QTE 场景和过渡/路由场景被过滤,子节点上浮一级。
三层判断:
| 优先级 | 来源 | 说明 |
|---|---|---|
| 1 | SceneNode.keyMoment |
手动覆盖。true=强制展示,false=强制隐藏 |
| 2 | endings[].sceneId |
结局节点 |
| 3 | 自动判断 | 章节起点 / 有 choices(分支点)→ 关键节点。QTE 不算 |
扁平化: 非关键节点不渲染,子节点上浮到父节点层级,路径语义不变。
实现清单:
engine/types.ts—SceneNode.keyMoment?: booleansrc/components/StoryGallery.vue—isKeyMoment()三层逻辑 +collectKeyTargets()扁平化非关键节点- 验证:TypeScript + Vite build 通过
P27 全局计时器 — 跨场景时间压力(待实现)
目标:跨场景倒计时,时间用尽强制跳转。独立的 TimerSystem 类 + 三个新 Effect 类型,
支持启动/停止/重置/加减时间。
Effect 类型:
| Effect type | 参数 | 说明 |
|---|---|---|
startTimer |
duration(秒), expireScene |
启动倒计时。如已存在则重置 |
stopTimer |
— | 暂停计时器 |
addTime |
value(秒) |
增加剩余时间(正数)或扣减(负数) |
使用场景:
{
"id": "chapter_start",
"onEnter": [
{ "type": "startTimer", "duration": 3600, "value": 3600, "target": "timeout_ending" }
]
}
TimerSystem 核心逻辑:
class TimerSystem {
private remaining: number = 0
private expireScene: string = ''
private intervalId: ReturnType<typeof setInterval> | null = null
private onExpire: ((sceneId: string) => void) | null = null
start(duration: number, expireScene: string) { ... }
stop() { ... }
addTime(seconds: number) { ... }
getRemaining(): number { ... }
}
setInterval 每秒递减,剩余 ≤0 时调用 onExpire(expireScene)。
UI 显示: PlaybackBar 右下角 MM:SS 格式,最后一分钟变红。
实现清单:
engine/systems/TimerSystem.ts— 新建 — 计时器核心逻辑engine/types.ts— Effect 新增startTimer/stopTimer/addTime类型engine/core/StateManager.ts—apply中处理新 Effectengine/core/Engine.ts— 集成TimerSystem;startChapter停止旧 Timerengine/systems/SaveSystem.ts— 存档/读档包含 Timer 状态src/components/PlaybackBar.vue— HUD 显示倒计时
P28 随机路由 — 变量初始值/场景随机选择(待实现)
目标:nextScene / 变量初始值支持随机选择,每次玩法不同。
P29 背包/装备系统 — 物品持有影响叙事(待实现)
目标:玩家可持有物品,物品影响 conditions 判断、选择可见性、场景解锁。
P30 通关评分/反馈 — 结算面板展示统计(待实现)
目标:通关后展示玩家行为统计(线索数、成就数、结局数)。DeathPanel 升级为通用的 ResultPanel,死亡和通关统一走这里。
数据设计(GameData 顶层):
{
"stats": [
{ "label": "线索发现", "variable": "investigation", "max": 5 },
{ "label": "QTE 成功次数", "variable": "qte_succeeded" },
{ "label": "达成结局数", "type": "endingsCount" }
]
}
| 字段 | 说明 |
|---|---|
label |
统计项名称 |
variable |
从 variables 读值 |
max |
满分(可选),用于进度条 |
type: "endingsCount" |
特殊统计 — visitedSceneIds ∩ endings[].sceneId 计数 |
实现清单:
engine/types.ts—GameData.stats?: StatDef[]src/components/DeathPanel.vue→ 升级为ResultPanel.vuesrc/App.vue—gameEnd触发后展示 ResultPanel
P31 战斗 HUD + 结算面板 — RPG HUD 流派 ✅ 已完成 2026-06-12
目标:战斗场景中展示角色属性 HUD(头像 + HP/MP 条 + 数值),胜利后弹出结算面板。 走 RPG HUD 流派,非极简派。战败不做结算面板,直接走战败叙事。
SceneNode 新增字段:
{
"id": "combat",
"videoUrl": "combat/combat.mp4",
"qte": { ... },
"battleHUD": [
{
"label": "你",
"portrait": "images/player.jpg",
"stats": [
{ "variable": "player_hp", "label": "HP", "max": 100 },
{ "variable": "player_mp", "label": "MP", "max": 50 },
{ "variable": "combo_score", "label": "连击" }
]
},
{
"label": "敌人",
"portrait": "images/enemy.jpg",
"stats": [
{ "variable": "enemy_hp", "label": "HP", "max": 100 }
]
}
],
"battleResult": {
"title": "战斗胜利!",
"stats": [
{ "label": "剩余生命", "variable": "player_hp" },
{ "label": "QTE 成功次数", "variable": "qte_succeeded" }
]
}
}
BattleHUD 字段说明:
| 字段 | 说明 |
|---|---|
label |
角色名称 |
portrait |
角色头像路径 |
stats |
属性数组。variable/label/max/style("bar" 或 "number",缺省时根据有无 max 自动判断) |
布局: 角色头像左侧,stats 竖排叠在头像右侧。多角色水平排列在屏幕一侧。
组件:
| 组件 | 说明 |
|---|---|
BattleHUD.vue |
战斗场景中显示角色属性条,variables 实时响应 |
BattleResult.vue |
胜利结算面板 — 标题 + stats + "继续"按钮 → 下一场景 |
实现清单:
engine/types.ts—BattleHUDStat/BattleHUDEntry/BattleResultStat/BattleResultDef接口src/components/BattleHUD.vue— 新建 — 角色头像 + stats 进度条/数值,i18n labelKeysrc/components/BattleResult.vue— 新建 — 胜利结算面板 + titleKey + "继续"按钮src/stores/gameStore.ts—variable()读值 +showBattleResult状态src/composables/useGameEngine.ts—sceneChange中检测scene.battleResult自动弹出src/App.vue— 整合 BattleHUD + BattleResultsrc/locales/zh.json+en.json—continue/toMenui18npublic/scenes/demo.json—right_door场景添加battleHUD示例- 验证:TypeScript + Vite build 通过
已完成
P0~P23 全部实现(除 P18)。详见 CHANGELOG.md。
相关文档
| 文档 | 说明 |
|---|---|
| docs/SCENE_JSON_SPEC.md | 场景 JSON 完整字段参考手册 |
| docs/ARCHITECTURE.md | 关键架构决策记录 |
| PRODUCTION.md | 生产级交付检查清单 |
| FUTURE.md | 远期功能扩展笔记 |
| CHANGELOG.md | 功能更新日志 |