bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.18k stars 3.57k forks source link

Window centering does not respect scale factor override #9606

Open ewrogers opened 1 year ago

ewrogers commented 1 year ago

Bevy version

0.11.2

[Optional] Relevant system information

`AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }`

What you did

  1. Create a basic Bevy application with DefaultPlugins
  2. Override the primary_window to 640x360 in the WindowPlugin initialization
  3. Add Startup system for setup_resolution that adjusts window.resolution.set_scale_factor_override
  4. Call window.position.center(MonitorSelection::Current) to center the window
  5. Observe incorrect centering using scale_factor instead of override

What went wrong

The window should be centered using the new scale_factor_override instead of the current scale_factor from the OS.

Ex:

pub fn setup_resolution(mut windows: Query<&mut Window>) {
    let mut window = windows.single_mut();

    window.resolution.set_scale_factor_override(Some(3.0));
    window.position.center(MonitorSelection::Current);

    info!("Window created: {:?}", window.resolution);
}
`Window created: WindowResolution { physical_width: 1920, physical_height: 1080, scale_factor_override: Some(3.0), scale_factor: 2.0 }`

Additional information

Only tested on macOS currently, not sure if works properly on Windows due to how scale factor works.

I encountered this by working on a pixel-art game with a reference resolution of 640x360 and want to apply a scale factor to retain the sharpness via integer scaling (1x, 2x, 3x, 4x, etc). I don't know if this the correct way, but it did expose this bug.

ewrogers commented 1 year ago

For those wondering how to do this properly, setting the scale value for the projection when creating a Camera2dBundle is the way to go:

const REF_WIDTH: f32 = 640.;

pub fn setup_camera(mut commands: Commands, primary_window: Query<&Window, With<PrimaryWindow>>) {
    let window = primary_window.single();
    let (width, height) = (window.resolution.width(), window.resolution.height());

    // Scale the camera to match the reference resolution
    // This is done to retain the pixel-art look and sharpness
    let scale = window.resolution.width() / REF_WIDTH;

    commands.spawn((
        Camera2dBundle {
            projection: OrthographicProjection { scale, ..default() },
            ..default()
        },
        MainCamera,
    ));

    info!(
        "Camera initialized for resolution: {}x{} (scale: {:.1})",
        width, height, scale
    );
}

You don't need to mess with the window.resolution.scale_factor_override at this point.

Probably a good thing to add to docs somewhere for those going for pixel-art aesthetics and integer scaling on various resolutions.

rparrett commented 1 year ago

This is specific to MonitorSelection::Current.

It seems like this would be relatively easy to fix for the case where you are positioning the window after initialization as shown in the issue description.

But it's not as clear when the position is configured in the plugin like

app.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_window: Some(Window {
        resolution: WindowResolution::default().with_scale_factor_override(1.4),
        position: WindowPosition::Centered(MonitorSelection::Primary),
        ..default()
    }),
    ..default()
}));

Because the "current monitor" can't be known before the window is created.

However, the full-screen WindowModes seem to make the assumption that the window should be shown on the primary monitor, which we could perhaps do here.

The current_monitor seems to be immediately available after building the winit window on macos but I'm not sure if that's the case one very platform. But perhaps window centering could be moved just after that.