过场场景运行时
场景所有权、触发历史、演员控制权、中途加入与客户端清理。
每个过场场景包含一条服务端时间线,以及每位观众各自的本地播放。服务端负责演员绑定、世界事件和队伍会话,客户端负责镜头采样、玩家转向与呈现;集成时必须同时释放两侧持有的状态。
它做什么
internal.scene.ServerSceneManager 是服务端内部入口,不是公共 API 门面。它启动数据包加载的定义,并将场景共享给符合条件的观众。全部制作字段见场景数据参考;独立的纯客户端动画使用镜头 API。
触发方块提供自动准入、按进度选取场景和持久重播规则。它们不保存活动时间线,也不会在服务器重启后续播被中断的场景。
运行时模型
| 所有者 | 状态与更新时机 |
|---|---|
SceneDefinitionLoader | 当前定义表与修订号,服务端数据重载时替换;已有会话继续持有捕获的定义 |
ServerSceneManager | 以 team:<uuid> 或 player:<uuid> 为键的静态映射;在 END_SERVER_TICK 更新,并移除断线观众 |
ServerScenePlayback | 世界、枢轴、演员、观众 UUID、跳过票数、已触发事件索引和播放年龄 |
ScenePuppetController | 演员 AI/疾跑快照、指令起始标记及服务端路径跟随器 |
ClientSceneManager | 一个活动或待启动场景、退出过渡、保存的 HUD/视角与输入所有权 |
ClientScenePlayback | 演员实体 ID、本地移动跟随器、呈现事件标记与本地年龄 |
SceneTriggerMemoryState | 持久触发器/观众历史,与活动会话分开保存 |
服务端只解析一次演员,取得控制权后发送启动包。每个服务端刻依次移除无效观众、接纳附近的合格观众、停止空会话、更新演员、触发到期世界事件,最后增加年龄。年龄超过 lengthTicks 才停止,因此包含最后一个制作刻。
客户端在实体更新前应用移动,在实体更新后推进呈现。启动包包含当前年龄、枢轴、演员实体 ID 和重建后的定义。场景 JSON 压缩为 NBT,压缩后上限为 262144 字节,包内最多携带 64 个演员绑定。经 SceneAuthoringDraft 重建时会规范化并舍入数据,不是源 JSON 的逐字节副本。尤其是含 look_at 的镜头节点,其制作时的 rot 会在这条路径中丢失。
准入与替换
观众必须存活、非创造且非旁观、仍连接服务器,并处于场景所在世界。队伍身份来自 IsekaiCore 队伍桥接;无队伍玩家使用个人键。本仓库在队伍拥有者在线时读取其主线进度,否则读取触发玩家的进度。
| 观众模式 | 初始及后续准入 |
|---|---|
TRIGGERING_PLAYER / triggering_player | 仅触发玩家 |
TEAM_NEARBY / team_nearby | 枢轴球形半径内符合条件的同队玩家 |
ALL_NEARBY / all_nearby | 球形范围内符合条件的任意队伍玩家 |
玩家演员也会加入观众,因为移动由他们自己的客户端驱动;仍需通过资格与世界检查。半径大于零时启用后续准入检查;已有观众不会仅因离开半径而被移除。非演员观众必须继续满足模式对应的身份或队伍条件;演员绕过身份限制,但不能绕过世界或资格检查。
启动场景会替换该队伍键的现有会话,并停止包含任一初始观众的其他会话。后续观众刷新和玩家演员准入不会执行同样的冲突扫描,因此不能保证每名玩家始终只属于一个会话。应避免观众或演员重叠的触发器同时启动。
isPlaying(player) 先查玩家当前队伍的会话,再查包含该观众的会话。因此,队友即使不是观众也可能返回正在播放,stop(player, ...) 也能停止该队伍会话。跳过请求另行要求实际观众身份。
触发选择与重播
isekaiexpansion:scene_trigger 方块不可见且无碰撞。可选中轮廓与主手编辑均要求创造模式及权限等级 2。放置时使用玩家的水平方向;枢轴为方块中心,朝向提供偏航。
检测范围为以配置半径作为半边长的立方体,与观众搜索使用的球形范围不同。冷却之外的每个服务端刻都会扫描合格玩家,第一个成功启动所选场景的玩家会消耗本次触发机会。规则按列表顺序检查含端点的进度区间,没有匹配项时使用默认场景。若已匹配规则引用的定义不存在,不会继续尝试后续规则或默认场景。
| 重播模式 | 记忆与冷却 |
|---|---|
ONCE_PER_AUDIENCE / once_per_audience | 为本触发器与所选场景保存触发者的队伍/个人键 |
GLOBAL_ONCE / global_once | 为本触发器与所选场景保存 global 键 |
REPEATABLE / repeatable | 不保存持久历史;冷却至少 200 刻(10 秒) |
重播范围独立于观众模式。例如,all_nearby 配合 once_per_audience 仅记录触发队伍的键,不会记录每位观众的队伍。历史在启动被接受时写入,而不是播放完成后,因此跳过或断线不会撤销记录。已关联活动会话的玩家会被触发器跳过。
冷却由整个方块共享,不按观众分别计算。配置默认值为 80 刻(4 秒),限制为 1–72000 刻(0.05–3600 秒);可重复模式则为 200–72000 刻(10–3600 秒)。剩余倒计时不保存,重载后只保留配置时长。
约定
演员与世界控制权
演员解析及指令格式见数据参考。所有已解析演员都会被接管,即使没有指令:停止生物导航、禁用 AI,并取得 NPC 大脑控制权。停止时恢复原有 AI 和非玩家疾跑状态,释放控制权,清除场景移动及忙碌状态。不会恢复位置、世界时间或天气。
快照按演员键保存,而不是按唯一实体保存。不要将多个演员键绑定到同一实体后假定它们可以独立恢复。管理器也没有通用的跨会话演员锁。
| 指令路径 | 控制权与时序 |
|---|---|
| 指令开始 | 服务端年龄恰好等于指定刻时触发一次;世界事件则在到期或逾期时触发一次 |
玩家 walk / sprint | 所属客户端通过 PlayerLocomotionOverride.Source.CINEMATIC 写入移动,由正常行走流程产生位置更新 |
非玩家 walk / sprint | 服务端跟随器驱动带碰撞的移动、贴地及身体/头部转向 |
navigation | 对可寻路演员请求导航至终点,不走镜头跟随器路径 |
| 其他插值移动 | 服务端沿制作路径插值并设置演员位置,不是贴地步态 |
| 姿态/传送 | 服务端设置位置;玩家使用网络传送 |
| 玩家动画 | 客户端动画层;NPC 动画和身体表情仍由服务端演员操作处理 |
移动跟随器根据实际位置计算进度。演员被挡住时不会仅因时间经过而沿路径前进。移动审计可警告路径与时长不匹配,但不会延长指令窗口或场景时长。玩家动画的淡入时长受指令持续时间约束,不用于重定时动画片段。
世界声音与粒子使用世界广播,时间和天气会改变世界。visible_to_all 开关属于 Effek 路径,不是通用观众过滤器。停止场景会阻止后续事件,但不会回滚此前事件。
锁定、跳过投票与清理
启用 lock_input 或 lock_movement 任一项时,服务端以唯一的 cinematic_scene:<uuid> 来源禁用物品使用、攻击和跳跃。动作系统的 MOVE 标记保持未设置,以便玩家行走流程继续运行。锁定时长为剩余场景刻数加退出淡化刻数,再加 20 刻(1 秒),最少 1 刻(0.05 秒)。清理只移除此场景自己的动作锁来源。
客户端强制背后第三人称视角,可按配置隐藏 HUD,在输入锁定期间关闭界面,并在需要时应用过场移动覆盖。它保存并恢复原有视角与 HUD 设置。场景活动时会隐藏不在演员表中的玩家实体,不会隐藏所有非演员实体。
只有当前观众且 allow_esc_skip 为 true 时,跳过请求才有效。每位观众只有一票,需要所有当前观众同意。离开的观众失去投票并可能降低所需票数,中途加入者则增加票数。重复投票返回当前状态。客户端用带键的弹窗显示票数,持续 2400 ms,进入淡化为 80 ms,退出淡化为 160 ms。
进入淡化会延后本地播放,并将淡化时长加到收到的年龄上,因为服务端仍在推进。迟到客户端只预载该年龄仍有效的呈现,不会重新播放已过期字幕或完整动画。移动从玩家实际位置开始跟随。世界事件不会为迟到观众重新执行。
退出淡化停止本地时间线工作,但保留最后的镜头姿态与锁定,直到回调恢复状态。常规 stop(false) 取消待启动场景时会恢复状态,但不显式清空淡化控制器;立即替换或断线清理会清空。断线还会清除过场移动、输入、字幕和动画状态。共享的震动/扭曲控制器按全局清空,效果并不按场景所有权隔离。
启动与停止包通过客户端执行器处理,跳过与更新包通过服务端执行器处理。停止/跳过包没有场景令牌;其他调用者必须协调,不能假定过时请求可对应到某次具体播放。
字幕与客户端设置
字幕以不可变呈现快照从客户端传给非交互式 ModernUI 浮层。共享的RetainedOverlay负责挂载、卸载与原子投递,忽略连续相同的快照,挂载时立即应用最新快照。浮层保留最新待处理快照,不在 UI 线程读取配置,并独立于原版 HUD 的可见性。正文与说话人按翻译键解析,找不到翻译时显示原文。
FeatheredPlateView为过场字幕和CaptionLineView提供共用背景;NPC指令反馈通过CaptionAPI的NPC_FEEDBACK频道进入后者。背景用缓存网格连接多层圆角轮廓,在一次绘制中插值五次缓动透明度。相邻区域共用边缘,不叠加透明度,最外层完全透明。两种字幕均使用这套羽化,不保留不透明边框,也不拉伸背景贴图。
只有尺寸、颜色、宽度比例、羽化轮廓或圆角半径改变时才重建几何数据,视图卸载时释放缓存数组。两种字幕的实心区域圆角半径均为10 dp乘以呈现缩放,外层圆角随羽化宽度向外扩展。文字逐字显示和位移动画共用已缓存的网格。
SubtitleFade为两种字幕提供五次缓动:淡入最长360 ms,自然淡出最长460 ms,各自不超过视图创建时剩余指令时长的35%。UI延迟收到字幕时,背景仍从全透明开始淡入,文字进度则按原定时间戳追赶。替换或清空字幕时,从当前显示的透明度开始,在360 ms内淡出;已经不可见的旧行可以立即移除。反馈旧行淡出时保留已有的弱化透明度,不再次叠加;字幕堆叠位置变化时,从当前显示位置平滑移至新位置。
过场字幕同一时刻只有一条当前指令。旧视图在交叉淡化期间保留自己的文字和布局;快速连续替换时,之前仍可见的视图也会完成淡出。浮层用同一个逐帧回调推进所有保留视图。逐字显示的时间戳使用墙钟毫秒,每个场景刻折算为50 ms,同时也按场景年龄检查指令过期。因此,渲染时钟的逐字显示不保证随场景刻一起暂停。主题值在显示指令时捕获,修改配置不会重新着色已发布的快照。
| 选项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
cinematicSubtitleBandColor | string | "#0A0F18" | 字幕带 RGB,格式错误时回退到此值。 |
cinematicSubtitleBandOpacity | integer | 88 | 百分比,限制为 0–100,按 ×2.55 四舍五入为透明度字节。 |
cinematicSubtitleBandWidthPercent | integer | 96 | 字幕带宽度比例,限制为 40–100%。 |
cinematicSubtitleTextWidthPercent | integer | 62 | 正文宽度比例,限制为 30–96%。 |
cinematicSubtitleSpeakerColor | string | "#FFD79A" | 不透明说话人 RGB,格式错误时使用此回退值。 |
cinematicSubtitleTextColor | string | "#F3F6FF" | 不透明正文 RGB;指令颜色的透明度字节非零时由指令覆盖。 |
cinematicSubtitleScalePercent | integer | 100 | 缩放比例,限制为 60–160%。 |
这些是客户端呈现选项,不是场景数据,也不由服务端同步。颜色解析会去掉首尾空白,接受 6 或 8 位十六进制数字及可选的前导 #,忽略提供的透明度;字幕带透明度由独立选项控制。指令颜色的透明度字节为零时,继承配置的正文颜色。
触发器存档数据
下表是方块实体 NBT,不是场景 JSON。编辑器更新在服务端执行,要求同样的创造/权限条件、目标位置确有触发器,且距离不超过 8 方块。更新会验证 ID 语法,但不要求对应定义存在。
| NBT 键 | 类型 | 默认值 | 含义 |
|---|---|---|---|
Scene | identifier | 无 | 默认场景;读取时语法无效则为 null。 |
SceneRules | array<object> | [] | 保留前 16 条有效规则;缺失或列表类型错误时无规则。 |
SceneRules[].Scene | identifier | 必填 | 标识符无效时跳过此规则。 |
SceneRules[].MinProgress | integer | 0 | 含端点的下界;上下界颠倒时交换。 |
SceneRules[].MaxProgress | integer | 0 | 含端点的上界;不要求为正数。 |
Radius | number | 2.0 | 方块。配置时限制为 0.25–128;NBT 读取仅应用 0.25 下限。 |
ReplayMode | string | "once_per_audience" | 上表中的重播 ID;空白或未知值使用默认值。 |
AudienceMode | string | "team_nearby" | 上表中的观众 ID;空白或未知值使用默认值。 |
CooldownTicks | integer | 80 | 配置冷却,按重播模式校正;可重复模式的默认值变为 200。 |
模式读取器会去掉首尾空白并转为小写。半径校正是数值限幅,不包含显式有限数检查。规则保持原有顺序,不检测区间重叠,也不排序。
持久历史使用持久状态中说明的服务端作用域文件 isekaiexpansion/scene/trigger_memory_v2.dat。作用域包含维度、不可变方块位置及所选场景 ID,因此改换场景或移动方块都会得到不同的历史键。
| NBT 键 | 类型 | 默认值 | 含义 |
|---|---|---|---|
version | integer | 0 | 必须等于 2;缺失或版本不符时记录警告并丢弃读入历史。 |
entries | array<object> | [] | 触发器作用域;缺失或列表类型错误时得到空状态。 |
entries[].world | identifier | 必填 | 维度 ID 语法;无效条目被跳过。 |
entries[].x、entries[].y、entries[].z | integer | 0 | 触发器坐标。 |
entries[].scene | identifier | 必填 | 所选场景 ID;无效条目被跳过。 |
entries[].audiences | array<string> | 必填 | 观众键集合;缺少列表时跳过条目,重复作用域/键会合并。空白键规范化为 unknown。 |
只有加入新观众键时才标记此状态需要保存。写入会排序作用域与观众键,并省略空集合。它记录触发历史,不是成功播放完成列表;此类不提供自动清理或重置命令。
扩展方式
内部服务端集成应在定义已加载、玩家已处于服务端世界后,从服务端线程调用:
boolean accepted = ServerSceneManager.play(
player,
new Identifier("isekaiexpansion", "hidden_hud_subtitle_scene"),
new SceneTransform(player.getPos(), player.getYaw())
);
此重载仅将触发玩家作为观众,后续准入半径为零。场景缺失时在替换现有会话之前返回 false。True 表示已创建播放,不表示所有请求观众均已准入或播放已完成;创造模式触发玩家可能使会话没有观众,并在下一刻停止。
需要共享时,使用带显式 SceneAudienceMode 的 playFromTrigger。这些管理器调用不会自动切换线程,也不支持在服务端或玩家世界存在之前调用。专用服务端路径不得加载客户端管理器和镜头类。
添加指令或事件时,应同时更新定义解析、草稿序列化及对应服务端/客户端分派器。只改解析器,字段可能在网络包重建时丢失。保持按来源释放动作锁,并保留玩家与非玩家移动分工。
验证
- 以生存玩家启动已知场景,再以不合格玩家启动,区分启动被接受与观众实际准入。
- 使用另一队伍、无队伍玩家、中途到场、离开半径和切换维度,检查全部观众模式。
- 检查进度规则首项匹配、队伍拥有者离线回退、缺失定义及可重复冷却。重启世界,检查持久与可重复历史的差别。
- 投票跳过、让投票者断线、加入新观众并重复投票,确认所需票数由当前观众决定。
- 让步行演员遇到障碍,确认碰撞影响实际进度,而场景仍按制作时长结束。
- 中断进入和退出淡化、替换场景并断线,确认视角、HUD、移动及动作锁均已释放。
- 使用内置
hidden_hud_subtitle_scene检查隐藏 HUD 时的字幕、主题继承及中途加入。以上是验证步骤,不代表已完成多人或视觉测试。