bevy/examples/2d/cpu_draw.rs
Emerson Coskey 7d40e3ec87
Migrate bevy_sprite to required components (#15489)
# Objective

Continue migration of bevy APIs to required components, following
guidance of https://hackmd.io/@bevy/required_components/

## Solution

- Make `Sprite` require `Transform` and `Visibility` and
`SyncToRenderWorld`
- move image and texture atlas handles into `Sprite`
- deprecate `SpriteBundle`
- remove engine uses of `SpriteBundle`

## Testing

ran cargo tests on bevy_sprite and tested several sprite examples.

---

## Migration Guide

Replace all uses of `SpriteBundle` with `Sprite`. There are several new
convenience constructors: `Sprite::from_image`,
`Sprite::from_atlas_image`, `Sprite::from_color`.

WARNING: use of `Handle<Image>` and `TextureAtlas` as components on
sprite entities will NO LONGER WORK. Use the fields on `Sprite` instead.
I would have removed the `Component` impls from `TextureAtlas` and
`Handle<Image>` except it is still used within ui. We should fix this
moving forward with the migration.
2024-10-09 16:17:26 +00:00

129 lines
4.6 KiB
Rust

//! Example of how to draw to a texture from the CPU.
//!
//! You can set the values of individual pixels to whatever you want.
//! Bevy provides user-friendly APIs that work with [`Color`](bevy::color::Color)
//! values and automatically perform any necessary conversions and encoding
//! into the texture's native pixel format.
use bevy::color::{color_difference::EuclideanDistance, palettes::css};
use bevy::prelude::*;
use bevy::render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
};
use rand::Rng;
const IMAGE_WIDTH: u32 = 256;
const IMAGE_HEIGHT: u32 = 256;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// In this example, we will use a fixed timestep to draw a pattern on the screen
// one pixel at a time, so the pattern will gradually emerge over time, and
// the speed at which it appears is not tied to the framerate.
// Let's make the fixed update very fast, so it doesn't take too long. :)
.insert_resource(Time::<Fixed>::from_hz(1024.0))
.add_systems(Startup, setup)
.add_systems(FixedUpdate, draw)
.run();
}
/// Store the image handle that we will draw to, here.
#[derive(Resource)]
struct MyProcGenImage(Handle<Image>);
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
// spawn a camera
commands.spawn(Camera2d);
// create an image that we are going to draw into
let mut image = Image::new_fill(
// 2D image of size 256x256
Extent3d {
width: IMAGE_WIDTH,
height: IMAGE_HEIGHT,
depth_or_array_layers: 1,
},
TextureDimension::D2,
// Initialize it with a beige color
&(css::BEIGE.to_u8_array()),
// Use the same encoding as the color we set
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
);
// to make it extra fancy, we can set the Alpha of each pixel
// so that it fades out in a circular fashion
for y in 0..IMAGE_HEIGHT {
for x in 0..IMAGE_WIDTH {
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
let r = Vec2::new(x as f32, y as f32).distance(center);
let a = 1.0 - (r / max_radius).clamp(0.0, 1.0);
// here we will set the A value by accessing the raw data bytes
// (it is the 4th byte of each pixel, as per our `TextureFormat`)
// find our pixel by its coordinates
let pixel_bytes = image.pixel_bytes_mut(UVec3::new(x, y, 0)).unwrap();
// convert our f32 to u8
pixel_bytes[3] = (a * u8::MAX as f32) as u8;
}
}
// add it to Bevy's assets, so it can be used for rendering
// this will give us a handle we can use
// (to display it in a sprite, or as part of UI, etc.)
let handle = images.add(image);
// create a sprite entity using our image
commands.spawn(Sprite::from_image(handle.clone()));
commands.insert_resource(MyProcGenImage(handle));
}
/// Every fixed update tick, draw one more pixel to make a spiral pattern
fn draw(
my_handle: Res<MyProcGenImage>,
mut images: ResMut<Assets<Image>>,
// used to keep track of where we are
mut i: Local<u32>,
mut draw_color: Local<Color>,
) {
let mut rng = rand::thread_rng();
if *i == 0 {
// Generate a random color on first run.
*draw_color = Color::linear_rgb(rng.gen(), rng.gen(), rng.gen());
}
// Get the image from Bevy's asset storage.
let image = images.get_mut(&my_handle.0).expect("Image not found");
// Compute the position of the pixel to draw.
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
let rot_speed = 0.0123;
let period = 0.12345;
let r = ops::sin(*i as f32 * period) * max_radius;
let xy = Vec2::from_angle(*i as f32 * rot_speed) * r + center;
let (x, y) = (xy.x as u32, xy.y as u32);
// Get the old color of that pixel.
let old_color = image.get_color_at(x, y).unwrap();
// If the old color is our current color, change our drawing color.
let tolerance = 1.0 / 255.0;
if old_color.distance(&draw_color) <= tolerance {
*draw_color = Color::linear_rgb(rng.gen(), rng.gen(), rng.gen());
}
// Set the new color, but keep old alpha value from image.
image
.set_color_at(x, y, draw_color.with_alpha(old_color.alpha()))
.unwrap();
*i += 1;
}