Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

86
vendor/bevy/examples/2d/2d_shapes.rs vendored Normal file
View File

@@ -0,0 +1,86 @@
//! Shows how to render simple primitive shapes with a single color.
//!
//! You can toggle wireframes with the space bar except on wasm. Wasm does not support
//! `POLYGON_MODE_LINE` on the gpu.
use bevy::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use bevy::sprite::{Wireframe2dConfig, Wireframe2dPlugin};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
#[cfg(not(target_arch = "wasm32"))]
Wireframe2dPlugin::default(),
))
.add_systems(Startup, setup);
#[cfg(not(target_arch = "wasm32"))]
app.add_systems(Update, toggle_wireframe);
app.run();
}
const X_EXTENT: f32 = 900.;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
let shapes = [
meshes.add(Circle::new(50.0)),
meshes.add(CircularSector::new(50.0, 1.0)),
meshes.add(CircularSegment::new(50.0, 1.25)),
meshes.add(Ellipse::new(25.0, 50.0)),
meshes.add(Annulus::new(25.0, 50.0)),
meshes.add(Capsule2d::new(25.0, 50.0)),
meshes.add(Rhombus::new(75.0, 100.0)),
meshes.add(Rectangle::new(50.0, 100.0)),
meshes.add(RegularPolygon::new(50.0, 6)),
meshes.add(Triangle2d::new(
Vec2::Y * 50.0,
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
)),
];
let num_shapes = shapes.len();
for (i, shape) in shapes.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
commands.spawn((
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
0.0,
0.0,
),
));
}
#[cfg(not(target_arch = "wasm32"))]
commands.spawn((
Text::new("Press space to toggle wireframes"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
}
#[cfg(not(target_arch = "wasm32"))]
fn toggle_wireframe(
mut wireframe_config: ResMut<Wireframe2dConfig>,
keyboard: Res<ButtonInput<KeyCode>>,
) {
if keyboard.just_pressed(KeyCode::Space) {
wireframe_config.global = !wireframe_config.global;
}
}

View File

@@ -0,0 +1,191 @@
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method with a dynamic viewport and camera.
use bevy::{
color::palettes::{
basic::WHITE,
css::{GREEN, RED},
},
math::ops::powf,
prelude::*,
render::camera::Viewport,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(FixedUpdate, controls)
.add_systems(
PostUpdate,
draw_cursor.after(TransformSystem::TransformPropagate),
)
.run();
}
fn draw_cursor(
camera_query: Single<(&Camera, &GlobalTransform)>,
window: Query<&Window>,
mut gizmos: Gizmos,
) {
let (camera, camera_transform) = *camera_query;
let Ok(window) = window.single() else {
return;
};
let Some(cursor_position) = window.cursor_position() else {
return;
};
// Calculate a world position based on the cursor's position.
let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
return;
};
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
else {
return;
};
let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy()) else {
return;
};
gizmos.circle_2d(world_pos, 10., WHITE);
// Should be the same as world_pos
gizmos.circle_2d(world_check, 8., RED);
}
fn controls(
mut camera_query: Query<(&mut Camera, &mut Transform, &mut Projection)>,
window: Query<&Window>,
input: Res<ButtonInput<KeyCode>>,
time: Res<Time<Fixed>>,
) {
let Ok(window) = window.single() else {
return;
};
let Ok((mut camera, mut transform, mut projection)) = camera_query.single_mut() else {
return;
};
let fspeed = 600.0 * time.delta_secs();
let uspeed = fspeed as u32;
let window_size = window.resolution.physical_size();
// Camera movement controls
if input.pressed(KeyCode::ArrowUp) {
transform.translation.y += fspeed;
}
if input.pressed(KeyCode::ArrowDown) {
transform.translation.y -= fspeed;
}
if input.pressed(KeyCode::ArrowLeft) {
transform.translation.x -= fspeed;
}
if input.pressed(KeyCode::ArrowRight) {
transform.translation.x += fspeed;
}
// Camera zoom controls
if let Projection::Orthographic(projection2d) = &mut *projection {
if input.pressed(KeyCode::Comma) {
projection2d.scale *= powf(4.0f32, time.delta_secs());
}
if input.pressed(KeyCode::Period) {
projection2d.scale *= powf(0.25f32, time.delta_secs());
}
}
if let Some(viewport) = camera.viewport.as_mut() {
// Viewport movement controls
if input.pressed(KeyCode::KeyW) {
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(uspeed);
}
if input.pressed(KeyCode::KeyS) {
viewport.physical_position.y += uspeed;
}
if input.pressed(KeyCode::KeyA) {
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(uspeed);
}
if input.pressed(KeyCode::KeyD) {
viewport.physical_position.x += uspeed;
}
// Bound viewport position so it doesn't go off-screen
viewport.physical_position = viewport
.physical_position
.min(window_size - viewport.physical_size);
// Viewport size controls
if input.pressed(KeyCode::KeyI) {
viewport.physical_size.y = viewport.physical_size.y.saturating_sub(uspeed);
}
if input.pressed(KeyCode::KeyK) {
viewport.physical_size.y += uspeed;
}
if input.pressed(KeyCode::KeyJ) {
viewport.physical_size.x = viewport.physical_size.x.saturating_sub(uspeed);
}
if input.pressed(KeyCode::KeyL) {
viewport.physical_size.x += uspeed;
}
// Bound viewport size so it doesn't go off-screen
viewport.physical_size = viewport
.physical_size
.min(window_size - viewport.physical_position)
.max(UVec2::new(20, 20));
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
window: Single<&Window>,
) {
let window_size = window.resolution.physical_size().as_vec2();
// Initialize centered, non-window-filling viewport
commands.spawn((
Camera2d,
Camera {
viewport: Some(Viewport {
physical_position: (window_size * 0.125).as_uvec2(),
physical_size: (window_size * 0.75).as_uvec2(),
..default()
}),
..default()
},
));
// Create a minimal UI explaining how to interact with the example
commands.spawn((
Text::new(
"Move the mouse to see the circle follow your cursor.\n\
Use the arrow keys to move the camera.\n\
Use the comma and period keys to zoom in and out.\n\
Use the WASD keys to move the viewport.\n\
Use the IJKL keys to resize the viewport.",
),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
// Add mesh to make camera movement visible
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(40.0, 20.0))),
MeshMaterial2d(materials.add(Color::from(GREEN))),
));
// Add background to visualize viewport bounds
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
));
}

214
vendor/bevy/examples/2d/bloom_2d.rs vendored Normal file
View File

