Bevy Version: | 0.12 | (outdated!) |
---|
As this page is outdated, please refer to Bevy's official migration guides while reading, to cover the differences: 0.12 to 0.13, 0.13 to 0.14. 0.14 to 0.15.
I apologize for the inconvenience. I will update the page as soon as I find the time.
2D Camera Setup
Cameras in Bevy are mandatory to see anything: they configure the rendering.
This page will teach you about the specifics of 2D cameras. If you want to learn about general non-2D specific functionality, see the general page on cameras.
Creating a 2D Camera
Bevy provides a bundle (Camera2dBundle
)
that you can use to spawn a camera entity. It
has reasonable defaults to set up everything correctly.
You might want to set the transform, to position the camera.
#[derive(Component)]
struct MyCameraMarker;
fn setup_camera(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
transform: Transform::from_xyz(100.0, 200.0, 0.0),
..default()
},
MyCameraMarker,
));
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup_camera)
.run();
}
Projection
The projection is what determines how coordinates map to the viewport (commonly, the screen/window).
2D cameras always use an Orthographic projection.
When you spawn a 2D camera using Camera2dBundle
,
it adds the OrthographicProjection
component to your entity. When
you are working with 2D cameras and you want to access
the projection, you should query for
OrthographicProjection
.
fn debug_projection(
query_camera: Query<&OrthographicProjection, With<MyCameraMarker>>,
) {
let projection = query_camera.single();
// ... do something with the projection
}
Note that this is different from 3D. If you are making a library or some other code that should be able to handle both 2D and 3D, you cannot make a single query to access both 2D and 3D cameras. You should create separate systems, or at least two separate queries, to handle each kind of camera. This makes sense, as you will likely need different logic for 2D vs. 3D anyway.
Caveat: near/far values
The projection contains the near
and far
values, which indicate the minimum
and maximum Z coordinate (depth) that can be rendered, relative to the position
(transform) of the camera.
Camera2dBundle
sets them appropriately for 2D:
-1000.0
to 1000.0
, allowing entities to be displayed on both positive and
negative Z coordinates. However, if you create the
OrthographicProjection
yourself, to change any
other settings, you need to set these values yourself. The default value of the
OrthographicProjection
struct is designed for
3D and has a near
value of 0.0
, which means you might not be able to see
your 2D entities.
commands.spawn((
Camera2dBundle {
projection: OrthographicProjection {
// don't forget to set `near` and `far`
near: -1000.0,
far: 1000.0,
// ... any other settings you want to change ...
..default()
},
..default()
},
MyCameraMarker,
));
A more foolproof way to go about this is to use a temporary variable, to let the bundle do its thing, and then mutate whatever you want. This way, you don't have to worry about the exact values or getting anything wrong:
let mut camera_bundle = Camera2dBundle::default();
// change the settings we want to change:
camera_bundle.projection.scale = 2.0;
camera_bundle.transform.rotate_z(30f32.to_radians());
// ...
commands.spawn((
camera_bundle,
MyCameraMarker,
));
Scaling Mode
You can set the ScalingMode
according to how you want to
handle window size / resolution.
The default for Bevy 2D cameras is to have 1 screen pixel correspond to 1 world unit, thus allowing you to think of everything in "pixels". When the window is resized, that causes more or less content to be seen.
If you want to keep this window resizing behavior, but change the mapping of screen
pixels to world units, use ScalingMode::WindowSize(x)
with a value other than 1.0
.
The value represents the number of screen pixels for one world unit.
If, instead, you want to always fit the same amount of content
on-screen, regardless of resolution, you should use something like
ScalingMode::FixedVertical
or ScalingMode::AutoMax
. Then, you can directly
specify how many units you want to display on-screen, and your content will
be upscaled/downscaled as appropriate to fit the window size.
use bevy::render::camera::ScalingMode;
let mut my_2d_camera_bundle = Camera2dBundle::default();
// For this example, let's make the screen/window height correspond to
// 1600.0 world units. The width will depend on the aspect ratio.
my_2d_camera_bundle.projection.scaling_mode = ScalingMode::FixedVertical(1600.0);
my_2d_camera_bundle.transform = Transform::from_xyz(100.0, 200.0, 0.0);
commands.spawn((
my_2d_camera_bundle,
MyCameraMarker,
));
Zooming
To "zoom" in 2D, you can change the orthographic projection's scale
. This
allows you to just scale everything by some factor, regardless of the
ScalingMode
behavior.
fn zoom_scale(
mut query_camera: Query<&mut OrthographicProjection, With<MyCameraMarker>>,
) {
let mut projection = query_camera.single_mut();
// zoom in
projection.scale /= 1.25;
// zoom out
projection.scale *= 1.25;
}
Alternatively, you can reconfigure the ScalingMode
. This
way you can be confident about how exactly coordinates/units map to the
screen. This also helps avoid scaling artifacts with 2D assets, especially
pixel art.
fn zoom_scalingmode(
mut query_camera: Query<&mut OrthographicProjection, With<MyCameraMarker>>,
) {
use bevy::render::camera::ScalingMode;
let mut projection = query_camera.single_mut();
// 4 screen pixels to world/game pixel
projection.scaling_mode = ScalingMode::WindowSize(4.0);
// 6 screen pixels to world/game pixel
projection.scaling_mode = ScalingMode::WindowSize(6.0);
}
Consider having a list of predefined "zoom levels" / scale values, so that you can make sure your game always looks good.
If you are making a pixel-art game, you want to make sure the default texture filtering mode is set to Nearest (and not Linear), if you want your pixels to appear crisp instead of blurry:
fn main() {
App::new()
.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
)
// ...
.run();
}
However, when downscaling, Linear (the default) filtering is preferred for higher quality. So, for games with high-res assets, you want to leave it unchanged.