Files
tianshu-engine/docs/SCENE_JSON_SPEC.md

15 KiB
Raw Blame History

场景 JSON 规范文档

电影游戏引擎的场景数据全部定义在 JSON 文件中。本文档是完整字段参考手册。


目录

  1. 顶层结构 (GameData)
  2. 场景节点 (SceneNode)
  3. 选项 (Choice)
  4. 图片/视频热点 (Hotspot)
  5. 快速反应事件 (QTEDefinition)
  6. 条件和效果 (Condition & Effect)
  7. 背景音乐 (BGM 字段)
  8. 章节 (ChapterInfo)
  9. 成就 (AchievementDef)
  10. 结局 (EndingDef)
  11. 完整示例
  12. 验证规则

1. 顶层结构 (GameData)

{
  "startScene": "intro",
  "variables": { "trust": 50, "courage": 0 },
  "scenes": { ... },
  "chapters": [ ... ],
  "achievements": [ ... ],
  "endings": [ ... ]
}
字段 类型 必填 说明
startScene string 游戏开始的场景 ID
variables Record<string, number> 全局变量初始值(如好感度、勇气值等,制作者自定义)
scenes Record<string, SceneNode> 所有场景节点key 为场景唯一 ID
chapters ChapterInfo[] 章节定义,用于章节选择功能
achievements AchievementDef[] 成就定义,满足条件时自动解锁
endings EndingDef[] 结局定义,用于结局画廊

2. 场景节点 (SceneNode)

{
  "id": "intro",
  "type": "video",
  "videoUrl": "/videos/intro.mp4",
  "imageUrl": "/images/room.jpg",
  "subtitleUrl": "/subtitles/intro.vtt",
  "subtitles": { "zh": "/subtitles/intro_zh.vtt", "en": "/subtitles/intro_en.vtt" },
  "choices": [ ... ],
  "hotspots": [ ... ],
  "qte": { ... },
  "nextScene": "auto_next",
  "onEnter": [ { "type": "set", "target": "visited_room", "value": 1 } ],
  "loopStart": 8.0,
  "loopEnd": 10.0,
  "bgmUrl": "/audio/tense.mp3",
  "bgmVolume": 0.8,
  "bgmCrossFade": 2.0,
  "bgmDuckLevel": 0.35,
  "bgmDuckFade": 0.5,
  "videoMuted": false,
  "skippable": true
}
字段 类型 默认 说明
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": "帮助陌生人",
  "textKey": "scene.intro.choice.help",
  "prompt": "你的善意将被记住",
  "targetScene": "help_ending",
  "conditions": [
    { "variable": "trust", "op": ">=", "value": 50 }
  ],
  "effects": [
    { "type": "add", "target": "trust", "value": 20 }
  ],
  "timeLimit": 10
}
字段 类型 默认 说明
text string 选项显示文字(默认语言)
textKey string 多语言 key。有值时用翻译文件查文字否则用 text
prompt string 选后浮现提示文字Telltale 式 "某人会记住这件事")。有值时按钮前置金色标识
targetScene string 点击后跳转的目标场景 ID
conditions Condition[] 显示条件。所有条件都满足时选项才显示
effects Effect[] 选择后的效果(修改变量值等)
timeLimit number 限时选择。0 或不设表示不限时

4. 图片/视频热点 (Hotspot)

{
  "id": "hs_desk",
  "label": "查看书桌",
  "targetScene": "desk_detail",
  "x": 0.15, "y": 0.30,
  "width": 0.25, "height": 0.35,
  "showAt": 2.0,
  "hideAt": 8.0,
  "conditions": [ { "variable": "investigation", "op": ">=", "value": 1 } ],
  "effects": [ { "type": "setFlag", "target": "checked_desk" } ],
  "timeLimit": 10
}
字段 类型 默认 说明
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": 2.5,
  "prompt": "躲避飞来的石块!",
  "keys": ["ArrowLeft", "ArrowRight", "a", "d"],
  "timeLimit": 3.0,
  "successScene": "qte_win",
  "failScene": "qte_lose",
  "effects": {
    "success": [ { "type": "add", "target": "courage", "value": 10 } ],
    "fail": [ { "type": "add", "target": "health", "value": -20 } ]
  }
}
字段 类型 默认 说明
triggerTime number QTE 触发时间(视频播到第几秒时触发)
prompt string QTE 提示文字
keys string[] 需按下的键,大小写不敏感。如 ["Space"]
timeLimit number 限时(秒)
successScene string 成功时跳转的场景 ID
failScene string 失败/超时时跳转的场景 ID
effects object 成功/失败各自的效果(effects.successeffects.fail

6. 条件和效果 (Condition & Effect)

Condition条件

{ "variable": "trust", "op": ">=", "value": 80 }
字段 类型 说明
variable string 变量名
op string 比较操作符:> / < / >= / <= / == / !=
value number 比较值

注意: 条件中变量的值都是 number 类型。布尔值用 == 1 表示 true== 0 表示 false。

Effect效果

{ "type": "set", "target": "trust", "value": 80 }
{ "type": "add", "target": "courage", "value": 10 }
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": "ch1",
  "label": "第一章:相遇",
  "startScene": "intro",
  "thumbnail": "/images/ch1.jpg",
  "defaultVariables": { "trust": 50, "courage": 0 }
}
字段 类型 必填 说明
id string 章节唯一标识
label string 章节显示名称(支持多语言)
startScene string 章节起始场景 ID。玩家到达此场景时 → 章节自动解锁
thumbnail string 章节缩略图路径320×180 推荐)
defaultVariables Record<string, number> 从章节入口跳转时套用的变量初始值。未设时 fallback 到全局 variables

9. 成就 (AchievementDef)

{
  "id": "helper",
  "title": "善良的人",
  "description": "选择帮助陌生人",
  "icon": "/images/ach_helper.png",
  "hidden": false,
  "condition": { "variable": "trust", "op": ">=", "value": 70 }
}
字段 类型 必填 说明
id string 成就唯一标识
title string 成就标题
description string 成就描述
icon string 成就图标路径(可选)
hidden boolean true 时未解锁不显示标题和描述(显示 ???
condition Condition 解锁条件。变量满足时自动解锁并弹出 toast

10. 结局 (EndingDef)

{
  "id": "help_end",
  "label": "伸出援手",
  "sceneId": "help_ending",
  "thumbnail": "/images/end_help.jpg"
}
字段 类型 必填 说明
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.targetSceneHotspot.targetSceneQTEDefinition.successScene/failSceneChapterInfo.startSceneEndingDef.sceneId 必须引用存在的场景 ID
循环引用 场景 A 的 targetScene 指向 A 自身 → 允许(重玩同一场景)
死路检测 conditions 的 choice 如果条件永远无法满足 → 该分支永久不可达(建议制作者验证)
videoUrl 与 imageUrl type: "image" 时可省略 videoUrltype: "video"(默认)时必须提供 videoUrl
loopStart / loopEnd 两者必须同时出现或同时不出现。loopStart < loopEnd
JSON 格式 严格 JSON不支持注释、尾逗号。用编辑器导出可保证格式正确