@@ -0,0 +1,214 @@
//! Illustrates bloom post-processing in 2d.
use bevy::{
core_pipeline::{
bloom::{Bloom, BloomCompositeMode},
tonemapping::{DebandDither, Tonemapping},
},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, update_bloom_settings)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
Camera2d,
Camera {
hdr: true, // 1. HDR is required for bloom
clear_color: ClearColorConfig::Custom(Color::BLACK),
..default()
},
Tonemapping::TonyMcMapface, // 2. Using a tonemapper that desaturates to white is recommended
Bloom::default(), // 3. Enable bloom for the camera
DebandDither::Enabled, // Optional: bloom causes gradients which cause banding
));
// Sprite
commands.spawn(Sprite {
image: asset_server.load("branding/bevy_bird_dark.png"),
color: Color::srgb(5.0, 5.0, 5.0), // 4. Put something bright in a dark environment to see the effect
custom_size: Some(Vec2::splat(160.0)),
..default()
});
// Circle mesh
commands.spawn((
Mesh2d(meshes.add(Circle::new(100.))),
// 4. Put something bright in a dark environment to see the effect
MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
Transform::from_translation(Vec3::new(-200., 0., 0.)),
));
// Hexagon mesh
commands.spawn((
Mesh2d(meshes.add(RegularPolygon::new(100., 6))),
// 4. Put something bright in a dark environment to see the effect
MeshMaterial2d(materials.add(Color::srgb(6.25, 9.4, 9.1))),
Transform::from_translation(Vec3::new(200., 0., 0.)),
));
// UI
commands.spawn((
Text::default(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
}
// ------------------------------------------------------------------------------------------------
fn update_bloom_settings(
camera: Single<(Entity, &Tonemapping, Option<&mut Bloom>), With<Camera>>,
mut text: Single<&mut Text>,
mut commands: Commands,
keycode: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
let (camera_entity, tonemapping, bloom) = camera.into_inner();
match bloom {
Some(mut bloom) => {
text.0 = "Bloom (Toggle: Space)\n".to_string();
text.push_str(&format!("(Q/A) Intensity: {}\n", bloom.intensity));
text.push_str(&format!(
"(W/S) Low-frequency boost: {}\n",
bloom.low_frequency_boost
));
text.push_str(&format!(
"(E/D) Low-frequency boost curvature: {}\n",
bloom.low_frequency_boost_curvature
));
text.push_str(&format!(
"(R/F) High-pass frequency: {}\n",
bloom.high_pass_frequency
));
text.push_str(&format!(
"(T/G) Mode: {}\n",
match bloom.composite_mode {
BloomCompositeMode::EnergyConserving => "Energy-conserving",
BloomCompositeMode::Additive => "Additive",
}
));
text.push_str(&format!("(Y/H) Threshold: {}\n", bloom.prefilter.threshold));
text.push_str(&format!(
"(U/J) Threshold softness: {}\n",
bloom.prefilter.threshold_softness
));
text.push_str(&format!("(I/K) Horizontal Scale: {}\n", bloom.scale.x));
if keycode.just_pressed(KeyCode::Space) {
commands.entity(camera_entity).remove::<Bloom>();
}
let dt = time.delta_secs();
if keycode.pressed(KeyCode::KeyA) {
bloom.intensity -= dt / 10.0;
}
if keycode.pressed(KeyCode::KeyQ) {
bloom.intensity += dt / 10.0;
}
bloom.intensity = bloom.intensity.clamp(0.0, 1.0);
if keycode.pressed(KeyCode::KeyS) {
bloom.low_frequency_boost -= dt / 10.0;
}
if keycode.pressed(KeyCode::KeyW) {
bloom.low_frequency_boost += dt / 10.0;
}
bloom.low_frequency_boost = bloom.low_frequency_boost.clamp(0.0, 1.0);
if keycode.pressed(KeyCode::KeyD) {
bloom.low_frequency_boost_curvature -= dt / 10.0;
}
if keycode.pressed(KeyCode::KeyE) {
bloom.low_frequency_boost_curvature += dt / 10.0;
}
bloom.low_frequency_boost_curvature =
bloom.low_frequency_boost_curvature.clamp(0.0, 1.0);
if keycode.pressed(KeyCode::KeyF) {
bloom.high_pass_frequency -= dt / 10.0;
}
if keycode.pressed(KeyCode::KeyR) {
bloom.high_pass_frequency += dt / 10.0;
}
bloom.high_pass_frequency = bloom.high_pass_frequency.clamp(0.0, 1.0);
if keycode.pressed(KeyCode::KeyG) {
bloom.composite_mode = BloomCompositeMode::Additive;
}
if keycode.pressed(KeyCode::KeyT) {
bloom.composite_mode = BloomCompositeMode::EnergyConserving;
}
if keycode.pressed(KeyCode::KeyH) {
bloom.prefilter.threshold -= dt;
}
if keycode.pressed(KeyCode::KeyY) {
bloom.prefilter.threshold += dt;
}
bloom.prefilter.threshold = bloom.prefilter.threshold.max(0.0);
if keycode.pressed(KeyCode::KeyJ) {
bloom.prefilter.threshold_softness -= dt / 10.0;
}
if keycode.pressed(KeyCode::KeyU) {
bloom.prefilter.threshold_softness += dt / 10.0;
}
bloom.prefilter.threshold_softness = bloom.prefilter.threshold_softness.clamp(0.0, 1.0);
if keycode.pressed(KeyCode::KeyK) {
bloom.scale.x -= dt * 2.0;
}
if keycode.pressed(KeyCode::KeyI) {
bloom.scale.x += dt * 2.0;
}
bloom.scale.x = bloom.scale.x.clamp(0.0, 16.0);
}
None => {
text.0 = "Bloom: Off (Toggle: Space)\n".to_string();
if keycode.just_pressed(KeyCode::Space) {
commands.entity(camera_entity).insert(Bloom::default());
}
}
}
text.push_str(&format!("(O) Tonemapping: {:?}\n", tonemapping));
if keycode.just_pressed(KeyCode::KeyO) {
commands
.entity(camera_entity)
.insert(next_tonemap(tonemapping));
}
}
/// Get the next Tonemapping algorithm
fn next_tonemap(tonemapping: &Tonemapping) -> Tonemapping {
match tonemapping {
Tonemapping::None => Tonemapping::AcesFitted,
Tonemapping::AcesFitted => Tonemapping::AgX,
Tonemapping::AgX => Tonemapping::BlenderFilmic,
Tonemapping::BlenderFilmic => Tonemapping::Reinhard,
Tonemapping::Reinhard => Tonemapping::ReinhardLuminance,
Tonemapping::ReinhardLuminance => Tonemapping::SomewhatBoringDisplayTransform,
Tonemapping::SomewhatBoringDisplayTransform => Tonemapping::TonyMcMapface,
Tonemapping::TonyMcMapface => Tonemapping::None,
}
}

144
vendor/bevy/examples/2d/cpu_draw.rs vendored Normal file
View File

@@ -0,0 +1,144 @@
//! 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, SeedableRng};
use rand_chacha::ChaCha8Rng;
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>);
#[derive(Resource)]
struct SeededRng(ChaCha8Rng);
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
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));
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
commands.insert_resource(SeededRng(seeded_rng));
}
/// 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>,
mut seeded_rng: ResMut<SeededRng>,
) {
if *i == 0 {
// Generate a random color on first run.
*draw_color = Color::linear_rgb(
seeded_rng.0.r#gen(),
seeded_rng.0.r#gen(),
seeded_rng.0.r#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(
seeded_rng.0.r#gen(),
seeded_rng.0.r#gen(),
seeded_rng.0.r#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;
}

View File

@@ -0,0 +1,96 @@
//! Renders a glTF mesh in 2D with a custom vertex attribute.
use bevy::{
gltf::GltfPlugin,
prelude::*,
reflect::TypePath,
render::{
mesh::{MeshVertexAttribute, MeshVertexBufferLayoutRef},
render_resource::*,
},
sprite::{Material2d, Material2dKey, Material2dPlugin},
};
/// This example uses a shader source file from the assets subdirectory
const SHADER_ASSET_PATH: &str = "shaders/custom_gltf_2d.wgsl";
/// This vertex attribute supplies barycentric coordinates for each triangle.
///
/// Each component of the vector corresponds to one corner of a triangle. It's
/// equal to 1.0 in that corner and 0.0 in the other two. Hence, its value in
/// the fragment shader indicates proximity to a corner or the opposite edge.
const ATTRIBUTE_BARYCENTRIC: MeshVertexAttribute =
MeshVertexAttribute::new("Barycentric", 2137464976, VertexFormat::Float32x3);
fn main() {
App::new()
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.0 / 5.0f32,
..default()
})
.add_plugins((
DefaultPlugins.set(
GltfPlugin::default()
// Map a custom glTF attribute name to a `MeshVertexAttribute`.
// The glTF file used here has an attribute name with *two*
// underscores: __BARYCENTRIC
// One is stripped to do the comparison here.
.add_custom_vertex_attribute("_BARYCENTRIC", ATTRIBUTE_BARYCENTRIC),
),
Material2dPlugin::<CustomMaterial>::default(),
))
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<CustomMaterial>>,
) {
// Add a mesh loaded from a glTF file. This mesh has data for `ATTRIBUTE_BARYCENTRIC`.
let mesh = asset_server.load(
GltfAssetLabel::Primitive {
mesh: 0,
primitive: 0,
}
.from_asset("models/barycentric/barycentric.gltf"),
);
commands.spawn((
Mesh2d(mesh),
MeshMaterial2d(materials.add(CustomMaterial {})),
Transform::from_scale(150.0 * Vec3::ONE),
));
commands.spawn(Camera2d);
}
/// This custom material uses barycentric coordinates from
/// `ATTRIBUTE_BARYCENTRIC` to shade a white border around each triangle. The
/// thickness of the border is animated using the global time shader uniform.
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct CustomMaterial {}
impl Material2d for CustomMaterial {
fn vertex_shader() -> ShaderRef {
SHADER_ASSET_PATH.into()
}
fn fragment_shader() -> ShaderRef {
SHADER_ASSET_PATH.into()
}
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
_key: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
let vertex_layout = layout.0.get_layout(&[
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
ATTRIBUTE_BARYCENTRIC.at_shader_location(2),
])?;
descriptor.vertex.buffers = vec![vertex_layout];
Ok(())
}
}

24
vendor/bevy/examples/2d/mesh2d.rs vendored Normal file
View File

@@ -0,0 +1,24 @@
//! Shows how to render a polygonal [`Mesh`], generated from a [`Rectangle`] primitive, in a 2D scene.
use bevy::{color::palettes::basic::PURPLE, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
commands.spawn((
Mesh2d(meshes.add(Rectangle::default())),
MeshMaterial2d(materials.add(Color::from(PURPLE))),
Transform::default().with_scale(Vec3::splat(128.)),
));
}

View File

@@ -0,0 +1,97 @@
//! This example is used to test how transforms interact with alpha modes for [`Mesh2d`] entities with a [`MeshMaterial2d`].
//! This makes sure the depth buffer is correctly being used for opaque and transparent 2d meshes
use bevy::{
color::palettes::css::{BLUE, GREEN, WHITE},
prelude::*,
sprite::AlphaMode2d,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
let texture_handle = asset_server.load("branding/icon.png");
let mesh_handle = meshes.add(Rectangle::from_size(Vec2::splat(256.0)));
// opaque
// Each sprite should be square with the transparent parts being completely black
// The blue sprite should be on top with the white and green one behind it
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: WHITE.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-400.0, 0.0, 0.0),
));
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: BLUE.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-300.0, 0.0, 1.0),
));
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: GREEN.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-200.0, 0.0, -1.0),
));
// Test the interaction between opaque/mask and transparent meshes
// The white sprite should be:
// - only the icon is opaque but background is transparent
// - on top of the green sprite
// - behind the blue sprite
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: WHITE.into(),
alpha_mode: AlphaMode2d::Mask(0.5),
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(200.0, 0.0, 0.0),
));
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: BLUE.with_alpha(0.7).into(),
alpha_mode: AlphaMode2d::Blend,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(300.0, 0.0, 1.0),
));
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial {
color: GREEN.with_alpha(0.7).into(),
alpha_mode: AlphaMode2d::Blend,
texture: Some(texture_handle),
..default()
})),
Transform::from_xyz(400.0, 0.0, -1.0),
));
}

120
vendor/bevy/examples/2d/mesh2d_arcs.rs vendored Normal file
View File

