200 lines
7.1 KiB
Markdown
200 lines
7.1 KiB
Markdown
# 交互式电影游戏引擎 — Roadmap
|
||
|
||
## 技术栈
|
||
|
||
- **框架**: Vue 3 (Composition API + `<script setup>`)
|
||
- **构建**: Vite
|
||
- **状态管理**: Pinia
|
||
- **可视化编辑器**: Vue Flow
|
||
- **存储**: IndexedDB (Dexie.js)
|
||
- **语言**: TypeScript
|
||
- **视频**: 原生 `<video>` + A/B 双缓冲
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
moviegame/
|
||
├── engine/ # 框架无关的核心引擎(纯 TS)
|
||
│ ├── core/
|
||
│ │ ├── Engine.ts # 主循环,驱动各子系统
|
||
│ │ ├── SceneManager.ts # 剧情节点图遍历
|
||
│ │ ├── VideoManager.ts # A/B 双缓冲视频播放
|
||
│ │ └── StateManager.ts # 全局状态、条件求值
|
||
│ ├── systems/
|
||
│ │ ├── ChoiceSystem.ts # 选择 UI + 倒计时
|
||
│ │ ├── QTESystem.ts # QTE 触发、判定、超时
|
||
│ │ └── SaveSystem.ts # IndexedDB 存取
|
||
│ └── types.ts # 场景数据类型定义
|
||
├── src/ # Vue 应用(播放器端)
|
||
│ ├── components/
|
||
│ │ ├── GamePlayer.vue # 主播放器(挂载 video 元素)
|
||
│ │ ├── ChoicePanel.vue # 选项面板
|
||
│ │ ├── QTEOverlay.vue # QTE 遮罩
|
||
│ │ ├── SaveLoadMenu.vue # 存档界面
|
||
│ │ └── Subtitles.vue # 字幕显示
|
||
│ ├── composables/
|
||
│ │ ├── useGameEngine.ts # 引擎实例 + 生命周期
|
||
│ │ ├── useVideoPlayer.ts # 视频状态响应式封装
|
||
│ │ └── useGameState.ts # 状态响应式封装
|
||
│ ├── stores/
|
||
│ │ └── gameStore.ts # 游戏全局状态(Pinia)
|
||
│ ├── App.vue
|
||
│ └── main.ts
|
||
├── editor/ # Vue Flow 可视化编辑器(独立入口)
|
||
│ ├── components/
|
||
│ │ ├── SceneGraph.vue
|
||
│ │ ├── NodeEditor.vue
|
||
│ │ └── PreviewPanel.vue
|
||
│ ├── composables/
|
||
│ │ └── useGraphEditor.ts
|
||
│ └── App.vue
|
||
├── public/
|
||
│ ├── videos/ # 视频资源
|
||
│ └── scenes/
|
||
│ └── demo.json # 示例剧情数据
|
||
├── vite.config.ts
|
||
├── tsconfig.json
|
||
├── package.json
|
||
└── index.html
|
||
```
|
||
|
||
## 场景数据格式
|
||
|
||
```typescript
|
||
// engine/types.ts
|
||
|
||
interface SceneNode {
|
||
id: string;
|
||
videoUrl: string;
|
||
subtitleUrl?: string;
|
||
choices?: Choice[];
|
||
qte?: QTEDefinition;
|
||
nextScene?: string;
|
||
onEnter?: Effect[];
|
||
}
|
||
|
||
interface Choice {
|
||
text: string;
|
||
targetScene: string;
|
||
conditions?: Condition[];
|
||
effects?: Effect[];
|
||
timeLimit?: number; // 限时选择(秒),0=不限时
|
||
}
|
||
|
||
interface Condition {
|
||
variable: string;
|
||
op: '>' | '<' | '>=' | '<=' | '==' | '!=' | 'hasFlag';
|
||
value: number | string | boolean;
|
||
}
|
||
|
||
interface Effect {
|
||
type: 'set' | 'add' | 'toggleFlag' | 'triggerEvent';
|
||
target: string;
|
||
value?: number | string | boolean;
|
||
}
|
||
|
||
interface QTEDefinition {
|
||
triggerTime: number;
|
||
prompt: string;
|
||
keys: string[];
|
||
timeLimit: number;
|
||
successScene: string;
|
||
failScene: string;
|
||
effects?: {
|
||
success: Effect[];
|
||
fail: Effect[];
|
||
};
|
||
}
|
||
|
||
interface GameData {
|
||
scenes: Record<string, SceneNode>;
|
||
startScene: string;
|
||
variables: Record<string, number>;
|
||
}
|
||
|
||
interface SaveData {
|
||
slot: number;
|
||
timestamp: number;
|
||
currentScene: string;
|
||
variables: Record<string, number>;
|
||
flags: string[];
|
||
history: ChoiceRecord[];
|
||
thumbnail?: string;
|
||
}
|
||
```
|
||
|
||
## 实现路线
|
||
|
||
### P0 MVP — 最小可玩原型(3-5 天)✅ 已完成 2026-06-07
|
||
|
||
目标:能播放一段视频 → 弹出选项 → 跳到下一段视频
|
||
|
||
- [x] 项目脚手架:Vite + Vue3 + TypeScript + Pinia
|
||
- [x] `engine/core/Engine.ts` — 主循环骨架(加载场景 → 播放 → 等选择 → 切换)
|
||
- [x] `engine/core/SceneManager.ts` — 加载 JSON,按 ID 查找场景节点
|
||
- [x] `engine/core/VideoManager.ts` — 单 video 元素播放,监听 ended 事件
|
||
- [x] `engine/core/StateManager.ts` — 变量存取、条件求值、效果执行
|
||
- [x] `engine/types.ts` — 类型定义
|
||
- [x] `src/components/GamePlayer.vue` — 挂载 video,控制播放
|
||
- [x] `src/components/ChoicePanel.vue` — 渲染选择按钮,触发引擎切换
|
||
- [x] `public/scenes/demo.json` — 编写一段简单剧情(7 个场景节点)
|
||
- [x] 验证:从 demo.json 加载场景,能走通 开始→选择→分支播放→结束 流程
|
||
|
||
### P1 核心 — 无缝切换 + 条件分支 + 存档(1-2 周)
|
||
|
||
- [ ] `engine/core/VideoManager.ts` 升级 — A/B 双缓冲,预加载候选视频,CSS 交叉淡化
|
||
- [ ] `engine/core/SceneManager.ts` 升级 — 支持条件分支(根据 variables/flags 过滤选项)
|
||
- [ ] `engine/systems/SaveSystem.ts` — Dexie.js IndexedDB 存取,多槽位
|
||
- [ ] `engine/systems/ChoiceSystem.ts` — 限时选择倒计时,超时默认选择(第一项或配置的默认项)
|
||
- [ ] `src/components/SaveLoadMenu.vue` — 存档/读档 UI
|
||
- [ ] `src/stores/gameStore.ts` — Pinia 全局状态管理
|
||
- [ ] `src/composables/` — 三个 composable 桥接层
|
||
- [ ] 验证:分支剧情走通,存档读档正常,视频切换无明显黑屏
|
||
|
||
### P2 进阶 — QTE + 字幕 + 多存档槽(1 周)
|
||
|
||
- [ ] `engine/systems/QTESystem.ts` — QTE 触发、键盘监听、超时判定
|
||
- [ ] `src/components/QTEOverlay.vue` — QTE 视觉遮罩(按键提示 + 倒计时环)
|
||
- [ ] `src/components/Subtitles.vue` — WebVTT 解析 + 字幕渲染
|
||
- [ ] 多存档槽位 + 存档缩略图(canvas 截图当前视频帧)
|
||
- [ ] `engine/core/Engine.ts` — 完整事件总线(sceneChange, choiceMade, qteTriggered 等)
|
||
- [ ] 验证:QTE 正常触发与判定,字幕同步,多存档正常工作
|
||
|
||
### P3 编辑器 — 可视化剧情编辑(2-3 周)
|
||
|
||
- [ ] 编辑器入口:独立 `editor/index.html` + `editor/main.ts`
|
||
- [ ] `editor/components/SceneGraph.vue` — Vue Flow 节点图(节点=场景,边=选择分支)
|
||
- [ ] `editor/components/NodeEditor.vue` — 右侧面板,编辑选中节点的视频、选项、QTE、条件/效果
|
||
- [ ] `editor/components/PreviewPanel.vue` — 嵌入播放器,实时预览当前编辑的剧情线
|
||
- [ ] `editor/composables/useGraphEditor.ts` — 图数据与 JSON 双向同步
|
||
- [ ] JSON 导出/导入
|
||
- [ ] 验证:编辑器能产出合法 JSON,引擎能正确加载并运行
|
||
|
||
## 依赖清单
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"vue": "^3.4",
|
||
"pinia": "^2.1",
|
||
"@vue-flow/core": "^1.x",
|
||
"@vue-flow/background": "^1.x",
|
||
"@vue-flow/controls": "^1.x",
|
||
"dexie": "^4.0"
|
||
},
|
||
"devDependencies": {
|
||
"@vitejs/plugin-vue": "^5.0",
|
||
"typescript": "^5.3",
|
||
"vite": "^5.0",
|
||
"vue-tsc": "^2.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
## 关键架构决策记录
|
||
|
||
1. **引擎与 UI 分离**: `engine/` 下纯 TS 类,不 import Vue。UI 层通过 composables 桥接。
|
||
2. **A/B 双缓冲**: 两个 `<video>` 元素轮换,一个播放时另一个预加载候选视频。
|
||
3. **JSON 驱动**: 所有剧情数据放在 JSON 中,编辑器本质是 JSON 的可视化读写工具。
|
||
4. **IndexedDB 存档**: 比 localStorage 容量大,可存储截屏缩略图。
|