Skip to content
On this page

场景镜头

场景镜头是一段由锚点、输入策略和有序关键帧组成的声明式时间轴。客户端会在每个渲染帧采样镜头位置,不接收任意脚本代码,也不需要服务端每 Tick 推送镜头位置。已有场景无需迁移,会自动使用新的平滑轨迹。

服务端文件放在 plugins/ChaEngine/camera/scenes/,支持子目录;场景 ID 是相对于该目录且不含 .yml 的路径。

完整配置

yaml
# plugins/ChaEngine/camera/scenes/intro.yml
anchor:
  type: player                              # player、entity 或 world
  tracking: live                           # live 持续跟随;snapshot 固定开始时状态
input:
  perspective-locked: true                 # 播放时禁止玩家主动切换视角
  look-mode: locked                        # player、camera_orbit 或 locked
  movement-mode: block                     # allow 或 block
  movement-reference: player_body          # player_body 或 camera_yaw
  recenter-mode: none                      # none、immediate 或 smooth
visibility:
  render-player: true                      # 是否显示本地玩家模型
  render-hand: false                       # 是否显示第一人称手部
  hud: hide                                # keep 保留 HUD;hide 隐藏 HUD
skippable: true                            # true 时玩家可按 Esc 跳过
restore:
  mode: previous                           # 当前请使用 previous;original 暂按 previous 处理
  duration-ms: 500                         # 旧配置回退值;新配置由 transitions.exit 接管
transitions:
  enter:
    duration-ms: 600                       # 从当前画面移动到第一关键帧
    bezier: [0.25, 0.1, 0.25, 1.0]
  exit:
    duration-ms: 500                       # 正常播放结束或 stop 后的恢复衔接
    bezier: [0.25, 0.1, 0.25, 1.0]
  interrupt:
    duration-ms: 300                       # 玩家跳过或 interrupt 时的恢复衔接
    bezier: [0.25, 0.1, 0.25, 1.0]
keyframes:
  - name: establish                        # 关键帧唯一名称
    hold-ms: 400                            # 到达后停留时间
    position: [0.0, 2.0, 6.0]             # 相对于锚点的 X、Y、Z 位置
    rotation: [180.0, 8.0, 0.0]           # yaw、pitch、roll,单位为度
    fov: 70.0                               # 视野角,范围 1~170
    collision: clamp                       # clamp 防穿墙;ignore 忽略碰撞
    cinematic: [0.12, 0.12, 0.0, 0.0]     # [上, 下, 左, 右] 黑边比例
  - name: approach
    transition-ms: 1600
    hold-ms: 600
    position: [1.2, 1.4, 3.0]
    rotation: [165.0, 3.0, 5.0]
    fov: 55.0
    bezier: [0.42, 0.0, 0.58, 1.0]
    collision: ignore
    # 未填写 cinematic,自动继承 establish 的黑边

cinematic 数组固定使用 [top, bottom, left, right],即 [上, 下, 左, 右],每个值必须是 0~0.5 的有限数。第一关键帧省略时从全零开始;后续关键帧省略时继承上一帧。黑边在关键帧之间平滑变化,不会因为窗口尺寸或分辨率改变而使用过期像素值。普通 HUD 位于黑边下方,聊天和字幕仍显示在黑边上方。

显式配置 transitions.enter 后,第一关键帧可省略 transition-msbezier,进入段只计算一次。旧场景仍可继续把第一段写在第一关键帧,并用 restore.duration-ms 控制恢复;新旧字段同时存在时,以 transitions 为准。

锚点类型

类型额外字段适用场景
player围绕正在观看场景的玩家播放
entitydimensionentity-uuid跟随指定实体;实体消失或维度不匹配时中止
worlddimensionpositionrotation以固定世界坐标和朝向播放

实体锚点示例:

yaml
anchor:
  type: entity                              # 跟随指定实体
  tracking: live                           # 每帧读取实体当前位置
  dimension: "minecraft:overworld"         # 实体所在维度
  entity-uuid: "00000000-0000-0000-0000-000000000001" # 实体 UUID

世界锚点示例:

yaml
anchor:
  type: world                               # 固定世界坐标锚点
  tracking: snapshot                       # 世界锚点通常保持固定
  dimension: "minecraft:overworld"         # 锚点所在维度
  position: [120.5, 70.0, -35.5]           # 世界 X、Y、Z
  rotation: [90.0, 0.0]                    # 锚点 yaw、pitch

live 会持续读取玩家或实体的位置和朝向,适合跟随移动目标;snapshot 在开始时固定锚点,适合相对于开场位置播放。

关键帧与时间

  • 第二帧及后续帧的 transition-ms 是从上一关键帧移动到当前关键帧的时间。
  • hold-ms: 0 表示镜头连续穿过当前关键帧,不会在该点刻意减速至静止。
  • hold-ms 大于 0 时,镜头会平滑减速并准确停在当前关键帧,停留结束后再平滑启动。
  • 第一帧由 transitions.enter 从玩家当前看到的画面平滑进入,不会先瞬切到目标位置。
  • 位置、旋转和 FOV 会在相邻关键帧间生成连续轨迹,yaw 与 roll 会走跨越 -180/180 的最短角路径,FOV 不会越过相邻关键帧给出的范围。
  • 时间轴按渲染 FPS 更新;live 锚点仍使用游戏的 partialTick 渲染插值跟随玩家或实体,因此镜头轨迹和移动锚点都不会被 20 TPS 的更新频率限制。
  • bezier 仍控制每段移动的快慢节奏。建议 Y 控制点保持在 0~1;如果故意写成非单调曲线,镜头可能在该段内短暂倒退。
  • collision: clamp 会在镜头接近墙面时缩短最终可见距离,因此实际画面可能偏离理想曲线;障碍消失后会继续按当前时间轴位置渲染,不会把碰撞结果带入后续关键帧。
  • 场景至少需要 1 个、最多 512 个关键帧,总时长最多 30 分钟。
  • 所有数值必须是有限数,贝塞尔曲线的第 1、3 个值必须在 0~1 范围内。

