场景 JSON 规范文档
电影游戏引擎的场景数据全部定义在 JSON 文件中。本文档是完整字段参考手册。
目录
- 顶层结构 (GameData)
- 场景节点 (SceneNode)
- 选项 (Choice)
- 图片/视频热点 (Hotspot)
- 快速反应事件 (QTEDefinition)
- 条件和效果 (Condition & Effect)
- 背景音乐 (BGM 字段)
- 章节 (ChapterInfo)
- 成就 (AchievementDef)
- 结局 (EndingDef)
- 完整示例
- 验证规则
1. 顶层结构 (GameData)
| 字段 |
类型 |
必填 |
说明 |
startScene |
string |
✅ |
游戏开始的场景 ID |
variables |
Record<string, number> |
✅ |
全局变量初始值(如好感度、勇气值等,制作者自定义) |
scenes |
Record<string, SceneNode> |
✅ |
所有场景节点,key 为场景唯一 ID |
chapters |
ChapterInfo[] |
否 |
章节定义,用于章节选择功能 |
achievements |
AchievementDef[] |
否 |
成就定义,满足条件时自动解锁 |
endings |
EndingDef[] |
否 |
结局定义,用于结局画廊 |
2. 场景节点 (SceneNode)
| 字段 |
类型 |
默认 |
说明 |
id |
string |
— |
唯一标识。任意字符串,建议用有意义的英文名 |
type |
"video" | "image" |
"video" |
"image" 时显示静态图片和热点,不播放视频 |
videoUrl |
string |
"" |
视频文件路径,推荐 /videos/xxx.mp4 |
imageUrl |
string |
— |
图片场景的图片路径 |
subtitleUrl |
string |
— |
字幕 VTT 文件路径(单语言,向后兼容) |
subtitles |
Record<string, string> |
— |
多语言字幕映射,如 {"zh": "...", "en": "..."}。优先级高于 subtitleUrl |
choices |
Choice[] |
— |
选项列表。为空或不存在时场景播放完毕后自动结束(或走 nextScene) |
hotspots |
Hotspot[] |
— |
可点击热区。视频热点按 showAt/hideAt 时间轴显隐 |
qte |
QTEDefinition |
— |
QTE 事件定义 |
nextScene |
string |
— |
无选项时的默认下一场景 ID(自动跳转) |
onEnter |
Effect[] |
— |
进入场景时执行的效果(如设置变量) |
loopStart |
number |
— |
循环起点(秒)。到达后弹出选项并开始循环。需配合 loopEnd |
loopEnd |
number |
— |
循环终点(秒)。播到时 seek 回 loopStart |
bgmUrl |
string |
— |
背景音乐 MP3 路径。null/省略时静默 |
bgmVolume |
number |
0.8 |
BGM 音量(0~1) |
bgmCrossFade |
number |
2.0 |
BGM 切换时的交叉淡化时长(秒) |
bgmDuckLevel |
number |
0.35 |
QTE/选项出现时 BGM 降低到的百分比 |
bgmDuckFade |
number |
0.5 |
Ducking 的渐变时长(秒) |
videoMuted |
boolean |
false |
是否静音视频自带音轨 |
skippable |
boolean |
true |
false 时禁止跳过此场景(即使已看过) |
3. 选项 (Choice)
| 字段 |
类型 |
默认 |
说明 |
text |
string |
— |
选项显示文字(默认语言) |
textKey |
string |
— |
多语言 key。有值时用翻译文件查文字,否则用 text |
prompt |
string |
— |
选后浮现提示文字(Telltale 式 "某人会记住这件事")。有值时按钮前置金色标识 |
targetScene |
string |
— |
点击后跳转的目标场景 ID |
conditions |
Condition[] |
— |
显示条件。所有条件都满足时选项才显示 |
effects |
Effect[] |
— |
选择后的效果(修改变量值等) |
timeLimit |
number |
— |
限时选择(秒)。0 或不设表示不限时 |
4. 图片/视频热点 (Hotspot)
| 字段 |
类型 |
默认 |
说明 |
id |
string |
— |
热区唯一标识 |
label |
string |
— |
鼠标悬停时显示的提示文字 |
targetScene |
string |
— |
点击后跳转的目标场景 ID |
x |
number |
— |
热区左上角 X 坐标,相对比例(0~1),自适应屏幕 |
y |
number |
— |
热区左上角 Y 坐标,相对比例(0~1) |
width |
number |
— |
热区宽度,相对比例(0~1) |
height |
number |
— |
热区高度,相对比例(0~1) |
showAt |
number |
— |
视频模式下的出现时间(秒)。未设时始终显示 |
hideAt |
number |
— |
视频模式下的消失时间(秒)。未设时始终显示 |
conditions |
Condition[] |
— |
显示条件 |
effects |
Effect[] |
— |
点击后的效果 |
timeLimit |
number |
— |
限时热区(秒)。超时后热区消失,不触发跳转 |
5. 快速反应事件 (QTEDefinition)
| 字段 |
类型 |
默认 |
说明 |
triggerTime |
number |
— |
QTE 触发时间(视频播到第几秒时触发) |
prompt |
string |
— |
QTE 提示文字 |
keys |
string[] |
— |
需按下的键,大小写不敏感。如 ["Space"] |
timeLimit |
number |
— |
限时(秒) |
successScene |
string |
— |
成功时跳转的场景 ID |
failScene |
string |
— |
失败/超时时跳转的场景 ID |
effects |
object |
— |
成功/失败各自的效果(effects.success 和 effects.fail) |
6. 条件和效果 (Condition & Effect)
Condition(条件)
| 字段 |
类型 |
说明 |
variable |
string |
变量名 |
op |
string |
比较操作符:> / < / >= / <= / == / != |
value |
number |
比较值 |
注意: 条件中变量的值都是 number 类型。布尔值用 == 1 表示 true,== 0 表示 false。
Effect(效果)
type |
说明 |
set |
设置变量为目标值 |
add |
变量增加指定值(负数为减) |
| 字段 |
类型 |
说明 |
target |
string |
变量名(与 Condition 的 variable 对应) |
value |
number |
值 |
7. 背景音乐 (BGM 字段)
BGM 字段定义在 SceneNode 中,由独立 AudioSystem 驱动,不受视频循环/切换影响。
| 字段 |
类型 |
默认 |
说明 |
bgmUrl |
string |
— |
BGM MP3 路径。两个相邻场景的 bgmUrl 相同时 → BGM 不中断继续播放。 不同时 → 交叉淡化切换。null/省略时 → fade out |
bgmVolume |
number |
0.8 |
BGM 音量(0~1) |
bgmCrossFade |
number |
2.0 |
交叉淡化时长(秒) |
bgmDuckLevel |
number |
0.35 |
QTE 触发 / 选项出现 / 热点出现时 BGM 自动降到 bgmVolume × bgmDuckLevel |
bgmDuckFade |
number |
0.5 |
Ducking 渐变时长(秒) |
videoMuted |
boolean |
false |
是否静音视频自带音轨。引擎不自动设置,需制作者手动指定 |
制作建议: BGM 使用独立的 .mp3 文件,视频导出时不要嵌入背景音乐。这样 BGM 在场景切换和画面循环时保持连贯。
8. 章节 (ChapterInfo)
| 字段 |
类型 |
必填 |
说明 |
id |
string |
✅ |
章节唯一标识 |
label |
string |
✅ |
章节显示名称(支持多语言) |
startScene |
string |
✅ |
章节起始场景 ID。玩家到达此场景时 → 章节自动解锁 |
thumbnail |
string |
否 |
章节缩略图路径(320×180 推荐) |
defaultVariables |
Record<string, number> |
否 |
从章节入口跳转时套用的变量初始值。未设时 fallback 到全局 variables |
9. 成就 (AchievementDef)
| 字段 |
类型 |
必填 |
说明 |
id |
string |
✅ |
成就唯一标识 |
title |
string |
✅ |
成就标题 |
description |
string |
✅ |
成就描述 |
icon |
string |
否 |
成就图标路径(可选) |
hidden |
boolean |
否 |
true 时未解锁不显示标题和描述(显示 ??? ) |
condition |
Condition |
✅ |
解锁条件。变量满足时自动解锁并弹出 toast |
10. 结局 (EndingDef)
| 字段 |
类型 |
必填 |
说明 |
id |
string |
✅ |
结局唯一标识 |
label |
string |
✅ |
结局显示名称 |
sceneId |
string |
✅ |
结局场景 ID。玩家到达此场景时 → 结局自动标记已解锁 |
thumbnail |
string |
否 |
结局缩略图路径(320×180 推荐) |
11. 完整示例
以下示例覆盖所有功能:
{
"startScene": "intro",
"variables": { "trust": 50, "courage": 0, "investigation": 0 },
"scenes": {
"intro": {
"id": "intro",
"videoUrl": "/videos/intro.mp4",
"subtitles": { "zh": "/subtitles/intro_zh.vtt", "en": "/subtitles/intro_en.vtt" },
"bgmUrl": "/audio/calm.mp3",
"bgmVolume": 0.6,
"loopStart": 5.0,
"loopEnd": 8.0,
"choices": [
{
"text": "帮助陌生人",
"textKey": "scene.intro.choice.help",
"prompt": "你的善意将被记住",
"targetScene": "help_ending",
"effects": [{ "type": "add", "target": "trust", "value": 20 }]
},
{
"text": "调查房间",
"textKey": "scene.intro.choice.investigate",
"targetScene": "crime_scene"
}
]
},
"crime_scene": {
"id": "crime_scene",
"type": "image",
"imageUrl": "/images/crime_scene.jpg",
"hotspots": [
{
"id": "hs_desk",
"label": "查看书桌",
"targetScene": "desk_detail",
"x": 0.15, "y": 0.30, "width": 0.25, "height": 0.35,
"effects": [{ "type": "add", "target": "investigation", "value": 1 }]
}
],
"choices": [
{ "text": "离开房间", "targetScene": "corridor" }
]
},
"corridor": {
"id": "corridor",
"videoUrl": "/videos/corridor.mp4",
"bgmUrl": "/audio/tense.mp3",
"bgmVolume": 0.7,
"bgmCrossFade": 1.5,
"qte": {
"triggerTime": 2.0,
"prompt": "躲避飞来的石块!",
"keys": ["Space"],
"timeLimit": 3.0,
"successScene": "qte_win",
"failScene": "qte_lose",
"effects": {
"success": [{ "type": "add", "target": "courage", "value": 10 }],
"fail": [{ "type": "add", "target": "trust", "value": -10 }]
}
}
},
"help_ending": {
"id": "help_ending",
"videoUrl": "/videos/help.mp4",
"bgmUrl": "/audio/calm.mp3",
"bgmVolume": 0.6,
"onEnter": [{ "type": "set", "target": "completed_game", "value": 1 }],
"choices": []
}
},
"chapters": [
{
"id": "ch1", "label": "第一章", "startScene": "intro",
"thumbnail": "/images/ch1.jpg",
"defaultVariables": { "trust": 50, "courage": 0, "investigation": 0 }
}
],
"achievements": [
{
"id": "helper", "title": "善良的人", "description": "选择帮助陌生人",
"hidden": false,
"condition": { "variable": "trust", "op": ">=", "value": 70 }
}
],
"endings": [
{ "id": "help_end", "label": "伸出援手", "sceneId": "help_ending", "thumbnail": "/images/end_help.jpg" }
]
}
12. 验证规则
| 规则 |
说明 |
| ID 唯一性 |
scenes 的 key、Choice.targetScene、Hotspot.targetScene、QTEDefinition.successScene/failScene、ChapterInfo.startScene、EndingDef.sceneId 必须引用存在的场景 ID |
| 循环引用 |
场景 A 的 targetScene 指向 A 自身 → 允许(重玩同一场景) |
| 死路检测 |
有 conditions 的 choice 如果条件永远无法满足 → 该分支永久不可达(建议制作者验证) |
| videoUrl 与 imageUrl |
type: "image" 时可省略 videoUrl;type: "video"(默认)时必须提供 videoUrl |
| loopStart / loopEnd |
两者必须同时出现或同时不出现。loopStart < loopEnd |
| JSON 格式 |
严格 JSON(不支持注释、尾逗号)。用编辑器导出可保证格式正确 |