@@ -0,0 +1,120 @@
//! Demonstrates UV mappings of the [`CircularSector`] and [`CircularSegment`] primitives.
//!
//! Also draws the bounding boxes and circles of the primitives.
use std::f32::consts::FRAC_PI_2;
use bevy::{
color::palettes::css::{BLUE, GRAY, RED},
math::{
bounding::{Bounded2d, BoundingVolume},
Isometry2d,
},
prelude::*,
render::mesh::{CircularMeshUvMode, CircularSectorMeshBuilder, CircularSegmentMeshBuilder},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(
Update,
(
draw_bounds::<CircularSector>,
draw_bounds::<CircularSegment>,
),
)
.run();
}
#[derive(Component, Debug)]
struct DrawBounds<Shape: Bounded2d + Send + Sync + 'static>(Shape);
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let material = materials.add(asset_server.load("branding/icon.png"));
commands.spawn((
Camera2d,
Camera {
clear_color: ClearColorConfig::Custom(GRAY.into()),
..default()
},
));
const NUM_SLICES: i32 = 8;
const SPACING_X: f32 = 100.0;
const OFFSET_X: f32 = SPACING_X * (NUM_SLICES - 1) as f32 / 2.0;
// This draws NUM_SLICES copies of the Bevy logo as circular sectors and segments,
// with successively larger angles up to a complete circle.
for i in 0..NUM_SLICES {
let fraction = (i + 1) as f32 / NUM_SLICES as f32;
let sector = CircularSector::from_turns(40.0, fraction);
// We want to rotate the circular sector so that the sectors appear clockwise from north.
// We must rotate it both in the Transform and in the mesh's UV mappings.
let sector_angle = -sector.half_angle();
let sector_mesh =
CircularSectorMeshBuilder::new(sector).uv_mode(CircularMeshUvMode::Mask {
angle: sector_angle,
});
commands.spawn((
Mesh2d(meshes.add(sector_mesh)),
MeshMaterial2d(material.clone()),
Transform {
translation: Vec3::new(SPACING_X * i as f32 - OFFSET_X, 50.0, 0.0),
rotation: Quat::from_rotation_z(sector_angle),
..default()
},
DrawBounds(sector),
));
let segment = CircularSegment::from_turns(40.0, fraction);
// For the circular segment, we will draw Bevy charging forward, which requires rotating the
// shape and texture by 90 degrees.
//
// Note that this may be unintuitive; it may feel like we should rotate the texture by the
// opposite angle to preserve the orientation of Bevy. But the angle is not the angle of the
// texture itself, rather it is the angle at which the vertices are mapped onto the texture.
// so it is the negative of what you might otherwise expect.
let segment_angle = -FRAC_PI_2;
let segment_mesh =
CircularSegmentMeshBuilder::new(segment).uv_mode(CircularMeshUvMode::Mask {
angle: -segment_angle,
});
commands.spawn((
Mesh2d(meshes.add(segment_mesh)),
MeshMaterial2d(material.clone()),
Transform {
translation: Vec3::new(SPACING_X * i as f32 - OFFSET_X, -50.0, 0.0),
rotation: Quat::from_rotation_z(segment_angle),
..default()
},
DrawBounds(segment),
));
}
}
fn draw_bounds<Shape: Bounded2d + Send + Sync + 'static>(
q: Query<(&DrawBounds<Shape>, &GlobalTransform)>,
mut gizmos: Gizmos,
) {
for (shape, transform) in &q {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
let translation = translation.truncate();
let rotation = rotation.to_euler(EulerRot::XYZ).2;
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
let aabb = shape.0.aabb_2d(isometry);
gizmos.rect_2d(aabb.center(), aabb.half_size() * 2.0, RED);
let bounding_circle = shape.0.bounding_circle(isometry);
gizmos.circle_2d(bounding_circle.center, bounding_circle.radius(), BLUE);
}
}

432
vendor/bevy/examples/2d/mesh2d_manual.rs vendored Normal file
View File

@@ -0,0 +1,432 @@
//! This example shows how to manually render 2d items using "mid level render apis" with a custom
//! pipeline for 2d meshes.
//! It doesn't use the [`Material2d`] abstraction, but changes the vertex buffer to include vertex color.
//! Check out the "mesh2d" example for simpler / higher level 2d meshes.
//!
//! [`Material2d`]: bevy::sprite::Material2d
use bevy::{
asset::weak_handle,
color::palettes::basic::YELLOW,
core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
math::{ops, FloatOrd},
prelude::*,
render::{
mesh::{Indices, MeshVertexAttribute, RenderMesh},
render_asset::{RenderAssetUsages, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::{
BlendState, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PipelineCache,
PolygonMode, PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor,
SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState,
TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
},
sync_component::SyncComponentPlugin,
sync_world::{MainEntityHashMap, RenderEntity},
view::{ExtractedView, RenderVisibleEntities, ViewTarget},
Extract, Render, RenderApp, RenderSet,
},
sprite::{
extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey,
Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, SetMesh2dBindGroup,
SetMesh2dViewBindGroup,
},
};
use std::f32::consts::PI;
fn main() {
App::new()
.add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
.add_systems(Startup, star)
.run();
}
fn star(
mut commands: Commands,
// We will add a new Mesh for the star being created
mut meshes: ResMut<Assets<Mesh>>,
) {
// Let's define the mesh for the object we want to draw: a nice star.
// We will specify here what kind of topology is used to define the mesh,
// that is, how triangles are built from the vertices. We will use a
// triangle list, meaning that each vertex of the triangle has to be
// specified. We set `RenderAssetUsages::RENDER_WORLD`, meaning this mesh
// will not be accessible in future frames from the `meshes` resource, in
// order to save on memory once it has been uploaded to the GPU.
let mut star = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
);
// Vertices need to have a position attribute. We will use the following
// vertices (I hope you can spot the star in the schema).
//
// 1
//
// 10 2
// 9 0 3
// 8 4
// 6
// 7 5
//
// These vertices are specified in 3D space.
let mut v_pos = vec![[0.0, 0.0, 0.0]];
for i in 0..10 {
// The angle between each vertex is 1/10 of a full rotation.
let a = i as f32 * PI / 5.0;
// The radius of inner vertices (even indices) is 100. For outer vertices (odd indices) it's 200.
let r = (1 - i % 2) as f32 * 100.0 + 100.0;
// Add the vertex position.
v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
}
// Set the position attribute
star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
// And a RGB color attribute as well. A built-in `Mesh::ATTRIBUTE_COLOR` exists, but we
// use a custom vertex attribute here for demonstration purposes.
let mut v_color: Vec<u32> = vec![LinearRgba::BLACK.as_u32()];
v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]);
star.insert_attribute(
MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32),
v_color,
);
// Now, we specify the indices of the vertex that are going to compose the
// triangles in our star. Vertices in triangles have to be specified in CCW
// winding (that will be the front face, colored). Since we are using
// triangle list, we will specify each triangle as 3 vertices
// First triangle: 0, 2, 1
// Second triangle: 0, 3, 2
// Third triangle: 0, 4, 3
// etc
// Last triangle: 0, 1, 10
let mut indices = vec![0, 1, 10];
for i in 2..=10 {
indices.extend_from_slice(&[0, i, i - 1]);
}
star.insert_indices(Indices::U32(indices));
// We can now spawn the entities for the star and the camera
commands.spawn((
// We use a marker component to identify the custom colored meshes
ColoredMesh2d,
// The `Handle<Mesh>` needs to be wrapped in a `Mesh2d` for 2D rendering
Mesh2d(meshes.add(star)),
));
commands.spawn(Camera2d);
}
/// A marker component for colored 2d meshes
#[derive(Component, Default)]
pub struct ColoredMesh2d;
/// Custom pipeline for 2d meshes with vertex colors
#[derive(Resource)]
pub struct ColoredMesh2dPipeline {
/// This pipeline wraps the standard [`Mesh2dPipeline`]
mesh2d_pipeline: Mesh2dPipeline,
}
impl FromWorld for ColoredMesh2dPipeline {
fn from_world(world: &mut World) -> Self {
Self {
mesh2d_pipeline: Mesh2dPipeline::from_world(world),
}
}
}
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
type Key = Mesh2dPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
// Customize how to store the meshes' vertex attributes in the vertex buffer
// Our meshes only have position and color
let formats = vec![
// Position
VertexFormat::Float32x3,
// Color
VertexFormat::Uint32,
];
let vertex_layout =
VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
let format = match key.contains(Mesh2dPipelineKey::HDR) {
true => ViewTarget::TEXTURE_FORMAT_HDR,
false => TextureFormat::bevy_default(),
};
RenderPipelineDescriptor {
vertex: VertexState {
// Use our custom shader
shader: COLORED_MESH2D_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: vec![],
// Use our custom vertex buffer
buffers: vec![vertex_layout],
},
fragment: Some(FragmentState {
// Use our custom shader
shader: COLORED_MESH2D_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
// Use the two standard uniforms for 2d meshes
layout: vec![
// Bind group 0 is the view uniform
self.mesh2d_pipeline.view_layout.clone(),
// Bind group 1 is the mesh uniform
self.mesh2d_pipeline.mesh_layout.clone(),
],
push_constant_ranges: vec![],
primitive: PrimitiveState {
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
topology: key.primitive_topology(),
strip_index_format: None,
},
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::GreaterEqual,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("colored_mesh2d_pipeline".into()),
zero_initialize_workgroup_memory: false,
}
}
}
// This specifies how to render a colored 2d mesh
type DrawColoredMesh2d = (
// Set the pipeline
SetItemPipeline,
// Set the view uniform as bind group 0
SetMesh2dViewBindGroup<0>,
// Set the mesh uniform as bind group 1
SetMesh2dBindGroup<1>,
// Draw the mesh
DrawMesh2d,
);
// The custom shader can be inline like here, included from another file at build time
// using `include_str!()`, or loaded like any other asset with `asset_server.load()`.
const COLORED_MESH2D_SHADER: &str = r"
// Import the standard 2d mesh uniforms and set their bind groups
#import bevy_sprite::mesh2d_functions
// The structure of the vertex buffer is as specified in `specialize()`
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
@location(1) color: u32,
};
struct VertexOutput {
// The vertex shader must set the on-screen position of the vertex
@builtin(position) clip_position: vec4<f32>,
// We pass the vertex color to the fragment shader in location 0
@location(0) color: vec4<f32>,
};
/// Entry point for the vertex shader
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
// Project the world position of the mesh into screen position
let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
// Unpack the `u32` from the vertex buffer into the `vec4<f32>` used by the fragment shader
out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
return out;
}
// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
struct FragmentInput {
// The color is interpolated between vertices by default
@location(0) color: vec4<f32>,
};
/// Entry point for the fragment shader
@fragment
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
return in.color;
}
";
/// Plugin that renders [`ColoredMesh2d`]s
pub struct ColoredMesh2dPlugin;
/// Handle to the custom shader with a unique random ID
pub const COLORED_MESH2D_SHADER_HANDLE: Handle<Shader> =
weak_handle!("f48b148f-7373-4638-9900-392b3b3ccc66");
/// Our custom pipeline needs its own instance storage
#[derive(Resource, Deref, DerefMut, Default)]
pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
impl Plugin for ColoredMesh2dPlugin {
fn build(&self, app: &mut App) {
// Load our custom shader
let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
shaders.insert(
&COLORED_MESH2D_SHADER_HANDLE,
Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()),
);
app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
// Register our custom draw function, and add our render systems
app.get_sub_app_mut(RenderApp)
.unwrap()
.add_render_command::<Transparent2d, DrawColoredMesh2d>()
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
.init_resource::<RenderColoredMesh2dInstances>()
.add_systems(
ExtractSchedule,
extract_colored_mesh2d.after(extract_mesh2d),
)
.add_systems(Render, queue_colored_mesh2d.in_set(RenderSet::QueueMeshes));
}
fn finish(&self, app: &mut App) {
// Register our custom pipeline
app.get_sub_app_mut(RenderApp)
.unwrap()
.init_resource::<ColoredMesh2dPipeline>();
}
}
/// Extract the [`ColoredMesh2d`] marker component into the render app
pub fn extract_colored_mesh2d(
mut commands: Commands,
mut previous_len: Local<usize>,
// When extracting, you must use `Extract` to mark the `SystemParam`s
// which should be taken from the main world.
query: Extract<
Query<
(
Entity,
RenderEntity,
&ViewVisibility,
&GlobalTransform,
&Mesh2d,
),
With<ColoredMesh2d>,
>,
>,
mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, render_entity, view_visibility, transform, handle) in &query {
if !view_visibility.get() {
continue;
}
let transforms = Mesh2dTransforms {
world_from_local: (&transform.affine()).into(),
flags: MeshFlags::empty().bits(),
};
values.push((render_entity, ColoredMesh2d));
render_mesh_instances.insert(
entity.into(),
RenderMesh2dInstance {
mesh_asset_id: handle.0.id(),
transforms,
material_bind_group_id: Material2dBindGroupId::default(),
automatic_batching: false,
tag: 0,
},
);
}
*previous_len = values.len();
commands.try_insert_batch(values);
}
/// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function
pub fn queue_colored_mesh2d(
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
pipeline_cache: Res<PipelineCache>,
render_meshes: Res<RenderAssets<RenderMesh>>,
render_mesh_instances: Res<RenderColoredMesh2dInstances>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
) {
if render_mesh_instances.is_empty() {
return;
}
// Iterate each view (a camera is a view)
for (visible_entities, view, msaa) in &views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let draw_colored_mesh2d = transparent_draw_functions.read().id::<DrawColoredMesh2d>();
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
// Queue all entities visible to that view
for (render_entity, visible_entity) in visible_entities.iter::<Mesh2d>() {
if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
let mesh2d_handle = mesh_instance.mesh_asset_id;
let mesh2d_transforms = &mesh_instance.transforms;
// Get our specialized pipeline
let mut mesh2d_key = mesh_key;
let Some(mesh) = render_meshes.get(mesh2d_handle) else {
continue;
};
mesh2d_key |= Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());
let pipeline_id =
pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
let mesh_z = mesh2d_transforms.world_from_local.translation.z;
transparent_phase.add(Transparent2d {
entity: (*render_entity, *visible_entity),
draw_function: draw_colored_mesh2d,
pipeline: pipeline_id,
// The 2d render items are sorted according to their z value before rendering,
// in order to get correct transparency
sort_key: FloatOrd(mesh_z),
// This material is not batched
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: mesh.indexed(),
});
}
}
}
}

