From 686b1b45eac5f4a92172bfd01fe8904569ee1a06 Mon Sep 17 00:00:00 2001 From: cocos02 Date: Wed, 10 Jun 2026 17:01:48 +0800 Subject: [PATCH] docs: reorganize docs into guide/ and electron/, add 6 new guide docs, update README --- README.md | 147 +++++----- .../mirror-setup.md} | 0 .../packaging-guide.md} | 0 .../port-finder.md} | 0 .../window-controls.md} | 0 docs/guide/BRANCHING.md | 138 ++++++++++ docs/guide/I18N.md | 159 +++++++++++ docs/guide/INTERACTIONS.md | 123 +++++++++ docs/guide/PUBLISHING.md | 116 ++++++++ docs/guide/QUICK_START.md | 87 ++++++ docs/guide/SCENE_JSON_SPEC.md | 255 ++++++++++++++++++ 11 files changed, 950 insertions(+), 75 deletions(-) rename docs/{electron-mirror-setup.md => electron/mirror-setup.md} (100%) rename docs/{electron-packaging-guide.md => electron/packaging-guide.md} (100%) rename docs/{electron-port-finder.md => electron/port-finder.md} (100%) rename docs/{electron-window-controls.md => electron/window-controls.md} (100%) create mode 100644 docs/guide/BRANCHING.md create mode 100644 docs/guide/I18N.md create mode 100644 docs/guide/INTERACTIONS.md create mode 100644 docs/guide/PUBLISHING.md create mode 100644 docs/guide/QUICK_START.md create mode 100644 docs/guide/SCENE_JSON_SPEC.md diff --git a/README.md b/README.md index 5572888..22e062c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 交互式电影游戏引擎 基于 Vue 3 + TypeScript 的浏览器端交互式电影游戏引擎。 -**零代码门槛**:你只需要会剪视频和写 JSON,不需要前端知识。 +**零代码门槛**:你只需要会剪视频和写 JSON。 ## 快速开始 @@ -12,33 +12,35 @@ npm install npm run dev ``` -打开 `http://localhost:5173/`,你会看到示例剧情。 +打开 `http://localhost:5173/` 看示例剧情。 +打开 `http://localhost:5173/editor/` 使用可视化剧情编辑器。 ## 制作你的游戏 ### 1. 准备素材 -| 素材 | 目录 | 格式要求 | -|------|------|---------| -| 视频文件 | `public/videos/` | MP4 (H.264),1280×720,30fps,2-5Mbps | -| 背景音乐 | `public/audio/` | MP3 | -| 缩略图 | `public/images/` | JPG/PNG,320×180 | -| 字幕 | `public/subtitles/` | WebVTT (.vtt) | +按场景组织素材,每个场景一个文件夹: -> `public/videos/` 已在 `.gitignore` 中,视频文件不需要提交到 Git。 +``` +public/my_story/ + scene_1/video.mp4 ← 视频文件 (MP4 H.264, 1280×720) + scene_2/video.mp4 + shared/bgm.mp3 ← 跨场景共享素材 +``` -### 2. 编写剧情 JSON +### 2. 写剧情 JSON -编辑 `public/scenes/demo.json`,定义你的场景和分支。**最小可玩示例只需 20 行 JSON**: +创建 `public/scenes/my_story.json`: ```json { - "startScene": "intro", + "assetBase": "my_story/", + "startScene": "scene_1", "variables": { "trust": 50 }, "scenes": { - "intro": { - "id": "intro", - "videoUrl": "/videos/intro.mp4", + "scene_1": { + "id": "scene_1", + "videoUrl": "scene_1/video.mp4", "choices": [ { "text": "帮助他", "targetScene": "help_end" }, { "text": "离开", "targetScene": "leave_end" } @@ -46,105 +48,100 @@ npm run dev }, "help_end": { "id": "help_end", - "videoUrl": "/videos/help.mp4", + "videoUrl": "scene_2/video.mp4", "choices": [] }, "leave_end": { "id": "leave_end", - "videoUrl": "/videos/leave.mp4", + "videoUrl": "shared/video.mp4", "choices": [] } } } ``` -完整字段参考见 **[docs/SCENE_JSON_SPEC.md](docs/SCENE_JSON_SPEC.md)**。 - -### 3. 实时预览 +### 3. 运行 ```bash npm run dev ``` -Vite 会启动热重载服务器,修改 JSON 或视频后自动刷新。 - -### 4. 可视化编辑剧情 - -浏览器打开 `http://localhost:5173/editor/`,可以用节点图编辑器拖拽编辑场景分支。 - -## 打包发布 - -```bash -npm run pack:html # Web 版 → release/mygame.zip → 上传 itch.io / Netlify -npm run pack:mac # macOS → release/MyGame-darwin-arm64/ -npm run pack:win # Windows → release/MyGame-win32-x64/ -``` - -打包前会自动验证 JSON 合法性。 +浏览器打开 `http://localhost:5173/?scene=my_story.json`。 ## 引擎功能 | 功能 | 说明 | |------|------| | 视频分支播放 | A/B 双缓冲无缝切换,300ms 交叉淡化 | -| 选择系统 | 限时选择、条件分支(根据变量显示/隐藏选项) | -| QTE 快速反应事件 | 视频中插入限时按键挑战,成功/失败跳转不同场景 | -| 图片/视频热点 | 点击画面区域触发分支,视频热点按时间轴显隐 | -| 循环等待 | 视频结束后自动循环指定片段,保持画面动态 | -| 独立 BGM | 背景音乐独立驱动,场景切换时交叉淡化,不受画面循环影响 | -| BGM Ducking | 选择/QTE/热点出现时 BGM 自动降低音量 | -| 字幕系统 | WebVTT 解析,多语言字幕切换 | -| 章节系统 | 分章节管理剧情,到达即解锁,可跳转 | -| 成就系统 | 变量满足条件时自动解锁,底部弹窗提示 | -| 结局画廊 | 所有结局缩略图展示,已解锁/未解锁状态 | -| 章节回顾 | 每章完成度百分比 + 未解锁分支条件提示 | -| 关键选择提示 | 重要选项前置金色标识 + 选后浮现提示文字 | -| 跳过已看 + 倍速 | 已看场景可跳过,1x/2x/4x 倍速播放 | -| 全屏模式 | 一键全屏沉浸式浏览器体验 | -| 键盘导航 | 方向键选选项,Esc 菜单,Space 暂停 | -| 多语言 i18n | UI + 字幕支持中英文切换 | -| 可访问性 | 字幕字号/背景、QTE 时限放宽/按键简化、防误触延迟 | +| 选择系统 | 限时选择、条件分支(根据变量显示/隐藏) | +| 选择提示 (Prompt) | 重要选项前置金色标识 + 选后浮现提示文字 | +| QTE 快速反应事件 | 视频中限时按键挑战,成功/失败导向不同分支 | +| 图片/视频热点 | 像素级精准定位,视频热点按时间显隐 | +| 循环等待 | 指定片段反复播放直到玩家做选择 | +| 独立 BGM | 背景音乐独立轨道,场景切换交叉淡化 | +| BGM Ducking | 选择/QTE/热点出现时 BGM 自动压低 | +| 字幕系统 | WebVTT 解析,多语言字幕一键切换 | +| 多语言 i18n | UI + 数据层双语体系,支持动态扩展语言 | +| 章节系统 | 分章节管理,到达即解锁 | +| 成就系统 | 条件触发自动解锁,底部 Toast 弹窗 | +| 故事进度总览 | 章节完成度 + 结局解锁状态 + 条件提示 | | 存档系统 | IndexedDB 多槽位,跨会话持久化 | +| 开场视频 + 菜单背景 | 电影级启动流程 | +| 暂停菜单 | Esc 呼出暂停面板:继续/存档/设置/主菜单 | +| 可访问性 | 字幕字号/背景、QTE 时限放宽/按键简化、防误触 | +| 跳过/倍速 | 已看场景可跳过,1x/2x/4x 倍速 | +| 全屏模式 | 一键全屏沉浸式体验 | +| 键盘导航 | 方向键选选项,Esc 暂停/菜单,P 暂停 | +| 自动隐藏 UI | 3s 无操作隐藏顶栏和光标 | +| Electron 打包 | macOS / Windows 桌面应用 | + +## 文档索引 + +| 文档 | 内容 | 适合 | +|------|------|------| +| [**快速开始**](docs/guide/QUICK_START.md) | 5 分钟做一个游戏 | 新手 | +| [**JSON 字段参考**](docs/guide/SCENE_JSON_SPEC.md) | 全部字段 + 类型说明 | 必读 | +| [**分支叙事**](docs/guide/BRANCHING.md) | 选项/条件/变量/效果/Prompt | 进阶 | +| [**交互指南**](docs/guide/INTERACTIONS.md) | QTE / Hotspot / Loop | 进阶 | +| [**国际化**](docs/guide/I18N.md) | 多语言 UI + 数据层 | 进阶 | +| [**打包发布**](docs/guide/PUBLISHING.md) | Web + macOS + Windows | 分发 | +| [**编辑器**](editor/README.md) | 可视化剧情编辑 | 可选 | +| [**开发路线图**](ROADMAP.md) | 已实现/待实现功能 | 参考 | ## 目录结构 ``` mygame/ ├── engine/ # 引擎核心(纯 TS,不依赖 Vue) -│ ├── core/ # Engine / VideoManager / StateManager / SceneManager +│ ├── core/ # Engine / VideoManager / SceneManager │ ├── systems/ # QTE / Choice / Audio / Achievement / Save -│ └── types.ts # 类型定义 +│ └── types.ts ├── src/ # Vue UI 层 │ ├── components/ # 所有界面组件 │ ├── composables/ # 引擎 ↔ UI 桥接 -│ ├── stores/ # Pinia 状态管理 -│ └── locales/ # 翻译文件(zh.json / en.json) +│ ├── stores/ # Pinia 状态 +│ └── locales/ # UI 翻译(开发者维护) ├── editor/ # 可视化剧情编辑器 -├── electron/ # 桌面应用打包(Electron) -├── public/ # 你的素材 -│ ├── videos/ # 视频文件(.mp4) -│ ├── audio/ # 背景音乐(.mp3) -│ ├── images/ # 缩略图 -│ ├── subtitles/ # 字幕(.vtt) -│ └── scenes/demo.json # 剧情定义 +├── electron/ # Electron 桌面打包 +├── public/ # 你的素材(唯一需要编辑的目录) +│ ├── demo/ # 示例数据(参考用) +│ │ ├── scenes/demo.json +│ │ └── locales/{zh,en,ja}.json +│ ├── your_story/ # 你的游戏 +│ │ ├── scene_1/video.mp4 +│ │ └── locales/zh.json +│ └── scenes/config.json # 默认加载哪个 JSON ├── docs/ # 文档 -│ └── SCENE_JSON_SPEC.md # JSON 完整字段参考 +│ ├── guide/ # 游戏制作引导 +│ └── electron/ # 打包配置 ├── scripts/ # 打包脚本 -└── ROADMAP.md # 开发路线图 +└── ROADMAP.md ``` -## 视频制作建议 - -| 参数 | 建议值 | -|------|--------| -| 视频不包含 BGM | 背景音乐用独立 .mp3 文件 + `bgmUrl` 字段,画面循环时 BGM 不中断 | -| 循环片段 | 正文段 + 循环段合成为一个文件,用 `loopStart`/`loopEnd` 标记 | -| 字幕 | WebVTT 格式,时间轴精确到毫秒 | - -## 命令参考 +## 命令 ```bash -npm run dev # 启动开发服务器(实时预览) +npm run dev # 启动开发服务器 npm run build # 构建生产版本 npm run preview # 预览构建结果 npm run pack:html # 打包 Web 版 diff --git a/docs/electron-mirror-setup.md b/docs/electron/mirror-setup.md similarity index 100% rename from docs/electron-mirror-setup.md rename to docs/electron/mirror-setup.md diff --git a/docs/electron-packaging-guide.md b/docs/electron/packaging-guide.md similarity index 100% rename from docs/electron-packaging-guide.md rename to docs/electron/packaging-guide.md diff --git a/docs/electron-port-finder.md b/docs/electron/port-finder.md similarity index 100% rename from docs/electron-port-finder.md rename to docs/electron/port-finder.md diff --git a/docs/electron-window-controls.md b/docs/electron/window-controls.md similarity index 100% rename from docs/electron-window-controls.md rename to docs/electron/window-controls.md diff --git a/docs/guide/BRANCHING.md b/docs/guide/BRANCHING.md new file mode 100644 index 0000000..39300cc --- /dev/null +++ b/docs/guide/BRANCHING.md @@ -0,0 +1,138 @@ +# 分支叙事指南 + +## 基本分支 + +最简单的分支:一个场景 → 多个选项 → 不同目标场景。 + +```json +"scene_1": { + "id": "scene_1", + "videoUrl": "scene_1/video.mp4", + "choices": [ + { "text": "帮助他", "targetScene": "help" }, + { "text": "离开", "targetScene": "leave" } + ] +} +``` + +## 条件分支 + +选项可以根据变量条件显示/隐藏: + +```json +"choices": [ + { + "text": "高信任路线", + "targetScene": "trust_path", + "conditions": [ + { "variable": "trust", "op": ">=", "value": 80 } + ] + }, + { "text": "普通路线", "targetScene": "normal_path" } +] +``` + +`op` 支持:`>`, `<`, `>=`, `<=`, `==`, `!=`。 + +## 变量与效果 + +全局变量在 JSON 顶层定义初始值: + +```json +{ + "variables": { "trust": 50, "courage": 0, "investigation": 0 } +} +``` + +选项选择后应用效果: + +```json +{ + "text": "与陌生人握手", + "targetScene": "trust_ending", + "effects": [ + { "type": "add", "target": "trust", "value": 30 } + ] +} +``` + +效果类型: +- `"set"` — 设置变量为指定值 +- `"add"` — 增加(负数=减少) + +## 场景进入效果 + +进入场景时自动触发: + +```json +"ending": { + "id": "ending", + "videoUrl": "ending/video.mp4", + "onEnter": [ + { "type": "set", "target": "completed_game", "value": 1 } + ] +} +``` + +## 限时选择 + +```json +{ + "text": "快速决定!", + "targetScene": "timeout_scene", + "timeLimit": 10 +} +``` + +10 秒内不选,自动选这个选项。`timeLimit: 0` 或省略 = 不限时。 + +## 默认跳转 + +无选项或有选项但都不满足条件时,自动跳转: + +```json +"scene": { + "id": "scene", + "videoUrl": "scene/video.mp4", + "choices": [], // 无选项时自动跳 + "nextScene": "auto_next" +} +``` + +优先级:choices > nextScene > 什么都没配(游戏结束)。 + +## 关键选择提示(Prompt) + +重要选项可以配置前置金色标识 + 选后浮现提示: + +```json +{ + "text": "与陌生人握手", + "textKey": "left_door.choice.handshake", + "prompt": "陌生人会记住你的善意", + "promptKey": "left_door.prompt.handshake", + "targetScene": "trust_ending" +} +``` + +- 前置:选项按钮左侧显示金色竖线 + 淡金边框 +- 后置:选择确认后,画面中央浮现 prompt 文字,2 秒淡出 + +`promptKey` 支持 i18n,配置方法见 [国际化指南](I18N.md)。 + +## 多国语言选项 + +选项文案支持中/英/日等多语言,使用 `textKey` 机制: + +```json +{ + "text": "继续前进", + "textKey": "qte_success.choice.continue", + "targetScene": "continue_ending" +} +``` + +- `text` 是回退值(翻译找不到时使用) +- `textKey` 指向 `public/locales/zh.json` 中的翻译 + +详细配置见 [国际化指南](I18N.md)。 diff --git a/docs/guide/I18N.md b/docs/guide/I18N.md new file mode 100644 index 0000000..78094f1 --- /dev/null +++ b/docs/guide/I18N.md @@ -0,0 +1,159 @@ +# 国际化指南(i18n) + +## 双源体系 + +| 类别 | 位置 | 加载 | 维护者 | +|------|------|------|--------| +| **UI 文案**(按钮、菜单、设置) | `src/locales/{zh,en,ja}.json` | `import` 打包进 bundle | 引擎开发者 | +| **故事文案**(选项、提示、章节名) | `public/demo/locales/{zh,en,ja}.json` | `fetch()` 动态加载 | 故事创作者 | + +**核心理念:** 游戏制作者只需编辑 `public/` 下的 JSON,刷新页面即生效,不需要重新打包。 + +## 数据层 i18n(故事文案) + +### 架构 + +```json +// public/scenes/demo.json +{ + "locales": { "path": "locales/", "languages": ["zh", "en", "ja"] }, + "scenes": { + "intro": { + "choices": [ + { "text": "走向左边那扇发光的门", "textKey": "intro.choice.left_door", ... } + ] + } + } +} +``` + +```json +// public/demo/locales/zh.json +{ + "intro": { + "choice": { + "left_door": "走向左边那扇发光的门" + } + } +} +``` + +```json +// public/demo/locales/en.json +{ + "intro": { + "choice": { + "left_door": "Walk toward the glowing door on the left" + } + } +} +``` + +### 所有支持 i18n 的数据字段 + +| 数据对象 | 字段 | key 字段 | 示例 key | +|---------|------|---------|----------| +| Choice | `text` | `textKey` | `"intro.choice.left_door"` | +| Choice | `prompt` | `promptKey` | `"left_door.prompt.handshake"` | +| QTE | `prompt` | `promptKey` | `"right_door.qte.dodge"` | +| Hotspot | `label` | `labelKey` | `"investigation_site.hotspot.desk"` | +| Chapter | `label` | `labelKey` | `"chapter.ch1"` | +| Ending | `label` | `labelKey` | `"ending.trust_end"` | +| Achievement | `title` | `titleKey` | `"achievement.qte_master.title"` | +| Achievement | `description` | `descKey` | `"achievement.qte_master.desc"` | + +**规律:** 每个可国际化的数据字段都有对应的 `xxxKey` 版本。引擎先查 key,找不到时回退原始字段值。 + +### 回退逻辑 + +``` +t('intro.choice.left_door') + → 查 public/locales/{lang}.json + → 找到了 → 返回翻译文本 + → 找不到 → 返回 choice.text("走向左边那扇发光的门") +``` + +### Locale JSON 结构示例 + +```json +{ + "intro": { + "choice": { + "left_door": "...", + "right_door": "...", + "search": "...", + "stay": "..." + } + }, + "left_door": { + "choice": { + "handshake": "...", + "reject": "..." + }, + "prompt": { + "handshake": "..." + } + }, + "right_door": { + "qte": { "dodge": "..." } + }, + "chapter": { + "ch1": "第一章:醒来", + "ch2": "第二章:调查" + }, + "ending": { + "trust_end": "信任的伙伴", + "alone_end": "独行之路" + }, + "achievement": { + "qte_master": { + "title": "反应达人", + "desc": "成功完成一次 QTE" + } + } +} +``` + +## 新增语言的方法 + +### 1. 注册到引擎 + +`src/locales/` 新增语言文件(复制 `en.json` 翻译): + +```bash +cp src/locales/en.json src/locales/ko.json +# 编辑 src/locales/ko.json → 翻译 UI 文案 +# 编辑 src/composables/useI18n.ts → 新增 `import ko from '@/locales/ko.json'` + 加入 `uiMessages` +``` + +### 2. 注册到故事数据 + +`public/demo/locales/` 新增语言文件: + +```bash +cp public/demo/locales/en.json public/demo/locales/ko.json +# 编辑 → 翻译故事文案 +``` + +`demo.json` 的 `locales.languages` 数组加 `"ko"`: + +```json +"locales": { "path": "locales/", "languages": ["zh", "en", "ja", "ko"] } +``` + +### 3. 刷新页面 + +语言切换按钮自动新增韩语选项,无需重新构建。 + +## 配置语言目录 + +所有 locale 文件放在 `assetBase` 指定的基础路径下。JSON 中配置: + +```json +{ + "assetBase": "my_story/", + "locales": { "path": "lang/", "languages": ["zh", "en"] } +} +``` + +则语言文件路径为 `my_story/lang/zh.json`。 diff --git a/docs/guide/INTERACTIONS.md b/docs/guide/INTERACTIONS.md new file mode 100644 index 0000000..cdcf1a2 --- /dev/null +++ b/docs/guide/INTERACTIONS.md @@ -0,0 +1,123 @@ +# 交互指南 — QTE / Hotspot / Loop + +## QTE 快速反应事件 + +在视频播放到特定时间点时弹出按键提示,玩家在倒计时内按下指定按键。 + +```json +"qte": { + "triggerTime": 1.0, + "prompt": "躲避飞来的石块!", + "promptKey": "right_door.qte.dodge", + "keys": ["ArrowLeft", "ArrowRight", "a", "d"], + "timeLimit": 3.0, + "successScene": "qte_success", + "failScene": "qte_fail", + "effects": { + "success": [{ "type": "add", "target": "courage", "value": 15 }], + "fail": [{ "type": "add", "target": "trust", "value": -20 }] + } +} +``` + +| 字段 | 说明 | +|------|------| +| `triggerTime` | 触发时间(秒) | +| `prompt` / `promptKey` | 提示文字 / i18n key | +| `keys` | 有效按键(键盘 key 名,不区分大小写) | +| `timeLimit` | 倒计时(秒) | +| `successScene` / `failScene` | 成功/失败目标场景 | +| `effects` | 成功/失败分别触发效果 | + +**注意:** QTE 是模态交互。视频播放到 QTE 触发时暂停场景流程,QTE 期间视频结束事件被忽略。 + +**可访问性:** 玩家可在设置中开启"QTE 按键简化"(所有 QTE 统一为空格键)和"QTE 时限放宽 ×1.5"。 + +**禁止跳过:** QTE 场景建议设 `"skippable": false`,防止玩家跳过 QTE。 + +## 图片/视频热点(Hotspot) + +在画面中划定可点击区域,玩家点击后触发分支。 + +### 图片热点 + +```json +"investigation_site": { + "type": "image", + "imageUrl": "investigation_site/scene.jpg", + "contentSize": { "w": 1280, "h": 720 }, + "hotspots": [ + { + "id": "hs_desk", + "label": "查看书桌", + "labelKey": "investigation_site.hotspot.desk", + "targetScene": "desk_detail", + "x": 154, "y": 144, "width": 230, "height": 101 + } + ] +} +``` + +### 视频热点(按时间显隐) + +```json +"corridor": { + "videoUrl": "corridor/video.mp4", + "contentSize": { "w": 1280, "h": 720 }, + "hotspots": [ + { + "id": "hs_left", + "label": "走向左边通道", + "targetScene": "left_door", + "x": 26, "y": 216, "width": 384, "height": 324, + "showAt": 1.5 + }, + { + "id": "hs_right", + "label": "走向右边通道", + "targetScene": "alone_ending", + "x": 870, "y": 216, "width": 384, "height": 324, + "showAt": 5.0 + } + ] +} +``` + +| 字段 | 说明 | +|------|------| +| `showAt` | 视频播放到此秒数后才显示热点(可选) | +| `hideAt` | 视频播放到此秒数后隐藏热点(可选) | +| `conditions` | 条件满足时才显示(可选) | + +### 坐标系统 + +Hotspot 坐标使用**绝对像素**,基于 `contentSize` 指定的基准分辨率: + +- `x, y` — 左上角像素坐标 +- `width, height` — 热点区域像素尺寸 + +引擎自动处理 `object-fit: contain` 的黑边偏移和屏幕缩放。 + +**制作建议:** 在 Photoshop 中测量坐标,直接写入 JSON。 + +## 循环等待(Loop) + +视频播放到指定区间后自动循环,适合"等待玩家做决定"的桥段。 + +```json +"stay": { + "videoUrl": "stay/loop.mp4", + "loopStart": 3.0, + "loopEnd": 6.0, + "choices": [ + { "text": "站起来离开", "targetScene": "alone_ending" } + ] +} +``` + +- 视频 0-3s 正常播放(正文段) +- 到 3s 时循环开始,选项面板同时弹出 +- 视频在 3.0-6.0s 之间反复播放,直到玩家做出选择 +- BGM 不受循环影响(独立音频轨道) + +**视频制作技巧:** 正文段和循环段合成为一个文件。循环段首尾画面应自然衔接。 diff --git a/docs/guide/PUBLISHING.md b/docs/guide/PUBLISHING.md new file mode 100644 index 0000000..dd9a5ac --- /dev/null +++ b/docs/guide/PUBLISHING.md @@ -0,0 +1,116 @@ +# 打包发布指南 + +## 准备工作 + +```bash +npm install +npm run build # 先构建,自动校验 JSON 合法性 +``` + +构建产物在 `dist/`,是纯静态文件,可直接部署到任意 HTTP 服务器。 + +## Web 版发布 + +### 一键打包 + +```bash +npm run pack:html +``` + +生成 `release/mygame.zip`,上传到任意平台: + +| 平台 | 上传方式 | +|------|---------| +| **itch.io** | 直接上传 zip | +| **Netlify** | 拖拽 `dist/` 文件夹到 Drop | +| **GitHub Pages** | 推送 `dist/` 到 `gh-pages` 分支 | +| **自有服务器** | 上传 `dist/` 到任意静态文件服务(Nginx, Apache, Caddy) | + +### 域名/CDN + +如果把素材放到 CDN,只需改一行: + +```json +{ + "assetBase": "https://cdn.example.com/mygame/" +} +``` + +所有 `videoUrl: "scene_1/video.mp4"` 自动拼为 `https://cdn.example.com/mygame/scene_1/video.mp4`。 + +## 桌面版发布 + +### macOS + +```bash +npm run pack:mac +``` + +生成 `release/MyGame-darwin-arm64/`。将整个文件夹打包为 `.dmg` 或直接分发文件夹。用户双击 `MyGame.app` 即可运行。 + +### Windows + +```bash +npm run pack:win +``` + +生成 `release/MyGame-win32-x64/`。运行 `MyGame.exe`。 + +## 桌面版命令行参数 + +打包后的应用支持 `--scene` 参数指定剧情文件: + +```bash +# macOS +./MyGame.app/Contents/MacOS/MyGame --scene=./scenes/my_story.json + +# Windows +MyGame.exe --scene=./scenes/my_story.json +``` + +## 素材管理 + +### 本地开发 + +所有素材放在 `public/` 下,Vite 自动 serve。 + +### 生产发布 + +- 视频/音频通常较大(几百 MB),建议单独分发或放 CDN +- `.gitignore` 中已排除 `public/videos/`,视频不提交到 Git +- 打包脚本自动复制 `public/` 到 `dist/`,无需手动处理 + +### 目录规范 + +``` +public/demo/ ← 示例数据 + scenes/demo.json + locales/{zh,en,ja}.json + intro/video.mp4 + shared/bgm.mp3 + +public/your_story/ ← 你的游戏(复制此结构) + scenes/main.json + locales/{zh,en}.json + scene_1/video.mp4 +``` + +## 常见问题 + +### Q: 构建时提示 "JSON 不合法" + +检查 JSON 文件是否有多余逗号(最后一项不能有逗号)。 + +### Q: 打包后视频不加载 + +检查 `assetBase` 配置,确保路径拼接正确。开发模式 `assetBase: ""`,发布到 CDN 时改为完整 URL。 + +### Q: 打包体积太大 + +- 视频是最大的文件,建议单独制作低码率预览版(2Mbps)用于小体积分发 +- 音频用 MP3 128kbps 即可 +- 缩略图 JPG 质量 60% 足够 + +### Q: 如何制作安装包(.dmg / .exe 安装程序) + +参考 `docs/electron/packaging-guide.md`。 diff --git a/docs/guide/QUICK_START.md b/docs/guide/QUICK_START.md new file mode 100644 index 0000000..5f641d2 --- /dev/null +++ b/docs/guide/QUICK_START.md @@ -0,0 +1,87 @@ +# 快速开始 — 5 分钟制作你的第一个交互式电影游戏 + +## 1. 准备视频 + +用任何方式制作三个短视频(MP4 H.264): + +``` +my_story/ + scene_1/video.mp4 ← 开场视频 + scene_2/video.mp4 ← 分支 A 视频 + ending/video.mp4 ← 结局视频 +``` + +推荐参数:1280×720,30fps,2-5Mbps。 + +## 2. 写剧情 JSON + +创建 `public/scenes/my_story.json`: + +```json +{ + "assetBase": "my_story/", + "startScene": "scene_1", + "variables": {}, + "scenes": { + "scene_1": { + "id": "scene_1", + "videoUrl": "scene_1/video.mp4", + "choices": [ + { "text": "选择 A", "targetScene": "scene_2" }, + { "text": "选择 B", "targetScene": "ending" } + ] + }, + "scene_2": { + "id": "scene_2", + "videoUrl": "scene_2/video.mp4", + "nextScene": "ending" + }, + "ending": { + "id": "ending", + "videoUrl": "ending/video.mp4", + "choices": [] + } + } +} +``` + +## 3. 启动 + +```bash +npm install +npm run dev +``` + +打开 `http://localhost:5173/?scene=my_story.json` + +## 4. 发生了什么 + +- 场景 1 播放 → 视频结束后弹出两个选项 +- 选 A → 场景 2 → 自动跳转到 ending +- 选 B → 直接到 ending +- ending 无选项 → 游戏结束,返回主菜单 + +## 下一步 + +- 想加限定条件的选择?→ [分支叙事指南](BRANCHING.md) +- 想加 QTE 或热点?→ [交互指南](INTERACTIONS.md) +- 想加背景音乐或字幕?→ [场景 JSON 参考](SCENE_JSON_SPEC.md) +- 想支持多语言?→ [国际化指南](I18N.md) +- 想打包发布?→ [发布指南](PUBLISHING.md) + +## 素材组织建议 + +``` +my_story/ + scene_1/video.mp4 ← 每个场景一个文件夹 + scene_2/video.mp4 + ending/video.mp4 + shared/ ← 跨场景共享素材 + bgm.mp3 + thumb.jpg + locales/ ← 多语言翻译文件(可选) + zh.json + en.json +``` + +JSON 中所有路径都是相对于素材根目录的相对路径,配合 `assetBase` 前缀使用。 diff --git a/docs/guide/SCENE_JSON_SPEC.md b/docs/guide/SCENE_JSON_SPEC.md new file mode 100644 index 0000000..e7f298f --- /dev/null +++ b/docs/guide/SCENE_JSON_SPEC.md @@ -0,0 +1,255 @@ +# Scene JSON 完整字段参考 + +## 顶层结构 + +```json +{ + "assetBase": "", + "locales": { "path": "locales/", "languages": ["zh", "en"] }, + "startScene": "intro", + "variables": { "trust": 50, "courage": 0 }, + "introVideo": "__intro__/logo.mp4", + "menuVideo": "__intro__/menu_bg.mp4", + "scenes": { ... }, + "chapters": [ ... ], + "achievements": [ ... ], + "endings": [ ... ] +} +``` + +| 字段 | 类型 | 必需 | 说明 | +|------|------|------|------| +| `assetBase` | string | 否 | 所有资源路径前缀,默认 `""`。设 `"demo/"` 后 `videoUrl: "intro/video.mp4"` 自动拼成 `demo/intro/video.mp4`。改 CDN 只需改这一行 | +| `locales` | object | 否 | 多语言配置。`path` 为 locale 文件目录(相对于 `assetBase`),`languages` 为支持的语言列表 | +| `startScene` | string | 是 | 开始场景的 ID | +| `variables` | object | 否 | 全局变量初始值 | +| `introVideo` | string | 否 | 开场视频路径 | +| `menuVideo` | string | 否 | 主菜单背景视频路径(自动循环播放) | +| `scenes` | object | 是 | 所有场景的集合,key 为场景 ID | +| `chapters` | array | 否 | 章节列表 | +| `achievements` | array | 否 | 成就列表 | +| `endings` | array | 否 | 结局列表 | + +--- + +## SceneNode + +```typescript +interface SceneNode { + id: string + type?: 'video' | 'image' + videoUrl: string + imageUrl?: string + contentSize?: { w: number; h: number } + subtitleUrl?: string + subtitles?: Record + choices?: Choice[] + hotspots?: Hotspot[] + qte?: QTEDefinition + nextScene?: string + onEnter?: Effect[] + loopStart?: number + loopEnd?: number + bgmUrl?: string + bgmVolume?: number + bgmCrossFade?: number + bgmDuckLevel?: number + bgmDuckFade?: number + videoMuted?: boolean + skippable?: boolean +} +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | string | 场景唯一标识 | +| `type` | string | `"video"` (默认) 或 `"image"`。image 类型展示图片+热点,不播放视频 | +| `videoUrl` | string | 视频文件路径。image 场景可为空 | +| `imageUrl` | string | 图片场景的图片路径 | +| `contentSize` | object | 内容基准分辨率 `{ w: 1280, h: 720 }`,Hotspot 坐标基于此计算。用于 object-fit: contain 的偏移补偿 | +| `subtitleUrl` | string | 回退字幕路径。优先使用 `subtitles` | +| `subtitles` | object | 多语言字幕 `{ "zh": "...", "en": "..." }`。语言切换时自动选择 | +| `choices` | Choice[] | 选项列表 | +| `hotspots` | Hotspot[] | 可点击热点区域 | +| `qte` | QTEDefinition | QTE 定义 | +| `nextScene` | string | 无选项时的默认跳转场景 | +| `onEnter` | Effect[] | 进入场景时触发的效果 | +| `loopStart` | number | 循环起始时间(秒) | +| `loopEnd` | number | 循环结束时间(秒)。视频播放到 loopEnd 时跳回 loopStart | +| `bgmUrl` | string | 背景音乐路径 | +| `bgmVolume` | number | 背景音乐音量(0-1),默认 0.8 | +| `bgmCrossFade` | number | 背景音乐交叉淡化时长(秒),默认 2.0 | +| `bgmDuckLevel` | number | BGM Duck 压低比例(0-1),默认 0.35 | +| `bgmDuckFade` | number | BGM Duck 过渡时长(秒),默认 0.5 | +| `videoMuted` | boolean | 视频静音(配合独立 BGM 使用) | +| `skippable` | boolean | `false` = 禁止跳过(用于 QTE 等关键场景) | + +--- + +## Choice + +```typescript +interface Choice { + text: string + textKey?: string + prompt?: string + promptKey?: string + targetScene: string + conditions?: Condition[] + effects?: Effect[] + timeLimit?: number +} +``` + +| 字段 | 说明 | +|------|------| +| `text` | 选项文本(回退值) | +| `textKey` | i18n key,优先于 `text`。如 `"intro.choice.left_door"` | +| `prompt` | 选择后浮现的提示文字(回退值) | +| `promptKey` | prompt 的 i18n key。如 `"left_door.prompt.handshake"` | +| `targetScene` | 目标场景 ID | +| `conditions` | 显示条件,不满足的选项隐藏 | +| `effects` | 选择后触发的效果 | +| `timeLimit` | 限时秒数,0=不限时 | + +--- + +## Hotspot + +```typescript +interface Hotspot { + id: string + label: string + labelKey?: string + targetScene: string + x: number + y: number + width: number + height: number + showAt?: number + hideAt?: number + conditions?: Condition[] + effects?: Effect[] +} +``` + +| 字段 | 说明 | +|------|------| +| `id` | 热点唯一标识 | +| `label` | 显示标签(回退值) | +| `labelKey` | i18n key | +| `x, y` | 左上角坐标(像素,基于 contentSize) | +| `width, height` | 尺寸(像素) | +| `showAt` | 视频时间(秒),此时间后显示。不设则始终可见 | +| `hideAt` | 视频时间(秒),此时间后隐藏 | +| `targetScene` | 点击后跳转的场景 | +| `conditions` | 显示条件 | +| `effects` | 点击后触发的效果 | + +--- + +## QTEDefinition + +```typescript +interface QTEDefinition { + triggerTime: number + prompt: string + promptKey?: string + keys: string[] + timeLimit: number + successScene: string + failScene: string + effects?: { + success: Effect[] + fail: Effect[] + } +} +``` + +| 字段 | 说明 | +|------|------| +| `triggerTime` | 触发时间(秒),视频播放到此时间弹出 QTE | +| `prompt` | 提示文字(回退值) | +| `promptKey` | i18n key。如 `"right_door.qte.dodge"` | +| `keys` | 有效按键列表,如 `["ArrowLeft", "ArrowRight", "a", "d"]` | +| `timeLimit` | 限时秒数 | +| `successScene` | 成功跳转场景 | +| `failScene` | 失败/超时跳转场景 | +| `effects` | 成功/失败分别触发效果 | + +--- + +## 其他类型 + +### Condition + +```typescript +interface Condition { + variable: string + op: '>' | '<' | '>=' | '<=' | '==' | '!=' + value: number | string | boolean +} +``` + +### Effect + +```typescript +interface Effect { + type: 'set' | 'add' + target: string + value?: number | string | boolean +} +``` + +- `set` — 设置变量值 +- `add` — 变量增加/减少 + +### ChapterInfo + +```typescript +interface ChapterInfo { + id: string + label: string + labelKey?: string + startScene: string + thumbnail?: string + defaultVariables?: Record +} +``` + +### AchievementDef + +```typescript +interface AchievementDef { + id: string + title: string + titleKey?: string + description: string + descKey?: string + icon?: string + hidden?: boolean + condition: Condition +} +``` + +### EndingDef + +```typescript +interface EndingDef { + id: string + label: string + labelKey?: string + sceneId: string + chapterId?: string + thumbnail?: string +} +``` + +### LocalesConfig + +```typescript +interface LocalesConfig { + path: string + languages: string[] +} +```