diff --git a/Cargo.toml b/Cargo.toml index cc9c26056daa9..1b39556b21e65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -488,13 +488,12 @@ category = "2D Rendering" wasm = true [[example]] -name = "pixel_perfect" -path = "examples/2d/pixel_perfect.rs" -doc-scrape-examples = true +name = "pixel_grid_snap" +path = "examples/2d/pixel_grid_snap.rs" -[package.metadata.example.pixel_perfect] -name = "Pixel Perfect" -description = "Demonstrates pixel perfect in 2d" +[package.metadata.example.pixel_grid_snap] +name = "Pixel Grid Snapping" +description = "Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D" category = "2D Rendering" wasm = true diff --git a/assets/pixel/bevy_pixel_dark.png b/assets/pixel/bevy_pixel_dark.png index 563531d0a5c95..93fe567b34bed 100644 Binary files a/assets/pixel/bevy_pixel_dark.png and b/assets/pixel/bevy_pixel_dark.png differ diff --git a/assets/pixel/bevy_pixel_light.png b/assets/pixel/bevy_pixel_light.png index f6225fe25eabb..03edd462457cf 100644 Binary files a/assets/pixel/bevy_pixel_light.png and b/assets/pixel/bevy_pixel_light.png differ diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs new file mode 100644 index 0000000000000..15e573299380a --- /dev/null +++ b/examples/2d/pixel_grid_snap.rs @@ -0,0 +1,174 @@ +//! Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D + +use bevy::{ + prelude::*, + render::{ + camera::RenderTarget, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + view::RenderLayers, + }, + sprite::MaterialMesh2dBundle, + window::WindowResized, +}; + +/// In-game resolution width. +const RES_WIDTH: u32 = 160; + +/// In-game resolution height. +const RES_HEIGHT: u32 = 90; + +/// Default render layers for pixel-perfect rendering. +/// You can skip adding this component, as this is the default. +const PIXEL_PERFECT_LAYERS: RenderLayers = RenderLayers::layer(0); + +/// Render layers for high-resolution rendering. +const HIGH_RES_LAYERS: RenderLayers = RenderLayers::layer(1); + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) + .insert_resource(Msaa::Off) + .add_systems(Startup, (setup_camera, setup_sprite, setup_mesh)) + .add_systems(Update, (rotate, fit_canvas)) + .run(); +} + +/// Low-resolution texture that contains the pixel-perfect world. +/// Canvas itself is rendered to the high-resolution world. +#[derive(Component)] +struct Canvas; + +/// Camera that renders the pixel-perfect world to the [`Canvas`]. +#[derive(Component)] +struct InGameCamera; + +/// Camera that renders the [`Canvas`] (and other graphics on [`HIGH_RES_LAYERS`]) to the screen. +#[derive(Component)] +struct OuterCamera; + +#[derive(Component)] +struct Rotate; + +fn setup_sprite(mut commands: Commands, asset_server: Res) { + // the sample sprite that will be rendered to the pixel-perfect canvas + commands.spawn(( + SpriteBundle { + texture: asset_server.load("pixel/bevy_pixel_dark.png"), + transform: Transform::from_xyz(-40., 20., 2.), + ..default() + }, + Rotate, + PIXEL_PERFECT_LAYERS, + )); + + // the sample sprite that will be rendered to the high-res "outer world" + commands.spawn(( + SpriteBundle { + texture: asset_server.load("pixel/bevy_pixel_light.png"), + transform: Transform::from_xyz(-40., -20., 2.), + ..default() + }, + Rotate, + HIGH_RES_LAYERS, + )); +} + +/// Spawns a capsule mesh on the pixel-perfect layer. +fn setup_mesh( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(( + MaterialMesh2dBundle { + mesh: meshes.add(Mesh::from(shape::Capsule::default())).into(), + transform: Transform::from_xyz(40., 0., 2.).with_scale(Vec3::splat(32.)), + material: materials.add(ColorMaterial::from(Color::BLACK)), + ..default() + }, + Rotate, + PIXEL_PERFECT_LAYERS, + )); +} + +fn setup_camera(mut commands: Commands, mut images: ResMut>) { + let canvas_size = Extent3d { + width: RES_WIDTH, + height: RES_HEIGHT, + ..default() + }; + + // this Image serves as a canvas representing the low-resolution game screen + let mut canvas = Image { + texture_descriptor: TextureDescriptor { + label: None, + size: canvas_size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..default() + }; + + // fill image.data with zeroes + canvas.resize(canvas_size); + + let image_handle = images.add(canvas); + + // this camera renders whatever is on `PIXEL_PERFECT_LAYERS` to the canvas + commands.spawn(( + Camera2dBundle { + camera: Camera { + // render before the "main pass" camera + order: -1, + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + ..default() + }, + InGameCamera, + PIXEL_PERFECT_LAYERS, + )); + + // spawn the canvas + commands.spawn(( + SpriteBundle { + texture: image_handle, + ..default() + }, + Canvas, + HIGH_RES_LAYERS, + )); + + // the "outer" camera renders whatever is on `HIGH_RES_LAYERS` to the screen. + // here, the canvas and one of the sample sprites will be rendered by this camera + commands.spawn((Camera2dBundle::default(), OuterCamera, HIGH_RES_LAYERS)); +} + +/// Rotates entities to demonstrate grid snapping. +fn rotate(time: Res