View File

@@ -0,0 +1,107 @@
//! By default Bevy loads images to textures that clamps the image to the edges
//! This example shows how to configure it to repeat the image instead.
use bevy::{
audio::AudioPlugin,
image::{ImageAddressMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor},
math::Affine2,
prelude::*,
};
/// How much to move some rectangles away from the center
const RECTANGLE_OFFSET: f32 = 250.0;
/// Length of the sides of the rectangle
const RECTANGLE_SIDE: f32 = 200.;
/// How much to move the label away from the rectangle
const LABEL_OFFSET: f32 = (RECTANGLE_SIDE / 2.) + 25.;
fn main() {
App::new()
.add_plugins(DefaultPlugins.build().disable::<AudioPlugin>())
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// #11111: We use a duplicated image so that it can be load with and without
// settings
let image_with_default_sampler =
asset_server.load("textures/fantasy_ui_borders/panel-border-010.png");
let image_with_repeated_sampler = asset_server.load_with_settings(
"textures/fantasy_ui_borders/panel-border-010-repeated.png",
|s: &mut _| {
*s = ImageLoaderSettings {
sampler: ImageSampler::Descriptor(ImageSamplerDescriptor {
// rewriting mode to repeat image,
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
}),
..default()
}
},
);
// central rectangle with not repeated texture
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(image_with_default_sampler.clone()),
..default()
})),
Transform::from_translation(Vec3::ZERO),
children![(
Text2d::new("Control"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));
// left rectangle with repeated texture
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(image_with_repeated_sampler),
// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(-RECTANGLE_OFFSET, 0.0, 0.0),
children![(
Text2d::new("Repeat On"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));
// right rectangle with scaled texture, but with default sampler.
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
// there is no sampler set, that's why
// by default you see only one small image in a row/column
// and other space is filled by image edge
texture: Some(image_with_default_sampler),
// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(RECTANGLE_OFFSET, 0.0, 0.0),
children![(
Text2d::new("Repeat Off"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));
// camera
commands.spawn((
Camera2d,
Transform::default().looking_at(Vec3::ZERO, Vec3::Y),
));
}

View File

@@ -0,0 +1,50 @@
//! Shows how to render a polygonal [`Mesh`], generated from a [`Rectangle`] primitive, in a 2D scene.
//! Adds a texture and colored vertices, giving per-vertex tinting.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
) {
// Load the Bevy logo as a texture
let texture_handle = asset_server.load("branding/banner.png");
// Build a default quad mesh
let mut mesh = Mesh::from(Rectangle::default());
// Build vertex colors for the quad. One entry per vertex (the corners of the quad)
let vertex_colors: Vec<[f32; 4]> = vec![
LinearRgba::RED.to_f32_array(),
LinearRgba::GREEN.to_f32_array(),
LinearRgba::BLUE.to_f32_array(),
LinearRgba::WHITE.to_f32_array(),
];
// Insert the vertex colors as an attribute
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors);
let mesh_handle = meshes.add(mesh);
commands.spawn(Camera2d);
// Spawn the quad with vertex colors
commands.spawn((
Mesh2d(mesh_handle.clone()),
MeshMaterial2d(materials.add(ColorMaterial::default())),
Transform::from_translation(Vec3::new(-96., 0., 0.)).with_scale(Vec3::splat(128.)),
));
// Spawning the quad with vertex colors and a texture results in tinting
commands.spawn((
Mesh2d(mesh_handle),
MeshMaterial2d(materials.add(texture_handle)),
Transform::from_translation(Vec3::new(96., 0., 0.)).with_scale(Vec3::splat(128.)),
));
}

44
vendor/bevy/examples/2d/move_sprite.rs vendored Normal file
View File

@@ -0,0 +1,44 @@
//! Renders a 2D scene containing a single, moving sprite.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, sprite_movement)
.run();
}
#[derive(Component)]
enum Direction {
Left,
Right,
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn((
Sprite::from_image(asset_server.load("branding/icon.png")),
Transform::from_xyz(0., 0., 0.),
Direction::Right,
));
}
/// The sprite is animated by changing its translation depending on the time that has passed since
/// the last frame.
fn sprite_movement(time: Res<Time>, mut sprite_position: Query<(&mut Direction, &mut Transform)>) {
for (mut logo, mut transform) in &mut sprite_position {
match *logo {
Direction::Right => transform.translation.x += 150. * time.delta_secs(),
Direction::Left => transform.translation.x -= 150. * time.delta_secs(),
}
if transform.translation.x > 200. {
*logo = Direction::Left;
} else if transform.translation.x < -200. {
*logo = Direction::Right;
}
}
}

View File

