对话镜头与地形淡化
按会话持有的肩后构图、演员朝向、移动控制与近处地形着色器覆盖范围。
对话镜头以肩后视角跟随玩家和说话人,每次重新评估视野最多搜索 32 个候选位置。会话与实体身份检查使镜头跟随当前对话,独立的地形着色器补丁则淡化任何脚本镜头附近的几何体。
它做什么
实现位于 client.camera.compat 和 client.camera.fade,均为内部集成类型,不是公共 API 门面。Blabber 桥接为普通和父级对话启动 isekaiexpansion:dialogue_humanoid,并在关闭时传递当前会话 ID;父级对话开始时还会认领对话实体。本仓库将第三方 Blabber 锁定为 0.5.1-build36,这里描述的是模组自身的桥接钩子。
镜头将已加载动画作为跟随对话锚点的偏移,使用 YawAlign.NONE 和对话播放选项。它保持动画末帧,以 5 刻(0.25 秒)混入,保留 HUD 和对话输入,并在自己持有的镜头停止后恢复原视角。内置动画是中性姿态,实际构图与 FOV 由镜头支架提供。
运行时模型
| 阶段 | 所有权与检查 |
|---|---|
| 服务端启动 | 发送会话 UUID、动画 ID、玩家实体 ID/UUID 和说话人实体 ID/UUID;会话 ID 为 null 时不发送 |
| 客户端准入 | 要求世界与本地玩家存在、本地玩家 UUID 匹配,且没有活动或待启动的完整过场 |
| 等待追踪 | 最多尝试 40 次刻末更新(每秒 20 刻时为 2 秒);两个实体 ID 都必须解析到预期 UUID |
| 镜头支架 | 持有原实体对象和客户端世界,每刻更新构图,并采样插值后的眼睛/位置 |
| 停止 | 匹配会话 UUID 或 null 通配停止会将倒计数设为 2;某次刻末更新开始时计数已为零才清理 |
| 清理 | 释放对话移动控制,仅当活动锚点仍持有此支架实例时停止镜头 |
追踪重试上限不保证实际等待时长。尚未观察到 Blabber 对话处理器时,缺失界面超过 10 刻便清理请求;曾观察到后,缺失超过 2 刻便清理。因此,界面检查可能在用完 40 次追踪尝试前结束请求。
新请求若具有相同动画和演员 UUID,且已有支架仍有效并归本会话所有,可复用该支架,只更新会话令牌并取消待停止状态。这样在对话界面之间切换时无需重启镜头。否则先清理旧对话状态,再安装新请求。
断线、世界对象改变、本地玩家死亡、完整过场接管、演员失效或镜头所有权丢失都会清理会话。有效支架要求两个不同、存活且未移除的实体位于当前客户端世界,彼此距离不超过 24 方块。清理会核对活动锚点的支架实例,不会停止无关的替代镜头。镜头控制器调用仍受镜头场景 API所述生命周期约束。
构图与搜索
下列值来自 DialogueCameraSettings.defaults(),不是用户配置:
| 设置 | 类型 | 默认值 | 含义 |
|---|---|---|---|
distance | number | 2.55 | 基础肩后距离,单位方块,乘以两名演员较大眼高除以 1.62 的比例。 |
minDistance | number | 1.25 | 候选距离下限,单位方块。 |
maxDistance | number | 6 | 理想距离上限,单位方块。 |
clearance | number | 0.22 | 镜头碰撞检测盒的半边长,单位方块。 |
followWeight | number | 0.35 | 位置与说话人屏幕 X 的逐刻插值权重。 |
sideHysteresis | number | 1.5 | 测试不同角度时增加的评分代价。 |
verticalFov | number | 35 | 基础垂直 FOV,单位度,窄窗口中会扩大。 |
speakerScreenX | number | 0.60 | 说话人眼睛的归一化屏幕 X,另一侧镜头镜像为 0.40。 |
eyeScreenY | number | 0.34 | 从屏幕顶部计算的归一化眼睛 Y。 |
枢轴使用玩家眼睛的 X/Z,高度向说话人眼睛靠近垂直差的四分之一。双方水平距离平方超过 0.04 时更新玩家指向说话人的水平轴;近乎重合时保留原轴,首次构建必要时使用玩家偏航。
搜索在首次更新、每 4 个更新刻(0.2 秒),或目标镜头检测盒受阻时执行。候选角度为 135、−135、120、−120、150、−150、165 和 −165 度;距离取理想值的 1、0.8、0.6、0.4 倍,均受最小距离约束。
分数越低越好。替代候选计入换角惩罚后仍未改善评分时,保留当前候选:
| 评分项 | 代价 |
|---|---|
| 偏离理想距离 | 距离差绝对值 ×2 |
| 偏离首选肩后角度 | 角度绝对值与 135 度的差,再除以 20 |
| 前景玩家眼睛位置 | 屏幕 X 误差 ×12,目标为 0.24 或 0.76;屏幕 Y 误差 ×8 |
| 玩家眼睛位于镜头后方 | +40 |
| 玩家头顶高于屏幕 Y 0.04 | 超出量 ×30 |
| 镜头碰撞检测盒受阻 | +100 |
| 指向说话人眼睛的射线受阻 | +25 |
| 镜头到任一眼睛的距离平方小于 0.64 | +20 |
| 玩家包围盒扩大 0.1 后与说话人视线相交 | +35 |
| 候选角度不同 | +1.5 |
碰撞使用盒状空间检测,可见性使用方块视觉形状射线并忽略流体。惩罚均为有限值,因此完全封闭的房间仍可能选中受阻镜头。这是有界的最优候选搜索,不保证可见性,也不是寻路器。
位置与屏幕 X 以 0.35 权重接近目标。位置跳变的距离平方超过 36 时,两端插值位置一起跳到目标。平滑后位置受阻时改用目标位置,不沿两者之间执行扫掠检测。焦点取插值后的说话人眼睛,构图滚转保持为零。
窄窗口 FOV 从半角正切计算:乘以 max(1, (16 / 9) / max(0.5, aspect)),再换回角度并限制到 90。计算使用窗口帧缓冲宽高比,不修改用户的游戏 FOV 选项。
约定
朝向与输入
持有有效支架时,对话会话在每个客户端刻开始取得 PlayerLocomotionOverride.Source.DIALOGUE 控制。优先级为 COMPULSION < DIALOGUE < CINEMATIC。生效的保持状态清除前后/横向输入、跳跃和潜行,并消除水平惯性,但保留垂直速度,因此开始对话不会让正在下落的玩家悬空。
对话持有玩家时,鼠标视角增量变为零。仅对话所有权不会启用完整过场的鼠标按键/滚轮锁,对话界面仍可接收输入。玩家朝向在继承的客户端玩家更新之后应用;服务端生物朝向在生物更新末尾执行,先检查追踪的对话玩家和活动会话目标。
| 朝向规则 | 值 |
|---|---|
| 偏航逼近 | 剩余环绕误差的 0.4 倍,每次调用最多 18 度 |
| 身体逼近 | 同样的逼近规则,上限 12 度 |
| 头部偏移 | 限制在共享的相对身体 75 度范围内 |
| 俯仰 | 剩余误差的 0.4 倍,单步限制为 ±12 度,最终范围 −85–85 度 |
| 注视控制 | 请求偏航/俯仰上限 18/12 度 |
朝向要求双方不同、存活、未移除且处于同一世界,转向者还不能睡眠。服务端生物在存在目标、攻击标记、攻击目标大脑记忆,或 NPC 战斗阶段非空闲时,让出朝向控制。若对话玩家的队伍/观众查询表明正在播放服务端过场,也会让出控制。这里修改实体已有的姿态字段,不另装身体动画。
近处地形淡化
CameraMixin 每次更新镜头时推进 CameraBlockFade,依据是控制器是否返回了脚本姿态。对话、镜头 API 和完整过场姿态均包含在内,即使混合权重仍为零。没有姿态的待处理请求本身不会启用淡化。
| 包络值 | 行为 |
|---|---|
| 近距离 | 0.35 方块 |
| 远距离 | 1.8 方块 |
| 进入时间常量 | 0.14 秒 |
| 退出时间常量 | 0.2 秒 |
| 单帧经过时间上限 | 0.1 秒 |
| 直接贴合目标的容差 | 距目标强度 0.001 |
存在脚本姿态时,指数包络趋近强度 1,否则趋近 0。世界改变会重置包络,断线会重置保存的世界、时间与强度。这只改变渲染,不修改方块、碰撞或区块网格。
注入的片段函数根据片段坐标、视口和逆投影重建视图空间位置。覆盖率为 smoothstep(near, far, distance),不透明度按包络强度从 1 插值到覆盖率。稳定的屏幕空间噪声阈值据此丢弃片段。全强度时,近距离以内的几何体消失,远距离以外保持不变,中间区域呈现抖动淡化,不使用 alpha 混合。
渲染路径与限制
| 路径 | 补丁覆盖 |
|---|---|
| 原版 | 名为 rendertype_solid、rendertype_cutout、rendertype_cutout_mipped、rendertype_translucent 和 rendertype_tripwire 的片段程序,允许命名空间前缀 |
| Sodium | 源 sodium:blocks/block_layer_opaque.fsh,GL 程序绑定时上传 uniform |
| Iris 配合 Sodium 地形管线 | 返回的主视图不透明、镂空和半透明地形片段源;不修改阴影 getter |
Sodium 和 Iris mixin 按各自模组 ID 是否存在启用。这些是当前仓库实现的钩子,不保证所有外部渲染器或光影包都能接受生成的程序。
补丁先遮蔽注释,再寻找第一个 void main() 或 void main(void),在其前插入声明,并在函数开头调用丢弃逻辑。若任意位置已出现标记 iseCameraFadeStrength、找不到匹配的 main,或去注释后的源中含 early_fragment_tests,则原样返回。标记检查包含注释,因此注释里出现它也会阻止注入。
Uniform 位置属于各个已链接程序包装器。没有强度 uniform 时不上传;权重为零时仅上传强度,活动时读取当前投影与 GL 视口。地形路径不涵盖任意实体、方块实体、粒子或自定义着色器程序,也不修改 Iris 阴影源。因此,主视图中隐藏的材质仍可能投下阴影。
扩展方式
普通对话使用现有 Blabber 开始/关闭钩子。内部切换其他动画时,服务端调用必须携带真实活动对话会话及说话人:
void sendDialogueShot(ServerPlayerEntity player, Entity speaker) {
var session = Blabber.getActiveDialogueSession(player).orElse(null);
if (session != null) {
DialogueCameraNetworking.sendStart(
player, session.sessionId(),
new Identifier("isekaiexpansion", "dialogue_humanoid"),
player, speaker
);
}
}
在服务端线程运行该调用,服务端初始化路径不得加载纯客户端支架类。接收端随后解析实体和动画,发送本身没有播放确认。关闭指定对话时使用匹配的会话令牌,null 明确表示通配停止。
支架自行构造默认设置,目前修改构图需改实现默认值,或添加显式设置传入路径。新增构图规则应在评分与最终输出中使用同一套构图/投影辅助方法。新增渲染器桥接必须只修改目标片段路径,并为对应链接程序上传 uniform,不能跨程序复用位置。
验证
- 打开普通和父级对话,再在相同演员的对话间切换。确认支架延续,旧停止令牌不能关闭新会话。
- 延迟说话人追踪、让不同 UUID 复用实体 ID,并在重试次数耗尽前关闭界面,检查清理及缺失界面阈值。
- 测试高矮说话人、近乎重合的演员、宽窄窗口、墙壁、角落和完全受阻房间,检查两侧肩后选择及 FOV 上限。
- 在移动或下落时开始对话,再关闭、进入战斗或启动完整过场,确认水平保持、垂直运动与朝向控制权。
- 用其他镜头替换对话镜头,再发送旧关闭事件,确认仅释放对话移动控制。
- 分别在原版、Sodium 和 Iris 中检查淡化进出、维度切换与资源重载。包括有阴影地形和含
early_fragment_tests的着色器;跳过补丁时预期源保持不变。 - 这些检查描述源码预期行为,不代表已认证视觉输出或未测试光影包的兼容性。