120 lines
6.7 KiB
Markdown
120 lines
6.7 KiB
Markdown
# 引擎开发约定与参考
|
||
|
||
本文档记录交互电影游戏引擎的开发规范、架构约定和工作流程,供后续开发对话中引用。
|
||
|
||
## 工作流程
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| **先讨论再执行** | 功能实现前必须先讨论方案,确认后再写代码 |
|
||
| **先更新 ROADMAP 再实现** | 新功能先写入 ROADMAP 的 P 条目,附实现清单,再逐项完成 |
|
||
| **不自动提交** | 代码写完不要 `git commit` / `git push`,等用户检查通过后再操作 |
|
||
| **验证方式** | `npx vue-tsc --noEmit` + `npx vite build` 通过视为基本验证 |
|
||
| **生成测试数据** | 新功能要生成配套的示例视频 / 音频 / JSON 数据 |
|
||
|
||
## 架构原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **引擎与 UI 分离** | `engine/` 下纯 TS 类,不 import Vue。UI 层通过 composables 桥接 |
|
||
| **A/B 双缓冲** | 两个 `<video>` 元素轮换,一个播放时另一个预加载候选场景 |
|
||
| **JSON 驱动** | 所有剧情数据放在 JSON 中,编辑器本质是 JSON 的可视化读写工具 |
|
||
| **IndexedDB 存档** | 比 localStorage 容量大,可存储截屏缩略图。多槽位支持 |
|
||
| **故事图与玩家树** | 创作端 JSON 是有向图,展示端 BFS 投影为树(汇聚节点复制展示) |
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
moviegame/
|
||
├── engine/ # 框架无关的核心引擎(纯 TS)
|
||
│ ├── core/
|
||
│ │ ├── Engine.ts # 主循环,驱动各子系统
|
||
│ │ ├── SceneManager.ts # 剧情节点图遍历、章节管理
|
||
│ │ ├── VideoManager.ts # A/B 双缓冲视频播放 + 流媒体质量选择
|
||
│ │ └── StateManager.ts # 全局状态、变量条件求值、效果执行
|
||
│ ├── systems/
|
||
│ │ ├── ChoiceSystem.ts # 限时选择 + 倒计时
|
||
│ │ ├── QTESystem.ts # QTE 触发、键盘监听、超时判定
|
||
│ │ ├── AudioSystem.ts # Web Audio API BGM + Ducking
|
||
│ │ ├── AchievementSystem.ts # 纯变量成就检测 + 解锁
|
||
│ │ └── SaveSystem.ts # IndexedDB 多表持久化
|
||
│ └── types.ts # 全部类型定义
|
||
├── src/
|
||
│ ├── components/ # Vue 组件——玩家端 UI + 编辑器 UI
|
||
│ ├── composables/ # 引擎 ↔ UI 桥接
|
||
│ ├── stores/ # Pinia 全局状态
|
||
│ └── locales/ # UI 文本翻译(静态 import,构建时打包)
|
||
├── editor/ # Vue Flow 可视化编辑器(独立入口)
|
||
├── electron/ # Electron 桌面应用打包
|
||
├── public/
|
||
│ └── demo/ # 示例素材——按场景分目录,每场景含视频、字幕、缩略图
|
||
│ └── locales/ # 故事文本翻译(动态 fetch 加载,制作者维护)
|
||
├── docs/
|
||
│ ├── SCENE_JSON_SPEC.md # 场景 JSON 完整字段参考
|
||
│ └── ARCHITECTURE.md # 关键架构决策记录
|
||
├── scripts/ # 构建与打包脚本
|
||
├── ROADMAP.md # 待实现功能清单
|
||
└── CHANGELOG.md # 功能更新日志
|
||
```
|
||
|
||
## 代码约定
|
||
|
||
| 规则 | 示例 |
|
||
|------|------|
|
||
| 不要写注释 | 代码自解释,不添加多余的中英文注释 |
|
||
| Engine 事件驱动 | `this.emit('sceneChange', scene)` → composable 响应 |
|
||
| composable 桥接 | `useGameEngine.ts` 是 Engine 和 Vue store 的中间层 |
|
||
| i18n 双文件分层 | `src/locales/` 存 UI 文本,`public/demo/locales/` 存故事文本。`useI18n.t()` 先查故事消息,fallback UI 消息 |
|
||
| 选择翻译在 composable 中 | `choiceRequest` 事件触发时 composable 调用 `t(textKey)` 翻译后存入 store |
|
||
| 引擎不感知 i18n | `Choice.textKey` 和 `Hotspot.labelKey` 是数据层字段,翻译完全在 Vue 层完成 |
|
||
| demo 素材按场景分目录 | `public/demo/<scene_id>/<file>` |
|
||
| demo 的 assetBase | `"assetBase": "demo/"` — 所有资源路径以此为前缀 |
|
||
|
||
## 菜单系统
|
||
|
||
| 菜单 | 设计 |
|
||
|------|------|
|
||
| **主菜单 (MainMenu.vue)** | 竖排单列,开始游戏最大、继续次之、底部小字装饰行(故事进度 · 成就 · 设置) |
|
||
| **暂停菜单 (PauseMenu.vue)** | ESC 弹出的全屏暂停,包含继续 / 存档 / 设置 / 返回主菜单 |
|
||
| **设置面板 (AccessibilitySettings.vue)** | 语言 + 画质(仅 Web) + 字幕 + QTE 辅助 + 防误触 + 可暂停 |
|
||
| **游戏内顶栏** | 精简:跳过(条件显示) · 倍速(条件显示) · 全屏 · ≡菜单 |
|
||
|
||
## 视频播放模式
|
||
|
||
| 模式 | 检测方式 | 使用的 URL 字段 | 切换方式 |
|
||
|------|---------|---------------|---------|
|
||
| **Electron 桌面** | `window.__ELECTRON__ === true` | `videoUrl`(本地 MP4) | 不切换,单文件 |
|
||
| **Web 浏览器** | `__ELECTRON__` undefined | `streamingUrl[quality]`(CDN HLS) | 设置面板手动选择超清/高清/标清 |
|
||
| **画质切换** | 仅在视频播放中切换(`video.ended === false`),结束后不切换等待下一场景 |
|
||
|
||
关键方法:
|
||
|
||
- `VideoManager.resolveVideoUrl(scene, quality)` — 环境检测 + URL 选择
|
||
- `VideoManager.switchQuality(src, seekTime)` — 实时切换画质
|
||
- `SceneManager.getCandidateSceneIds()` — 返回候选场景 ID,Engine 用 `resolveVideoUrl` 解析为 URL
|
||
- 预加载也走 `resolveVideoUrl`,Web 模式禁止预加载本地 MP4
|
||
|
||
## 场景 JSON 约定
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| 单文件包含全部场景 | 不拆分章节为独立 JSON 文件 |
|
||
| 章节用 `startScene` 标记 | 无 `endScene`,BFS 遍历可达场景作为章节范围 |
|
||
| 影像场景标记 | `"type": "image"`, `videoUrl` 为空 |
|
||
| QTE 配置 | `effects.success` / `effects.fail` 中附加变量修改,供成就检测 |
|
||
| 结局归属 | 通过 BFS 自动推导 `ending.sceneId` 属于哪个章节,不显式声明 `chapterId` |
|
||
|
||
## 成就系统
|
||
|
||
- 纯变量检测,在 `StateManager.apply` 末尾 `onAfterApply` 单一检查点触发
|
||
- 事件型成就改写为变量型:QTE 成功 → effects 中 `set: qte_succeeded, value: 1`
|
||
- 解锁时有 toast 弹出、持久化写入 IndexedDB `achievements` 表
|
||
|
||
## 流媒体 / HLS 约定
|
||
|
||
- 每场景三档 HLS,目录结构:`public/demo/<scene>/<quality>p/index.m3u8` + `seg_000.ts`
|
||
- `demo.json` 中每场景配 `streamingUrl: { "超清 (1080P)": "...", "高清 (720P)": "...", "标清 (480P)": "..." }`
|
||
- 生成命令:`ffmpeg -i source.mp4 -c:v libx264 -b:v <bitrate>k -c:a aac -b:a 128k -hls_time 2 -hls_segment_filename <dir>/seg_%03d.ts <dir>/index.m3u8`
|
||
- `pack-html.cjs` 跳过 `videos/` 目录(Web 版使用 CDN 流媒体)
|
||
- `pack-mac` / `pack-win` 保留完整视频文件(桌面版使用本地 MP4)
|