Files
tianshu-engine/ROADMAP.md

12 KiB
Raw Permalink Blame History

交互式电影游戏引擎 — 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.tsplay/switchTo 增加错误回调 + 超时检测5 秒)+ 重试(最多 3 次,指数退避 1s/2s/4s
  • src/components/VideoErrorOverlay.vue — 错误画面:图标 + 提示 + [重试] [跳过] 按钮
  • src/stores/gameStore.tsvideoError 状态
  • src/App.vue — 整合 VideoErrorOverlay
  • 验证:断网播放 → 错误画面 → 重试恢复 → 跳过下一场景

P24 多画质视频 — 本地 + CDN 流双模式 已完成 2026-06-10

目标:桌面版用本地 videoUrlWeb 版用 CDN streamingUrlHLS 流)。 Web 版不打包视频文件,用户手动选择超清/高清/标清,系统提示各画质所需网速。

设计决策:

决策 做法
环境检测 Electron preload.js 注入 __ELECTRON__VideoManager 判断走本地还是 CDN
Web 画质 用户从设置面板手动选择(超清/高清/标清非带宽自适应。localStorage 持久化
Web 打包 pack:html 跳过 videos/ 目录,音频/图片/字幕保留
HLS 兼容 Safari 原生播放 .m3u8Chrome/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.tsSceneNode.streamingUrl?: Record<string, string>
  • engine/core/VideoManager.tsresolveVideoUrl(scene, quality) + streamingQuality 属性
  • engine/core/Engine.tsgoToSceneresolveVideoUrl 替代直接 scene.videoUrl
  • electron/preload.jscontextBridge.exposeInMainWorld('__ELECTRON__', true)
  • electron/main.jswebPreferences.preload 加载 preload.js
  • src/stores/gameStore.tspreferredQuality + localStorage 持久化
  • src/components/AccessibilitySettings.vue — Web 模式新增画质下拉(附网速提示)
  • src/App.vue — watch preferredQuality → sync 到 engine.videoManager.streamingQuality
  • scripts/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.tsSceneNode.nextScene 类型改为 string | Choice[]
  • engine/core/Engine.tsonVideoEnd 中加数组判断,遍历 conditions 跳转
  • engine/core/SceneManager.tsgetCandidateTargetIds 支持数组 nextScene
  • src/components/StoryGallery.vue — BFS 遍历 + buildPlayerTree 支持数组 nextScene
  • public/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.tsSceneNode.keyMoment?: boolean
  • src/components/StoryGallery.vueisKeyMoment() 三层逻辑 + 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.tsapply 中处理新 Effect
  • engine/core/Engine.ts — 集成 TimerSystemstartChapter 停止旧 Timer
  • engine/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.tsGameData.stats?: StatDef[]
  • src/components/DeathPanel.vue → 升级为 ResultPanel.vue
  • src/App.vuegameEnd 触发后展示 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.tsBattleHUDStat / BattleHUDEntry / BattleResultStat / BattleResultDef 接口
  • src/components/BattleHUD.vue新建 — 角色头像 + stats 进度条/数值i18n labelKey
  • src/components/BattleResult.vue新建 — 胜利结算面板 + titleKey + "继续"按钮
  • src/stores/gameStore.tsvariable() 读值 + showBattleResult 状态
  • src/composables/useGameEngine.tssceneChange 中检测 scene.battleResult 自动弹出
  • src/App.vue — 整合 BattleHUD + BattleResult
  • src/locales/zh.json + en.jsoncontinue / toMenu i18n
  • public/scenes/demo.jsonright_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 功能更新日志