@@ -0,0 +1,159 @@
//! Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D
use bevy::{
color::palettes::css::GRAY,
prelude::*,
render::{
camera::RenderTarget,
render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
},
view::RenderLayers,
},
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()))
.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<AssetServer>) {
// The sample sprite that will be rendered to the pixel-perfect canvas
commands.spawn((
Sprite::from_image(asset_server.load("pixel/bevy_pixel_dark.png")),
Transform::from_xyz(-45., 20., 2.),
Rotate,
PIXEL_PERFECT_LAYERS,
));
// The sample sprite that will be rendered to the high-res "outer world"
commands.spawn((
Sprite::from_image(asset_server.load("pixel/bevy_pixel_light.png")),
Transform::from_xyz(-45., -20., 2.),
Rotate,
HIGH_RES_LAYERS,
));
}
/// Spawns a capsule mesh on the pixel-perfect layer.
fn setup_mesh(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn((
Mesh2d(meshes.add(Capsule2d::default())),
MeshMaterial2d(materials.add(Color::BLACK)),
Transform::from_xyz(25., 0., 2.).with_scale(Vec3::splat(32.)),
Rotate,
PIXEL_PERFECT_LAYERS,
));
}
fn setup_camera(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
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((
Camera2d,
Camera {
// Render before the "main pass" camera
order: -1,
target: RenderTarget::Image(image_handle.clone().into()),
clear_color: ClearColorConfig::Custom(GRAY.into()),
..default()
},
Msaa::Off,
InGameCamera,
PIXEL_PERFECT_LAYERS,
));
// Spawn the canvas
commands.spawn((Sprite::from_image(image_handle), 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((Camera2d, Msaa::Off, OuterCamera, HIGH_RES_LAYERS));
}
/// Rotates entities to demonstrate grid snapping.
fn rotate(time: Res<Time>, mut transforms: Query<&mut Transform, With<Rotate>>) {
for mut transform in &mut transforms {
let dt = time.delta_secs();
transform.rotate_z(dt);
}
}
/// Scales camera projection to fit the window (integer multiples only).
fn fit_canvas(
mut resize_events: EventReader<WindowResized>,
mut projection: Single<&mut Projection, With<OuterCamera>>,
) {
let Projection::Orthographic(projection) = &mut **projection else {
return;
};
for event in resize_events.read() {
let h_scale = event.width / RES_WIDTH as f32;
let v_scale = event.height / RES_HEIGHT as f32;
projection.scale = 1. / h_scale.min(v_scale).round();
}
}

246
vendor/bevy/examples/2d/rotation.rs vendored Normal file
View File

@@ -0,0 +1,246 @@
//! Demonstrates rotating entities in 2D using quaternions.
use bevy::{math::ops, prelude::*};
const BOUNDS: Vec2 = Vec2::new(1200.0, 640.0);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(Time::<Fixed>::from_hz(60.0))
.add_systems(Startup, setup)
.add_systems(
FixedUpdate,
(
player_movement_system,
snap_to_player_system,
rotate_to_player_system,
),
)
.run();
}
/// Player component
#[derive(Component)]
struct Player {
/// Linear speed in meters per second
movement_speed: f32,
/// Rotation speed in radians per second
rotation_speed: f32,
}
/// Snap to player ship behavior
#[derive(Component)]
struct SnapToPlayer;
/// Rotate to face player ship behavior
#[derive(Component)]
struct RotateToPlayer {
/// Rotation speed in radians per second
rotation_speed: f32,
}
/// Add the game's entities to our world and creates an orthographic camera for 2D rendering.
///
/// The Bevy coordinate system is the same for 2D and 3D, in terms of 2D this means that:
///
/// * `X` axis goes from left to right (`+X` points right)
/// * `Y` axis goes from bottom to top (`+Y` point up)
/// * `Z` axis goes from far to near (`+Z` points towards you, out of the screen)
///
/// The origin is at the center of the screen.
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let ship_handle = asset_server.load("textures/simplespace/ship_C.png");
let enemy_a_handle = asset_server.load("textures/simplespace/enemy_A.png");
let enemy_b_handle = asset_server.load("textures/simplespace/enemy_B.png");
commands.spawn(Camera2d);
// Create a minimal UI explaining how to interact with the example
commands.spawn((
Text::new("Up Arrow: Move Forward\nLeft / Right Arrow: Turn"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
let horizontal_margin = BOUNDS.x / 4.0;
let vertical_margin = BOUNDS.y / 4.0;
// Player controlled ship
commands.spawn((
Sprite::from_image(ship_handle),
Player {
movement_speed: 500.0, // Meters per second
rotation_speed: f32::to_radians(360.0), // Degrees per second
},
));
// Enemy that snaps to face the player spawns on the bottom and left
commands.spawn((
Sprite::from_image(enemy_a_handle.clone()),
Transform::from_xyz(0.0 - horizontal_margin, 0.0, 0.0),
SnapToPlayer,
));
commands.spawn((
Sprite::from_image(enemy_a_handle),
Transform::from_xyz(0.0, 0.0 - vertical_margin, 0.0),
SnapToPlayer,
));
// Enemy that rotates to face the player enemy spawns on the top and right
commands.spawn((
Sprite::from_image(enemy_b_handle.clone()),
Transform::from_xyz(0.0 + horizontal_margin, 0.0, 0.0),
RotateToPlayer {
rotation_speed: f32::to_radians(45.0), // Degrees per second
},
));
commands.spawn((
Sprite::from_image(enemy_b_handle),
Transform::from_xyz(0.0, 0.0 + vertical_margin, 0.0),
RotateToPlayer {
rotation_speed: f32::to_radians(90.0), // Degrees per second
},
));
}
/// Demonstrates applying rotation and movement based on keyboard input.
fn player_movement_system(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,
query: Single<(&Player, &mut Transform)>,
) {
let (ship, mut transform) = query.into_inner();
let mut rotation_factor = 0.0;
let mut movement_factor = 0.0;
if keyboard_input.pressed(KeyCode::ArrowLeft) {
rotation_factor += 1.0;
}
if keyboard_input.pressed(KeyCode::ArrowRight) {
rotation_factor -= 1.0;
}
if keyboard_input.pressed(KeyCode::ArrowUp) {
movement_factor += 1.0;
}
// Update the ship rotation around the Z axis (perpendicular to the 2D plane of the screen)
transform.rotate_z(rotation_factor * ship.rotation_speed * time.delta_secs());
// Get the ship's forward vector by applying the current rotation to the ships initial facing
// vector
let movement_direction = transform.rotation * Vec3::Y;
// Get the distance the ship will move based on direction, the ship's movement speed and delta
// time
let movement_distance = movement_factor * ship.movement_speed * time.delta_secs();
// Create the change in translation using the new movement direction and distance
let translation_delta = movement_direction * movement_distance;
// Update the ship translation with our new translation delta
transform.translation += translation_delta;
// Bound the ship within the invisible level bounds
let extents = Vec3::from((BOUNDS / 2.0, 0.0));
transform.translation = transform.translation.min(extents).max(-extents);
}
/// Demonstrates snapping the enemy ship to face the player ship immediately.
fn snap_to_player_system(
mut query: Query<&mut Transform, (With<SnapToPlayer>, Without<Player>)>,
player_transform: Single<&Transform, With<Player>>,
) {
// Get the player translation in 2D
let player_translation = player_transform.translation.xy();
for mut enemy_transform in &mut query {
// Get the vector from the enemy ship to the player ship in 2D and normalize it.
let to_player = (player_translation - enemy_transform.translation.xy()).normalize();
// Get the quaternion to rotate from the initial enemy facing direction to the direction
// facing the player
let rotate_to_player = Quat::from_rotation_arc(Vec3::Y, to_player.extend(0.));
// Rotate the enemy to face the player
enemy_transform.rotation = rotate_to_player;
}
}
/// Demonstrates rotating an enemy ship to face the player ship at a given rotation speed.
///
/// This method uses the vector dot product to determine if the enemy is facing the player and
/// if not, which way to rotate to face the player. The dot product on two unit length vectors
/// will return a value between -1.0 and +1.0 which tells us the following about the two vectors:
///
/// * If the result is 1.0 the vectors are pointing in the same direction, the angle between them is
/// 0 degrees.
/// * If the result is 0.0 the vectors are perpendicular, the angle between them is 90 degrees.
/// * If the result is -1.0 the vectors are parallel but pointing in opposite directions, the angle
/// between them is 180 degrees.
/// * If the result is positive the vectors are pointing in roughly the same direction, the angle
/// between them is greater than 0 and less than 90 degrees.
/// * If the result is negative the vectors are pointing in roughly opposite directions, the angle
/// between them is greater than 90 and less than 180 degrees.
///
/// It is possible to get the angle by taking the arc cosine (`acos`) of the dot product. It is
/// often unnecessary to do this though. Beware than `acos` will return `NaN` if the input is less
/// than -1.0 or greater than 1.0. This can happen even when working with unit vectors due to
/// floating point precision loss, so it pays to clamp your dot product value before calling
/// `acos`.
fn rotate_to_player_system(
time: Res<Time>,
mut query: Query<(&RotateToPlayer, &mut Transform), Without<Player>>,
player_transform: Single<&Transform, With<Player>>,
) {
// Get the player translation in 2D
let player_translation = player_transform.translation.xy();
for (config, mut enemy_transform) in &mut query {
// Get the enemy ship forward vector in 2D (already unit length)
let enemy_forward = (enemy_transform.rotation * Vec3::Y).xy();
// Get the vector from the enemy ship to the player ship in 2D and normalize it.
let to_player = (player_translation - enemy_transform.translation.xy()).normalize();
// Get the dot product between the enemy forward vector and the direction to the player.
let forward_dot_player = enemy_forward.dot(to_player);
// If the dot product is approximately 1.0 then the enemy is already facing the player and
// we can early out.
if (forward_dot_player - 1.0).abs() < f32::EPSILON {
continue;
}
// Get the right vector of the enemy ship in 2D (already unit length)
let enemy_right = (enemy_transform.rotation * Vec3::X).xy();
// Get the dot product of the enemy right vector and the direction to the player ship.
// If the dot product is negative them we need to rotate counter clockwise, if it is
// positive we need to rotate clockwise. Note that `copysign` will still return 1.0 if the
// dot product is 0.0 (because the player is directly behind the enemy, so perpendicular
// with the right vector).
let right_dot_player = enemy_right.dot(to_player);
// Determine the sign of rotation from the right dot player. We need to negate the sign
// here as the 2D bevy co-ordinate system rotates around +Z, which is pointing out of the
// screen. Due to the right hand rule, positive rotation around +Z is counter clockwise and
// negative is clockwise.
let rotation_sign = -f32::copysign(1.0, right_dot_player);
// Limit rotation so we don't overshoot the target. We need to convert our dot product to
// an angle here so we can get an angle of rotation to clamp against.
let max_angle = ops::acos(forward_dot_player.clamp(-1.0, 1.0)); // Clamp acos for safety
// Calculate angle of rotation with limit
let rotation_angle =
rotation_sign * (config.rotation_speed * time.delta_secs()).min(max_angle);
// Rotate the enemy to face the player
enemy_transform.rotate_z(rotation_angle);
}
}

18
vendor/bevy/examples/2d/sprite.rs vendored Normal file
View File

@@ -0,0 +1,18 @@
//! Displays a single [`Sprite`], created from an image.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Sprite::from_image(
asset_server.load("branding/bevy_bird_dark.png"),
));
}

View File

