Files
tianshu-engine/ROADMAP.md

319 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 交互式电影游戏引擎 — 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 |
**场景数据设计:**
```json
{
"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 |
**实现清单:**
- [x] `engine/types.ts``SceneNode.streamingUrl?: Record<string, string>`
- [x] `engine/core/VideoManager.ts``resolveVideoUrl(scene, quality)` + `streamingQuality` 属性
- [x] `engine/core/Engine.ts``goToScene``resolveVideoUrl` 替代直接 `scene.videoUrl`
- [x] `electron/preload.js``contextBridge.exposeInMainWorld('__ELECTRON__', true)`
- [x] `electron/main.js``webPreferences.preload` 加载 preload.js
- [x] `src/stores/gameStore.ts``preferredQuality` + localStorage 持久化
- [x] `src/components/AccessibilitySettings.vue` — Web 模式新增画质下拉(附网速提示)
- [x] `src/App.vue` — watch `preferredQuality` → sync 到 `engine.videoManager.streamingQuality`
- [x] `scripts/pack-html.cjs` — 跳过 `videos/` 目录
- [x] 验证TypeScript + Vite build 通过
- [ ] 验证Electron `window.__ELECTRON__` = true使用本地 `videoUrl`
- [ ] 验证:浏览器 `window.__ELECTRON__` = undefined设置面板显示画质下拉
- [ ] 验证:`pack:html` 产物不包含 `videos/` 目录
### P25 条件路由 — nextScene 支持条件数组 ✅ 已完成 2026-06-12
目标:`nextScene` 从单一场景 ID 扩展为条件路由数组。第一个满足条件的场景自动跳转,
否则 fallback 到末尾无条件的默认场景。不局限于 QTE所有场景均可使用。
**场景数据设计:**
```json
{
"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 场景(循环)
```
**实现清单:**
- [x] `engine/types.ts``SceneNode.nextScene` 类型改为 `string | Choice[]`
- [x] `engine/core/Engine.ts``onVideoEnd` 中加数组判断,遍历 conditions 跳转
- [x] `engine/core/SceneManager.ts``getCandidateTargetIds` 支持数组 nextScene
- [x] `src/components/StoryGallery.vue` — BFS 遍历 + `buildPlayerTree` 支持数组 nextScene
- [x] `public/scenes/demo.json` — 新增 `combat_router` 条件路由示例
- [x] 验证TypeScript + Vite build 通过
### P26 关键节点过滤 — StoryGallery 只展示剧情分叉点 ✅ 已完成 2026-06-12
目标StoryGallery 不再展示所有场景,只展示剧情关键节点(章节起始、选择分支点、结局点)。
QTE 场景和过渡/路由场景被过滤,子节点上浮一级。
**三层判断:**
| 优先级 | 来源 | 说明 |
|:--:|------|------|
| 1 | `SceneNode.keyMoment` | 手动覆盖。`true`=强制展示,`false`=强制隐藏 |
| 2 | `endings[].sceneId` | 结局节点 |
| 3 | 自动判断 | 章节起点 / 有 `choices`(分支点)→ 关键节点。QTE 不算 |
**扁平化:** 非关键节点不渲染,子节点上浮到父节点层级,路径语义不变。
**实现清单:**
- [x] `engine/types.ts``SceneNode.keyMoment?: boolean`
- [x] `src/components/StoryGallery.vue``isKeyMoment()` 三层逻辑 + `collectKeyTargets()` 扁平化非关键节点
- [x] 验证TypeScript + Vite build 通过
### P27 全局计时器 — 跨场景时间压力(待实现)
目标:跨场景倒计时,时间用尽强制跳转。独立的 `TimerSystem` 类 + 三个新 Effect 类型,
支持启动/停止/重置/加减时间。
**Effect 类型:**
| Effect type | 参数 | 说明 |
|-------------|------|------|
| `startTimer` | `duration`(秒), `expireScene` | 启动倒计时。如已存在则重置 |
| `stopTimer` | — | 暂停计时器 |
| `addTime` | `value`(秒) | 增加剩余时间(正数)或扣减(负数) |
**使用场景:**
```json
{
"id": "chapter_start",
"onEnter": [
{ "type": "startTimer", "duration": 3600, "value": 3600, "target": "timeout_ending" }
]
}
```
**TimerSystem 核心逻辑:**
```typescript
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` 中处理新 Effect
- [ ] `engine/core/Engine.ts` — 集成 `TimerSystem``startChapter` 停止旧 Timer
- [ ] `engine/systems/SaveSystem.ts` — 存档/读档包含 Timer 状态
- [ ] `src/components/PlaybackBar.vue` — HUD 显示倒计时
### P28 随机路由 — 变量初始值/场景随机选择(待实现)
目标:`nextScene` / 变量初始值支持随机选择,每次玩法不同。
### P29 背包/装备系统 — 物品持有影响叙事(待实现)
目标:玩家可持有物品,物品影响 `conditions` 判断、选择可见性、场景解锁。
### P30 通关评分/反馈 — 结算面板展示统计(待实现)
目标通关后展示玩家行为统计线索数、成就数、结局数。DeathPanel 升级为通用的 `ResultPanel`,死亡和通关统一走这里。
**数据设计GameData 顶层):**
```json
{
"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.vue`
- [ ] `src/App.vue``gameEnd` 触发后展示 ResultPanel
### P31 战斗 HUD + 结算面板 — RPG HUD 流派 ✅ 已完成 2026-06-12
目标:战斗场景中展示角色属性 HUD头像 + HP/MP 条 + 数值),胜利后弹出结算面板。
走 RPG HUD 流派,非极简派。战败不做结算面板,直接走战败叙事。
**SceneNode 新增字段:**
```json
{
"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 + "继续"按钮 → 下一场景 |
**实现清单:**
- [x] `engine/types.ts``BattleHUDStat` / `BattleHUDEntry` / `BattleResultStat` / `BattleResultDef` 接口
- [x] `src/components/BattleHUD.vue`**新建** — 角色头像 + stats 进度条/数值i18n labelKey
- [x] `src/components/BattleResult.vue`**新建** — 胜利结算面板 + titleKey + "继续"按钮
- [x] `src/stores/gameStore.ts``variable()` 读值 + `showBattleResult` 状态
- [x] `src/composables/useGameEngine.ts``sceneChange` 中检测 `scene.battleResult` 自动弹出
- [x] `src/App.vue` — 整合 BattleHUD + BattleResult
- [x] `src/locales/zh.json` + `en.json``continue` / `toMenu` i18n
- [x] `public/scenes/demo.json``right_door` 场景添加 `battleHUD` 示例
- [x] 验证TypeScript + Vite build 通过
## 已完成
P0~P23 全部实现(除 P18。详见 [CHANGELOG.md](CHANGELOG.md)。
## 相关文档
| 文档 | 说明 |
|------|------|
| [docs/SCENE_JSON_SPEC.md](docs/SCENE_JSON_SPEC.md) | 场景 JSON 完整字段参考手册 |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | 关键架构决策记录 |
| [PRODUCTION.md](PRODUCTION.md) | 生产级交付检查清单 |
| [FUTURE.md](FUTURE.md) | 远期功能扩展笔记 |
| [CHANGELOG.md](CHANGELOG.md) | 功能更新日志 |