Skip to content

推拉与适配:scale 与 ScalingMode

分镜表上写着三种景别:远景交代全场,中景跟住调度,特写怼脸拍情绪。摄影上这叫推拉镜头——可在正交投影的世界里,把相机沿 z 轴“拉远”毫无用处:正交的本性就是远近一样大,z 挪到天边,画面纹丝不动。正交世界的变焦环长在投影上。

一个常见的坑

变焦要动投影参数,那就得亲手构造一份 OrthographicProjection。凭 Rust 直觉,改一个字段、其余走默认,标准写法应该是结构体更新语法配 ..default()

rust
fn setup(mut commands: Commands) {
    commands.spawn((
        Camera2d,
        Projection::Orthographic(OrthographicProjection {
            scale: 1.5,
            ..default() // 错误:OrthographicProjection 没实现 Default
        }),
    ));
}

Listing 13-6:行不通——OrthographicProjection 拒绝 ..default()(no-compile/listing-13-06.rs)

编译器一口回绝:

text
error[E0277]: the trait bound `bevy::prelude::OrthographicProjection: std::default::Default` is not satisfied
  --> ch13-cameras\no-compile\listing-13-06.rs:11:15
   |
11 |             ..default() // 错误:OrthographicProjection 没实现 Default
   |               ^^^^^^^^^ the trait `std::default::Default` is not implemented for `bevy::prelude::OrthographicProjection`

这不是疏忽,是 Bevy 故意不给 OrthographicProjection 实现 Default。原因藏在上一节的 near/far 里:2D 惯用 near = -1000(相机身后也算数,z 只是图层号),3D 惯用 near = 0(背后的东西不该入镜)——没有一份默认值能同时伺候两边。所以构造函数一分为二,逼你表明身份:

  • OrthographicProjection::default_2d()——near = -1000,给 2D 用;
  • OrthographicProjection::default_3d()——near = 0,给 3D 用。

..default() 换成 ..OrthographicProjection::default_2d(),就是正确的打开方式。网上不少老版本教程还在写 ..default(),遇到这条报错你现在知道病根了。

三档分镜

正式的变焦环是 scale 字段:取景框整体乘上这个倍数。scale = 1.5,取景框放大 1.5 倍,更多世界挤进同一幅画面,物体显小——这是远景;scale = 0.6,取景框缩小,物体放大——这是特写。给片场排一张三档分镜表,三秒一切:

rust
commands.spawn((
        Camera2d,
        Projection::Orthographic(OrthographicProjection {
            // 锁定纵向视野 600 世界单位,横向随窗口比例配平
            scaling_mode: ScalingMode::FixedVertical {
                viewport_height: 600.0,
            },
            ..OrthographicProjection::default_2d()
        }),
    ));

Listing 13-7(节选一):自定义投影的正确姿势(examples/listing-13-07.rs)

rust
/// 三秒切一镜:改投影的 scale,镜头应声推拉
fn cut_shots(
    time: Res<Time>,
    mut projection: Single<&mut Projection, With<Camera2d>>,
    mut shot: Local<usize>,
    mut clock: Local<f32>,
) {
    *clock += time.delta_secs();
    if *clock < 3.0 {
        return;
    }
    *clock -= 3.0;
    *shot = (*shot + 1) % SHOTS.len();

    let (name, scale) = SHOTS[*shot];
    if let Projection::Orthographic(ortho) = &mut **projection {
        ortho.scale = scale;
        println!("老雷:切{name}!(scale = {scale})");
    }
}

Listing 13-7(节选二):三秒切一镜,改 scale 就是推拉(examples/listing-13-07.rs)

切镜系统的写法都是旧识:Local 计时(第 4 章)、Single<&mut Projection> 拿独苗(第 4 章)、改 enum 字段前先 if let 验流派。投影一被改写,引擎在帧末自动重算取景框——area 应声伸缩,上一节的报点系统若还在场,能看到范围跟着分镜变。

console
cargo run -p ch13-cameras --example listing-13-07
text
老雷:按分镜表走——远景开场,三秒一切。
老雷:切中景!(scale = 1)
老雷:切特写!(scale = 0.6)

画面三秒一跳:远景里全场尽收,灯笼柱排成糖葫芦;特写里阿燕占满半屏,红衣的像素粒粒可数。注意这是“硬切”——scale 瞬间改值。想要“缓推缓拉”的电影感?上一节刚学的 smooth_nudgef32 同样适用,练习里见。

三幅并排的实拍画面:远景 scale 1.5 里物体最小、视野最大,中景 scale 1.0 居中,特写 scale 0.6 里侠客和灯笼柱明显放大

Figure 13-4:三档分镜实拍——scale 越大,取景框越大,画面里的东西越小

窗口一拉,画面听谁的

Listing 13-7 的相机还埋了第二处新东西:scaling_mode 字段换成了 ScalingMode::FixedVertical { viewport_height: 600.0 }。它管的是另一件事:窗口尺寸变了,取景框怎么应对

默认值 WindowSize 就是前几节实测过的一比一规则:窗口多少像素,取景框就多少世界单位。这条规则对工具类应用很自然,对游戏却常常是错的——玩家把窗口拖大一倍,看到的世界就多一倍,等于开了“大窗口物理外挂”。ScalingMode 的六个变体就是六种应对方针:

ScalingMode方针
WindowSize一比一:窗口多大,世界看多大(默认)
Fixed { width, height }死守一块固定大小的世界,窗口变形就跟着拉伸变形
FixedVertical { viewport_height }纵向视野锁死,横向随窗口比例配平
FixedHorizontal { viewport_width }横向视野锁死,纵向随窗口比例配平
AutoMin { min_width, min_height }保比例缩放,保证至少看见这么大一块
AutoMax { max_width, max_height }保比例缩放,保证最多看见这么大一块

FixedVertical { viewport_height: 600.0 } 翻译成人话:不管窗口怎么拉,基础取景框的上下方向永远是 600 个世界单位,左右按窗口宽高比补齐。注意“永远”管住的变量是窗口,不是 scale——引擎先按 scaling_mode 从窗口尺寸算出基础取景框,再整体乘上 scale 才得到最终画面。所以本例实际看到的纵向范围是 600 × scale:远景 900、中景恰好 600、特写只剩 360;变的全在 scale 这一侧,窗口那一侧永远锁死。跑起 Listing 13-7 亲手拖一拖窗口边框,对比前几个示例(默认 WindowSize)的手感:

  • WindowSize:窗口拉高,看见更多世界,灯笼柱大小不变;
  • FixedVertical:窗口拉高,世界跟着放大,纵向能看见的范围分毫不让。

游戏里最常用的正是 FixedVertical/FixedHorizontal 与两个 Auto 流派——视野是设计师定的,不是玩家的显示器定的。两个字段就此分好了工:应对窗口归 scaling_mode,推拉变焦归 scale;想变焦就动 scale,别去改 viewport_height,官方文档也是这么劝的。

镜头会跟、会推拉、会应对窗口了——单机位的本事齐了。可老雷的片子是双主角:阿燕在东,踏雪在西,一台机器盯不过来。下一节,导播台上墙。