@@ -0,0 +1,145 @@
//! Animates a sprite in response to a keyboard event.
//!
//! See `sprite_sheet.rs` for an example where the sprite animation loops indefinitely.
use std::time::Duration;
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites
.add_systems(Startup, setup)
.add_systems(Update, execute_animations)
.add_systems(
Update,
(
// Press the right arrow key to animate the right sprite
trigger_animation::<RightSprite>.run_if(input_just_pressed(KeyCode::ArrowRight)),
// Press the left arrow key to animate the left sprite
trigger_animation::<LeftSprite>.run_if(input_just_pressed(KeyCode::ArrowLeft)),
),
)
.run();
}
// This system runs when the user clicks the left arrow key or right arrow key
fn trigger_animation<S: Component>(mut animation: Single<&mut AnimationConfig, With<S>>) {
// We create a new timer when the animation is triggered
animation.frame_timer = AnimationConfig::timer_from_fps(animation.fps);
}
#[derive(Component)]
struct AnimationConfig {
first_sprite_index: usize,
last_sprite_index: usize,
fps: u8,
frame_timer: Timer,
}
impl AnimationConfig {
fn new(first: usize, last: usize, fps: u8) -> Self {
Self {
first_sprite_index: first,
last_sprite_index: last,
fps,
frame_timer: Self::timer_from_fps(fps),
}
}
fn timer_from_fps(fps: u8) -> Timer {
Timer::new(Duration::from_secs_f32(1.0 / (fps as f32)), TimerMode::Once)
}
}
// This system loops through all the sprites in the `TextureAtlas`, from `first_sprite_index` to
// `last_sprite_index` (both defined in `AnimationConfig`).
fn execute_animations(time: Res<Time>, mut query: Query<(&mut AnimationConfig, &mut Sprite)>) {
for (mut config, mut sprite) in &mut query {
// We track how long the current sprite has been displayed for
config.frame_timer.tick(time.delta());
// If it has been displayed for the user-defined amount of time (fps)...
if config.frame_timer.just_finished() {
if let Some(atlas) = &mut sprite.texture_atlas {
if atlas.index == config.last_sprite_index {
// ...and it IS the last frame, then we move back to the first frame and stop.
atlas.index = config.first_sprite_index;
} else {
// ...and it is NOT the last frame, then we move to the next frame...
atlas.index += 1;
// ...and reset the frame timer to start counting all over again
config.frame_timer = AnimationConfig::timer_from_fps(config.fps);
}
}
}
}
}
#[derive(Component)]
struct LeftSprite;
#[derive(Component)]
struct RightSprite;
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
commands.spawn(Camera2d);
// Create a minimal UI explaining how to interact with the example
commands.spawn((
Text::new("Left Arrow: Animate Left Sprite\nRight Arrow: Animate Right Sprite"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
// Load the sprite sheet using the `AssetServer`
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
// The sprite sheet has 7 sprites arranged in a row, and they are all 24px x 24px
let layout = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None);
let texture_atlas_layout = texture_atlas_layouts.add(layout);
// The first (left-hand) sprite runs at 10 FPS
let animation_config_1 = AnimationConfig::new(1, 6, 10);
// Create the first (left-hand) sprite
commands.spawn((
Sprite {
image: texture.clone(),
texture_atlas: Some(TextureAtlas {
layout: texture_atlas_layout.clone(),
index: animation_config_1.first_sprite_index,
}),
..default()
},
Transform::from_scale(Vec3::splat(6.0)).with_translation(Vec3::new(-70.0, 0.0, 0.0)),
LeftSprite,
animation_config_1,
));
// The second (right-hand) sprite runs at 20 FPS
let animation_config_2 = AnimationConfig::new(1, 6, 20);
// Create the second (right-hand) sprite
commands.spawn((
Sprite {
image: texture.clone(),
texture_atlas: Some(TextureAtlas {
layout: texture_atlas_layout.clone(),
index: animation_config_2.first_sprite_index,
}),
..Default::default()
},
Transform::from_scale(Vec3::splat(6.0)).with_translation(Vec3::new(70.0, 0.0, 0.0)),
RightSprite,
animation_config_2,
));
}

View File

@@ -0,0 +1,23 @@
//! Displays a single [`Sprite`], created from an image, but flipped on one axis.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.spawn(Sprite {
image: asset_server.load("branding/bevy_bird_dark.png"),
// Flip the logo to the left
flip_x: true,
// And don't flip it upside-down ( the default )
flip_y: false,
..Default::default()
});
}

332
vendor/bevy/examples/2d/sprite_scale.rs vendored Normal file
View File