连续穿过多个关键帧

下面的场景会连续穿过 middle,只在最后的 finish 停下。三个关键帧方向一致时,镜头经过中间点不会出现突然减速或方向折断。

yaml
# plugins/ChaEngine/camera/scenes/smooth-pass.yml
anchor:
  type: player
  tracking: snapshot
input:
  perspective-locked: true
  look-mode: locked
  movement-mode: block
  movement-reference: player_body
  recenter-mode: none
visibility:
  render-player: true
  render-hand: false
  hud: hide
skippable: true
restore:
  mode: previous
  duration-ms: 500
keyframes:
  - name: start
    transition-ms: 0
    hold-ms: 0
    position: [0.0, 2.0, 8.0]
    rotation: [180.0, 5.0, 0.0]
    fov: 70.0
    bezier: [0.0, 0.0, 1.0, 1.0]
    collision: clamp
  - name: middle
    transition-ms: 1200
    hold-ms: 0
    position: [2.0, 2.4, 5.5]
    rotation: [165.0, 3.0, 2.0]
    fov: 62.0
    bezier: [0.42, 0.0, 0.58, 1.0]
    collision: clamp
  - name: finish
    transition-ms: 1200
    hold-ms: 600
    position: [4.0, 2.0, 3.0]
    rotation: [150.0, 0.0, 0.0]
    fov: 55.0
    bezier: [0.42, 0.0, 0.58, 1.0]
    collision: clamp

在指定关键帧平滑停下

下面的场景会在 inspect 准确停留 800 毫秒,再移动到 leave。适合展示 NPC、建筑或任务目标。

yaml
# plugins/ChaEngine/camera/scenes/smooth-stop.yml
anchor:
  type: player
  tracking: live
input:
  perspective-locked: true
  look-mode: locked
  movement-mode: block
  movement-reference: player_body
  recenter-mode: none
visibility:
  render-player: true
  render-hand: false
  hud: keep
skippable: true
restore:
  mode: previous
  duration-ms: 500
keyframes:
  - name: inspect
    transition-ms: 1400
    hold-ms: 800
    position: [1.5, 2.2, 4.0]
    rotation: [170.0, 4.0, 0.0]
    fov: 48.0
    bezier: [0.25, 0.1, 0.25, 1.0]
    collision: clamp
  - name: leave
    transition-ms: 1000
    hold-ms: 300
    position: [-1.0, 3.0, 7.0]
    rotation: [-170.0, 8.0, 0.0]
    fov: 70.0
    bezier: [0.42, 0.0, 0.58, 1.0]
    collision: clamp

播放、停止、打断与恢复

管理员可为在线玩家播放场景、停止最近场景、按会话 ID 停止指定场景,也可以独立打断当前场景。完整可复制命令见命令参考。Java 调用方应保存 playScene 成功返回的会话 ID,再用 stopinterrupt 精确处理自己的场景。

  • 自然播放完成:使用 transitions.exit,衔接结束后报告 FINISHED
  • scene stop:使用退出衔接,衔接结束后报告 ABORTED,原因是 stopped。
  • 玩家按 Esc 或执行 scene interrupt:使用 transitions.interrupt,衔接结束后报告 INTERRUPTED
  • camera reset、断线和世界卸载:用于立即清理,不承诺播放完整过渡。

终态会在衔接动画完成后上报,因此剧情插件不会在镜头仍移动时提前开始下一段。重复打断同一个正在退出的场景不会创建第二段恢复动画。

previous 会恢复被场景暂时压住的预设或普通控制。配置解析和通信协议也接受 original,但当前客户端尚未实现它“直接恢复进入相机系统前原始状态”的独立语义,实际仍按 previous 处理;现阶段不要依赖两者差异。无论正常结束、按 Esc 跳过、管理员停止还是异常中止,输入限制都必须随场景一起解除。

与剧情逻辑配合

场景文件只负责相机,不执行命令、条件、循环或其他玩法逻辑。需要在开场、结束或中止时继续剧情时,由业务插件监听 CameraStatusEvent,根据 STARTEDFINISHEDABORTEDINTERRUPTEDREJECTEDUNSUPPORTED 状态处理。

多人同一时刻调用只能保证服务端发起时机接近,不保证不同客户端逐帧完全同步。需要精确战斗判定时,仍应以服务端逻辑为准。

常见问题

场景一播放就结束:检查锚点是否合法;实体锚点必须使用目标维度和真实 UUID,且客户端需要能解析到该实体。

关键帧配置导致整次重载失败:检查关键帧名称是否重复、时间是否为非负整数、数组长度是否正确,以及 FOV、贝塞尔参数是否越界。

场景结束后没有回到肩射预设:使用 restore.mode: previous,并确认下层预设会话没有被单独停止或重置。

玩家无法跳过:确认 skippable: true;只有当前生效的可跳过场景会响应 Esc。

隐藏 HUD 后担心无法退出:HUD 隐藏不影响暂停菜单、聊天和恢复入口;异常结束也会恢复原状态。

相关文档