@@ -0,0 +1,332 @@
//! Shows how to use sprite scaling to fill and fit textures into the sprite.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(
Startup,
(setup_sprites, setup_texture_atlas).after(setup_camera),
)
.add_systems(Update, animate_sprite)
.run();
}
fn setup_camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
fn setup_sprites(mut commands: Commands, asset_server: Res<AssetServer>) {
let square = asset_server.load("textures/slice_square_2.png");
let banner = asset_server.load("branding/banner.png");
let rects = [
Rect {
size: Vec2::new(100., 225.),
text: "Stretched".to_string(),
transform: Transform::from_translation(Vec3::new(-570., 230., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Auto,
},
Rect {
size: Vec2::new(100., 225.),
text: "Fill Center".to_string(),
transform: Transform::from_translation(Vec3::new(-450., 230., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
},
Rect {
size: Vec2::new(100., 225.),
text: "Fill Start".to_string(),
transform: Transform::from_translation(Vec3::new(-330., 230., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
},
Rect {
size: Vec2::new(100., 225.),
text: "Fill End".to_string(),
transform: Transform::from_translation(Vec3::new(-210., 230., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
},
Rect {
size: Vec2::new(300., 100.),
text: "Fill Start Horizontal".to_string(),
transform: Transform::from_translation(Vec3::new(10., 290., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
},
Rect {
size: Vec2::new(300., 100.),
text: "Fill End Horizontal".to_string(),
transform: Transform::from_translation(Vec3::new(10., 155., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
},
Rect {
size: Vec2::new(200., 200.),
text: "Fill Center".to_string(),
transform: Transform::from_translation(Vec3::new(280., 230., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
},
Rect {
size: Vec2::new(200., 100.),
text: "Fill Center".to_string(),
transform: Transform::from_translation(Vec3::new(500., 230., 0.)),
texture: square.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
},
Rect {
size: Vec2::new(100., 100.),
text: "Stretched".to_string(),
transform: Transform::from_translation(Vec3::new(-570., -40., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Auto,
},
Rect {
size: Vec2::new(200., 200.),
text: "Fit Center".to_string(),
transform: Transform::from_translation(Vec3::new(-400., -40., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
},
Rect {
size: Vec2::new(200., 200.),
text: "Fit Start".to_string(),
transform: Transform::from_translation(Vec3::new(-180., -40., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitStart),
},
Rect {
size: Vec2::new(200., 200.),
text: "Fit End".to_string(),
transform: Transform::from_translation(Vec3::new(40., -40., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitEnd),
},
Rect {
size: Vec2::new(100., 200.),
text: "Fit Center".to_string(),
transform: Transform::from_translation(Vec3::new(210., -40., 0.)),
texture: banner.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
},
];
for rect in rects {
let mut cmd = commands.spawn((
Sprite {
image: rect.texture,
custom_size: Some(rect.size),
image_mode: rect.image_mode,
..default()
},
rect.transform,
));
cmd.with_children(|builder| {
builder.spawn((
Text2d::new(rect.text),
TextLayout::new_with_justify(JustifyText::Center),
TextFont::from_font_size(15.),
Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.),
bevy::sprite::Anchor::TopCenter,
));
});
}
}
fn setup_texture_atlas(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
commands.spawn(Camera2d);
let gabe = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let animation_indices_gabe = AnimationIndices { first: 0, last: 6 };
let gabe_atlas = TextureAtlas {
layout: texture_atlas_layouts.add(TextureAtlasLayout::from_grid(
UVec2::splat(24),
7,
1,
None,
None,
)),
index: animation_indices_gabe.first,
};
let sprite_sheets = [
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Stretched".to_string(),
transform: Transform::from_translation(Vec3::new(-570., -200., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Auto,
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fill Center".to_string(),
transform: Transform::from_translation(Vec3::new(-570., -300., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fill Start".to_string(),
transform: Transform::from_translation(Vec3::new(-430., -200., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fill End".to_string(),
transform: Transform::from_translation(Vec3::new(-430., -300., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(50., 120.),
text: "Fill Center".to_string(),
transform: Transform::from_translation(Vec3::new(-300., -250., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(50., 120.),
text: "Fill Start".to_string(),
transform: Transform::from_translation(Vec3::new(-190., -250., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(50., 120.),
text: "Fill End".to_string(),
transform: Transform::from_translation(Vec3::new(-90., -250., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fit Center".to_string(),
transform: Transform::from_translation(Vec3::new(20., -200., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fit Start".to_string(),
transform: Transform::from_translation(Vec3::new(20., -300., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitStart),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
SpriteSheet {
size: Vec2::new(120., 50.),
text: "Fit End".to_string(),
transform: Transform::from_translation(Vec3::new(160., -200., 0.)),
texture: gabe.clone(),
image_mode: SpriteImageMode::Scale(ScalingMode::FitEnd),
atlas: gabe_atlas.clone(),
indices: animation_indices_gabe.clone(),
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
},
];
for sprite_sheet in sprite_sheets {
let mut cmd = commands.spawn((
Sprite {
image_mode: sprite_sheet.image_mode,
custom_size: Some(sprite_sheet.size),
..Sprite::from_atlas_image(sprite_sheet.texture.clone(), sprite_sheet.atlas.clone())
},
sprite_sheet.indices,
sprite_sheet.timer,
sprite_sheet.transform,
));
cmd.with_children(|builder| {
builder.spawn((
Text2d::new(sprite_sheet.text),
TextLayout::new_with_justify(JustifyText::Center),
TextFont::from_font_size(15.),
Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.),
bevy::sprite::Anchor::TopCenter,
));
});
}
}
struct Rect {
size: Vec2,
text: String,
transform: Transform,
texture: Handle<Image>,
image_mode: SpriteImageMode,
}
struct SpriteSheet {
size: Vec2,
text: String,
transform: Transform,
texture: Handle<Image>,
image_mode: SpriteImageMode,
atlas: TextureAtlas,
indices: AnimationIndices,
timer: AnimationTimer,
}
#[derive(Component, Clone)]
struct AnimationIndices {
first: usize,
last: usize,
}
#[derive(Component, Deref, DerefMut)]
struct AnimationTimer(Timer);
fn animate_sprite(
time: Res<Time>,
mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>,
) {
for (indices, mut timer, mut sprite) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
if let Some(atlas) = &mut sprite.texture_atlas {
atlas.index = if atlas.index == indices.last {
indices.first
} else {
atlas.index + 1
};
}
}
}
}

67
vendor/bevy/examples/2d/sprite_sheet.rs vendored Normal file
View File

@@ -0,0 +1,67 @@
//! Renders an animated sprite by loading all animation frames from a single image (a sprite sheet)
//! into a texture atlas, and changing the displayed image periodically.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites
.add_systems(Startup, setup)
.add_systems(Update, animate_sprite)
.run();
}
#[derive(Component)]
struct AnimationIndices {
first: usize,
last: usize,
}
#[derive(Component, Deref, DerefMut)]
struct AnimationTimer(Timer);
fn animate_sprite(
time: Res<Time>,
mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>,
) {
for (indices, mut timer, mut sprite) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
if let Some(atlas) = &mut sprite.texture_atlas {
atlas.index = if atlas.index == indices.last {
indices.first
} else {
atlas.index + 1
};
}
}
}
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
let texture = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let layout = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None);
let texture_atlas_layout = texture_atlas_layouts.add(layout);
// Use only the subset of sprites in the sheet that make up the run animation
let animation_indices = AnimationIndices { first: 1, last: 6 };
commands.spawn(Camera2d);
commands.spawn((
Sprite::from_atlas_image(
texture,
TextureAtlas {
layout: texture_atlas_layout,
index: animation_indices.first,
},
),
Transform::from_scale(Vec3::splat(6.0)),
animation_indices,
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
));
}

136
vendor/bevy/examples/2d/sprite_slice.rs vendored Normal file
View File

@@ -0,0 +1,136 @@
//! Showcases sprite 9 slice scaling and tiling features, enabling usage of
//! sprites in multiple resolutions while keeping it in proportion
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn spawn_sprites(
commands: &mut Commands,
texture_handle: Handle<Image>,
mut position: Vec3,
slice_border: f32,
style: TextFont,
gap: f32,
) {
let cases = [
// Reference sprite
(
"Original",
style.clone(),
Vec2::splat(100.0),
SpriteImageMode::Auto,
),
// Scaled regular sprite
(
"Stretched",
style.clone(),
Vec2::new(100.0, 200.0),
SpriteImageMode::Auto,
),
// Stretched Scaled sliced sprite
(
"With Slicing",
style.clone(),
Vec2::new(100.0, 200.0),
SpriteImageMode::Sliced(TextureSlicer {
border: BorderRect::all(slice_border),
center_scale_mode: SliceScaleMode::Stretch,
..default()
}),
),
// Scaled sliced sprite
(
"With Tiling",
style.clone(),
Vec2::new(100.0, 200.0),
SpriteImageMode::Sliced(TextureSlicer {
border: BorderRect::all(slice_border),
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.5 },
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
..default()
}),
),
// Scaled sliced sprite horizontally
(
"With Tiling",
style.clone(),
Vec2::new(300.0, 200.0),
SpriteImageMode::Sliced(TextureSlicer {
border: BorderRect::all(slice_border),
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.3 },
..default()
}),
),
// Scaled sliced sprite horizontally with max scale
(
"With Corners Constrained",
style,
Vec2::new(300.0, 200.0),
SpriteImageMode::Sliced(TextureSlicer {
border: BorderRect::all(slice_border),
center_scale_mode: SliceScaleMode::Tile { stretch_value: 0.1 },
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 0.2 },
max_corner_scale: 0.2,
}),
),
];
for (label, text_style, size, scale_mode) in cases {
position.x += 0.5 * size.x;
commands.spawn((
Sprite {
image: texture_handle.clone(),
custom_size: Some(size),
image_mode: scale_mode,
..default()
},
Transform::from_translation(position),
children![(
Text2d::new(label),
text_style,
TextLayout::new_with_justify(JustifyText::Center),
Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
bevy::sprite::Anchor::TopCenter,
)],
));
position.x += 0.5 * size.x + gap;
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let style = TextFont {
font: font.clone(),
..default()
};
// Load textures
let handle_1 = asset_server.load("textures/slice_square.png");
let handle_2 = asset_server.load("textures/slice_square_2.png");
spawn_sprites(
&mut commands,
handle_1,
Vec3::new(-600.0, 150.0, 0.0),
200.0,
style.clone(),
40.,
);
spawn_sprites(
&mut commands,
handle_2,
Vec3::new(-600.0, -150.0, 0.0),
80.0,
style,
40.,
);
}

49
vendor/bevy/examples/2d/sprite_tile.rs vendored Normal file
View File

@@ -0,0 +1,49 @@
//! Displays a single [`Sprite`] tiled in a grid, with a scaling animation
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, animate)
.run();
}
#[derive(Resource)]
struct AnimationState {
min: f32,
max: f32,
current: f32,
speed: f32,
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
commands.insert_resource(AnimationState {
min: 128.0,
max: 512.0,
current: 128.0,
speed: 50.0,
});
commands.spawn(Sprite {
image: asset_server.load("branding/icon.png"),
image_mode: SpriteImageMode::Tiled {
tile_x: true,
tile_y: true,
stretch_value: 0.5, // The image will tile every 128px
},
..default()
});
}
fn animate(mut sprites: Query<&mut Sprite>, mut state: ResMut<AnimationState>, time: Res<Time>) {
if state.current >= state.max || state.current <= state.min {
state.speed = -state.speed;
};
state.current += state.speed * time.delta_secs();
for mut sprite in &mut sprites {
sprite.custom_size = Some(Vec2::splat(state.current));
}
}

187
vendor/bevy/examples/2d/text2d.rs vendored Normal file
View File

@@ -0,0 +1,187 @@
//! Shows text rendering with moving, rotating and scaling text.
//!
//! Note that this uses [`Text2d`] to display text alongside your other entities in a 2D scene.
//!
//! For an example on how to render text as part of a user interface, independent from the world
//! viewport, you may want to look at `games/contributors.rs` or `ui/text.rs`.
use bevy::{
color::palettes::css::*,
math::ops,
prelude::*,
sprite::Anchor,
text::{FontSmoothing, LineBreak, TextBounds},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(
Update,
(animate_translation, animate_rotation, animate_scale),
)
.run();
}
#[derive(Component)]
struct AnimateTranslation;
#[derive(Component)]
struct AnimateRotation;
#[derive(Component)]
struct AnimateScale;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let text_font = TextFont {
font: font.clone(),
font_size: 50.0,
..default()
};
let text_justification = JustifyText::Center;
commands.spawn(Camera2d);
// Demonstrate changing translation
commands.spawn((
Text2d::new("translation"),
text_font.clone(),
TextLayout::new_with_justify(text_justification),
AnimateTranslation,
));
// Demonstrate changing rotation
commands.spawn((
Text2d::new("rotation"),
text_font.clone(),
TextLayout::new_with_justify(text_justification),
AnimateRotation,
));
// Demonstrate changing scale
commands.spawn((
Text2d::new("scale"),
text_font,
TextLayout::new_with_justify(text_justification),
Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
AnimateScale,
));
// Demonstrate text wrapping
let slightly_smaller_text_font = TextFont {
font,
font_size: 35.0,
..default()
};
let box_size = Vec2::new(300.0, 200.0);
let box_position = Vec2::new(0.0, -250.0);
commands
.spawn((
Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), box_size),
Transform::from_translation(box_position.extend(0.0)),
))
.with_children(|builder| {
builder.spawn((
Text2d::new("this text wraps in the box\n(Unicode linebreaks)"),
slightly_smaller_text_font.clone(),
TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
// Wrap text in the rectangle
TextBounds::from(box_size),
// Ensure the text is drawn on top of the box
Transform::from_translation(Vec3::Z),
));
});
let other_box_size = Vec2::new(300.0, 200.0);
let other_box_position = Vec2::new(320.0, -250.0);
commands
.spawn((
Sprite::from_color(Color::srgb(0.25, 0.25, 0.55), other_box_size),
Transform::from_translation(other_box_position.extend(0.0)),
))
.with_children(|builder| {
builder.spawn((
Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"),
slightly_smaller_text_font.clone(),
TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter),
// Wrap text in the rectangle
TextBounds::from(other_box_size),
// Ensure the text is drawn on top of the box
Transform::from_translation(Vec3::Z),
));
});
// Demonstrate font smoothing off
commands.spawn((
Text2d::new("This text has\nFontSmoothing::None\nAnd JustifyText::Center"),
slightly_smaller_text_font
.clone()
.with_font_smoothing(FontSmoothing::None),
TextLayout::new_with_justify(JustifyText::Center),
Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
));
commands
.spawn((
Sprite {
color: Color::Srgba(LIGHT_CYAN),
custom_size: Some(Vec2::new(10., 10.)),
..Default::default()
},
Transform::from_translation(250. * Vec3::Y),
))
.with_children(|commands| {
for (text_anchor, color) in [
(Anchor::TopLeft, Color::Srgba(LIGHT_SALMON)),
(Anchor::TopRight, Color::Srgba(LIGHT_GREEN)),
(Anchor::BottomRight, Color::Srgba(LIGHT_BLUE)),
(Anchor::BottomLeft, Color::Srgba(LIGHT_YELLOW)),
] {
commands
.spawn((
Text2d::new(" Anchor".to_string()),
slightly_smaller_text_font.clone(),
text_anchor,
))
.with_child((
TextSpan("::".to_string()),
slightly_smaller_text_font.clone(),
TextColor(LIGHT_GREY.into()),
))
.with_child((
TextSpan(format!("{text_anchor:?} ")),
slightly_smaller_text_font.clone(),
TextColor(color),
));
}
});
}
fn animate_translation(
time: Res<Time>,
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateTranslation>)>,
) {
for mut transform in &mut query {
transform.translation.x = 100.0 * ops::sin(time.elapsed_secs()) - 400.0;
transform.translation.y = 100.0 * ops::cos(time.elapsed_secs());
}
}
fn animate_rotation(
time: Res<Time>,
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateRotation>)>,
) {
for mut transform in &mut query {
transform.rotation = Quat::from_rotation_z(ops::cos(time.elapsed_secs()));
}
}
fn animate_scale(
time: Res<Time>,
mut query: Query<&mut Transform, (With<Text2d>, With<AnimateScale>)>,
) {
// Consider changing font-size instead of scaling the transform. Scaling a Text2D will scale the
// rendered quad, resulting in a pixellated look.
for mut transform in &mut query {
let scale = (ops::sin(time.elapsed_secs()) + 1.1) * 2.0;
transform.scale.x = scale;
transform.scale.y = scale;
}
}

288
vendor/bevy/examples/2d/texture_atlas.rs vendored Normal file
View File

@@ -0,0 +1,288 @@
//! In this example we generate four texture atlases (sprite sheets) from a folder containing
//! individual sprites.
//!
//! The texture atlases are generated with different padding and sampling to demonstrate the
//! effect of these settings, and how bleeding issues can be resolved by padding the sprites.
//!
//! Only one padded and one unpadded texture atlas are rendered to the screen.
//! An upscaled sprite from each of the four atlases are rendered to the screen.
use bevy::{asset::LoadedFolder, image::ImageSampler, prelude::*};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // fallback to nearest sampling
.init_state::<AppState>()
.add_systems(OnEnter(AppState::Setup), load_textures)
.add_systems(Update, check_textures.run_if(in_state(AppState::Setup)))
.add_systems(OnEnter(AppState::Finished), setup)
.run();
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, States)]
enum AppState {
#[default]
Setup,
Finished,
}
#[derive(Resource, Default)]
struct RpgSpriteFolder(Handle<LoadedFolder>);
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
// Load multiple, individual sprites from a folder
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder("textures/rpg")));
}
fn check_textures(
mut next_state: ResMut<NextState<AppState>>,
rpg_sprite_folder: Res<RpgSpriteFolder>,
mut events: EventReader<AssetEvent<LoadedFolder>>,
) {
// Advance the `AppState` once all sprite handles have been loaded by the `AssetServer`
for event in events.read() {
if event.is_loaded_with_dependencies(&rpg_sprite_folder.0) {
next_state.set(AppState::Finished);
}
}
}
fn setup(
mut commands: Commands,
rpg_sprite_handles: Res<RpgSpriteFolder>,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
loaded_folders: Res<Assets<LoadedFolder>>,
mut textures: ResMut<Assets<Image>>,
) {
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
// Create texture atlases with different padding and sampling
let (texture_atlas_linear, linear_sources, linear_texture) = create_texture_atlas(
loaded_folder,
None,
Some(ImageSampler::linear()),
&mut textures,
);
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear);
let (texture_atlas_nearest, nearest_sources, nearest_texture) = create_texture_atlas(
loaded_folder,
None,
Some(ImageSampler::nearest()),
&mut textures,
);
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);
let (texture_atlas_linear_padded, linear_padded_sources, linear_padded_texture) =
create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::linear()),
&mut textures,
);
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());
let (texture_atlas_nearest_padded, nearest_padded_sources, nearest_padded_texture) =
create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::nearest()),
&mut textures,
);
let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded);
commands.spawn(Camera2d);
// Padded textures are to the right, unpadded to the left
// Draw unpadded texture atlas
commands.spawn((
Sprite::from_image(linear_texture.clone()),
Transform {
translation: Vec3::new(-250.0, -160.0, 0.0),
scale: Vec3::splat(0.5),
..default()
},
));
// Draw padded texture atlas
commands.spawn((
Sprite::from_image(linear_padded_texture.clone()),
Transform {
translation: Vec3::new(250.0, -160.0, 0.0),
scale: Vec3::splat(0.5),
..default()
},
));
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
// Padding label text style
let text_style: TextFont = TextFont {
font: font.clone(),
font_size: 42.0,
..default()
};
// Labels to indicate padding
// No padding
create_label(
&mut commands,
(-250.0, 250.0, 0.0),
"No padding",
text_style.clone(),
);
// Padding
create_label(&mut commands, (250.0, 250.0, 0.0), "Padding", text_style);
// Get handle to a sprite to render
let vendor_handle: Handle<Image> = asset_server
.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png")
.unwrap();
// Configuration array to render sprites through iteration
let configurations: [(
&str,
Handle<TextureAtlasLayout>,
TextureAtlasSources,
Handle<Image>,
f32,
); 4] = [
(
"Linear",
atlas_linear_handle,
linear_sources,
linear_texture,
-350.0,
),
(
"Nearest",
atlas_nearest_handle,
nearest_sources,
nearest_texture,
-150.0,
),
(
"Linear",
atlas_linear_padded_handle,
linear_padded_sources,
linear_padded_texture,
150.0,
),
(
"Nearest",
atlas_nearest_padded_handle,
nearest_padded_sources,
nearest_padded_texture,
350.0,
),
];
// Label text style
let sampling_label_style = TextFont {
font,
font_size: 25.0,
..default()
};
let base_y = 80.0; // y position of the sprites
for (sampling, atlas_handle, atlas_sources, atlas_texture, x) in configurations {
// Render a sprite from the texture_atlas
create_sprite_from_atlas(
&mut commands,
(x, base_y, 0.0),
atlas_texture,
atlas_sources,
atlas_handle,
&vendor_handle,
);
// Render a label to indicate the sampling setting
create_label(
&mut commands,
(x, base_y + 110.0, 0.0), // Offset to y position of the sprite
sampling,
sampling_label_style.clone(),
);
}
}
/// Create a texture atlas with the given padding and sampling settings
/// from the individual sprites in the given folder.
fn create_texture_atlas(
folder: &LoadedFolder,
padding: Option<UVec2>,
sampling: Option<ImageSampler>,
textures: &mut ResMut<Assets<Image>>,
) -> (TextureAtlasLayout, TextureAtlasSources, Handle<Image>) {
// Build a texture atlas using the individual sprites
let mut texture_atlas_builder = TextureAtlasBuilder::default();
texture_atlas_builder.padding(padding.unwrap_or_default());
for handle in folder.handles.iter() {
let id = handle.id().typed_unchecked::<Image>();
let Some(texture) = textures.get(id) else {
warn!(
"{} did not resolve to an `Image` asset.",
handle.path().unwrap()
);
continue;
};
texture_atlas_builder.add_texture(Some(id), texture);
}
let (texture_atlas_layout, texture_atlas_sources, texture) =
texture_atlas_builder.build().unwrap();
let texture = textures.add(texture);
// Update the sampling settings of the texture atlas
let image = textures.get_mut(&texture).unwrap();
image.sampler = sampling.unwrap_or_default();
(texture_atlas_layout, texture_atlas_sources, texture)
}
/// Create and spawn a sprite from a texture atlas
fn create_sprite_from_atlas(
commands: &mut Commands,
translation: (f32, f32, f32),
atlas_texture: Handle<Image>,
atlas_sources: TextureAtlasSources,
atlas_handle: Handle<TextureAtlasLayout>,
vendor_handle: &Handle<Image>,
) {
commands.spawn((
Transform {
translation: Vec3::new(translation.0, translation.1, translation.2),
scale: Vec3::splat(3.0),
..default()
},
Sprite::from_atlas_image(
atlas_texture,
atlas_sources.handle(atlas_handle, vendor_handle).unwrap(),
),
));
}
/// Create and spawn a label (text)
fn create_label(
commands: &mut Commands,
translation: (f32, f32, f32),
text: &str,
text_style: TextFont,
) {
commands.spawn((
Text2d::new(text),
text_style,
TextLayout::new_with_justify(JustifyText::Center),
Transform {
translation: Vec3::new(translation.0, translation.1, translation.2),
..default()
},
));
}

View File

@@ -0,0 +1,39 @@
//! Demonstrates how to use transparency in 2D.
//! Shows 3 bevy logos on top of each other, each with a different amount of transparency.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
let sprite_handle = asset_server.load("branding/icon.png");
commands.spawn((
Sprite::from_image(sprite_handle.clone()),
Transform::from_xyz(-100.0, 0.0, 0.0),
));
commands.spawn((
Sprite {
image: sprite_handle.clone(),
// Alpha channel of the color controls transparency.
color: Color::srgba(0.0, 0.0, 1.0, 0.7),
..default()
},
Transform::from_xyz(0.0, 0.0, 0.1),
));
commands.spawn((
Sprite {
image: sprite_handle,
color: Color::srgba(0.0, 1.0, 0.0, 0.3),
..default()
},
Transform::from_xyz(100.0, 0.0, 0.2),
));
}

147
vendor/bevy/examples/2d/wireframe_2d.rs vendored Normal file
View File

@@ -0,0 +1,147 @@
//! Showcases wireframe rendering for 2d meshes.
//!
//! Wireframes currently do not work when using webgl or webgpu.
//! Supported platforms:
//! - DX12
//! - Vulkan
//! - Metal
//!
//! This is a native only feature.
use bevy::{
color::palettes::basic::{GREEN, RED, WHITE},
prelude::*,
render::{
render_resource::WgpuFeatures,
settings::{RenderCreation, WgpuSettings},
RenderPlugin,
},
sprite::{NoWireframe2d, Wireframe2d, Wireframe2dColor, Wireframe2dConfig, Wireframe2dPlugin},
};
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
// WARN this is a native only feature. It will not work with webgl or webgpu
features: WgpuFeatures::POLYGON_MODE_LINE,
..default()
}),
..default()
}),
// You need to add this plugin to enable wireframe rendering
Wireframe2dPlugin::default(),
))
// Wireframes can be configured with this resource. This can be changed at runtime.
.insert_resource(Wireframe2dConfig {
// The global wireframe config enables drawing of wireframes on every mesh,
// except those with `NoWireframe2d`. Meshes with `Wireframe2d` will always have a wireframe,
// regardless of the global configuration.
global: true,
// Controls the default color of all wireframes. Used as the default color for global wireframes.
// Can be changed per mesh using the `Wireframe2dColor` component.
default_color: WHITE.into(),
})
.add_systems(Startup, setup)
.add_systems(Update, update_colors)
.run();
}
/// Set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// Triangle: Never renders a wireframe
commands.spawn((
Mesh2d(meshes.add(Triangle2d::new(
Vec2::new(0.0, 50.0),
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
))),
MeshMaterial2d(materials.add(Color::BLACK)),
Transform::from_xyz(-150.0, 0.0, 0.0),
NoWireframe2d,
));
// Rectangle: Follows global wireframe setting
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(100.0, 100.0))),
MeshMaterial2d(materials.add(Color::BLACK)),
Transform::from_xyz(0.0, 0.0, 0.0),
));
// Circle: Always renders a wireframe
commands.spawn((
Mesh2d(meshes.add(Circle::new(50.0))),
MeshMaterial2d(materials.add(Color::BLACK)),
Transform::from_xyz(150.0, 0.0, 0.0),
Wireframe2d,
// This lets you configure the wireframe color of this entity.
// If not set, this will use the color in `WireframeConfig`
Wireframe2dColor {
color: GREEN.into(),
},
));
commands.spawn(Camera2d);
// Text used to show controls
commands.spawn((
Text::default(),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
},
));
}
/// This system lets you toggle various wireframe settings
fn update_colors(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut config: ResMut<Wireframe2dConfig>,
mut wireframe_colors: Query<&mut Wireframe2dColor>,
mut text: Single<&mut Text>,
) {
text.0 = format!(
"Controls
---------------
Z - Toggle global
X - Change global color
C - Change color of the circle wireframe
Wireframe2dConfig
-------------
Global: {}
Color: {:?}",
config.global,
config.default_color.to_srgba(),
);
// Toggle showing a wireframe on all meshes
if keyboard_input.just_pressed(KeyCode::KeyZ) {
config.global = !config.global;
}
// Toggle the global wireframe color
if keyboard_input.just_pressed(KeyCode::KeyX) {
config.default_color = if config.default_color == WHITE.into() {
RED.into()
} else {
WHITE.into()
};
}
// Toggle the color of a wireframe using `Wireframe2dColor` and not the global color
if keyboard_input.just_pressed(KeyCode::KeyC) {
for mut color in &mut wireframe_colors {
color.color = if color.color == GREEN.into() {
RED.into()
} else {
GREEN.into()
};
}
}
}