mirror of
https://github.com/bevyengine/bevy
synced 2024-11-14 17:07:47 +00:00
599e5e4e76
# Objective - As part of the migration process we need to a) see the end effect of the migration on user ergonomics b) check for serious perf regressions c) actually migrate the code - To accomplish this, I'm going to attempt to migrate all of the remaining user-facing usages of `LegacyColor` in one PR, being careful to keep a clean commit history. - Fixes #12056. ## Solution I've chosen to use the polymorphic `Color` type as our standard user-facing API. - [x] Migrate `bevy_gizmos`. - [x] Take `impl Into<Color>` in all `bevy_gizmos` APIs - [x] Migrate sprites - [x] Migrate UI - [x] Migrate `ColorMaterial` - [x] Migrate `MaterialMesh2D` - [x] Migrate fog - [x] Migrate lights - [x] Migrate StandardMaterial - [x] Migrate wireframes - [x] Migrate clear color - [x] Migrate text - [x] Migrate gltf loader - [x] Register color types for reflection - [x] Remove `LegacyColor` - [x] Make sure CI passes Incidental improvements to ease migration: - added `Color::srgba_u8`, `Color::srgba_from_array` and friends - added `set_alpha`, `is_fully_transparent` and `is_fully_opaque` to the `Alpha` trait - add and immediately deprecate (lol) `Color::rgb` and friends in favor of more explicit and consistent `Color::srgb` - standardized on white and black for most example text colors - added vector field traits to `LinearRgba`: ~~`Add`, `Sub`, `AddAssign`, `SubAssign`,~~ `Mul<f32>` and `Div<f32>`. Multiplications and divisions do not scale alpha. `Add` and `Sub` have been cut from this PR. - added `LinearRgba` and `Srgba` `RED/GREEN/BLUE` - added `LinearRgba_to_f32_array` and `LinearRgba::to_u32` ## Migration Guide Bevy's color types have changed! Wherever you used a `bevy::render::Color`, a `bevy::color::Color` is used instead. These are quite similar! Both are enums storing a color in a specific color space (or to be more precise, using a specific color model). However, each of the different color models now has its own type. TODO... - `Color::rgba`, `Color::rgb`, `Color::rbga_u8`, `Color::rgb_u8`, `Color::rgb_from_array` are now `Color::srgba`, `Color::srgb`, `Color::srgba_u8`, `Color::srgb_u8` and `Color::srgb_from_array`. - `Color::set_a` and `Color::a` is now `Color::set_alpha` and `Color::alpha`. These are part of the `Alpha` trait in `bevy_color`. - `Color::is_fully_transparent` is now part of the `Alpha` trait in `bevy_color` - `Color::r`, `Color::set_r`, `Color::with_r` and the equivalents for `g`, `b` `h`, `s` and `l` have been removed due to causing silent relatively expensive conversions. Convert your `Color` into the desired color space, perform your operations there, and then convert it back into a polymorphic `Color` enum. - `Color::hex` is now `Srgba::hex`. Call `.into` or construct a `Color::Srgba` variant manually to convert it. - `WireframeMaterial`, `ExtractedUiNode`, `ExtractedDirectionalLight`, `ExtractedPointLight`, `ExtractedSpotLight` and `ExtractedSprite` now store a `LinearRgba`, rather than a polymorphic `Color` - `Color::rgb_linear` and `Color::rgba_linear` are now `Color::linear_rgb` and `Color::linear_rgba` - The various CSS color constants are no longer stored directly on `Color`. Instead, they're defined in the `Srgba` color space, and accessed via `bevy::color::palettes::css`. Call `.into()` on them to convert them into a `Color` for quick debugging use, and consider using the much prettier `tailwind` palette for prototyping. - The `LIME_GREEN` color has been renamed to `LIMEGREEN` to comply with the standard naming. - Vector field arithmetic operations on `Color` (add, subtract, multiply and divide by a f32) have been removed. Instead, convert your colors into `LinearRgba` space, and perform your operations explicitly there. This is particularly relevant when working with emissive or HDR colors, whose color channel values are routinely outside of the ordinary 0 to 1 range. - `Color::as_linear_rgba_f32` has been removed. Call `LinearRgba::to_f32_array` instead, converting if needed. - `Color::as_linear_rgba_u32` has been removed. Call `LinearRgba::to_u32` instead, converting if needed. - Several other color conversion methods to transform LCH or HSL colors into float arrays or `Vec` types have been removed. Please reimplement these externally or open a PR to re-add them if you found them particularly useful. - Various methods on `Color` such as `rgb` or `hsl` to convert the color into a specific color space have been removed. Convert into `LinearRgba`, then to the color space of your choice. - Various implicitly-converting color value methods on `Color` such as `r`, `g`, `b` or `h` have been removed. Please convert it into the color space of your choice, then check these properties. - `Color` no longer implements `AsBindGroup`. Store a `LinearRgba` internally instead to avoid conversion costs. --------- Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com> Co-authored-by: Afonso Lage <lage.afonso@gmail.com> Co-authored-by: Rob Parrett <robparrett@gmail.com> Co-authored-by: Zachary Harrold <zac@harrold.com.au>
347 lines
10 KiB
Rust
347 lines
10 KiB
Rust
//! This example displays each contributor to the bevy source code as a bouncing bevy-ball.
|
|
|
|
use bevy::{
|
|
prelude::*,
|
|
utils::{thiserror, HashSet},
|
|
};
|
|
use rand::{prelude::SliceRandom, Rng};
|
|
use std::{
|
|
env::VarError,
|
|
io::{self, BufRead, BufReader},
|
|
process::Stdio,
|
|
};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.init_resource::<SelectionState>()
|
|
.add_systems(Startup, (setup_contributor_selection, setup))
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
velocity_system,
|
|
move_system,
|
|
collision_system,
|
|
select_system,
|
|
),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
// Store contributors in a collection that preserves the uniqueness
|
|
type Contributors = HashSet<String>;
|
|
|
|
#[derive(Resource)]
|
|
struct ContributorSelection {
|
|
order: Vec<Entity>,
|
|
idx: usize,
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
struct SelectionState {
|
|
timer: Timer,
|
|
has_triggered: bool,
|
|
}
|
|
|
|
impl Default for SelectionState {
|
|
fn default() -> Self {
|
|
Self {
|
|
timer: Timer::from_seconds(SHOWCASE_TIMER_SECS, TimerMode::Repeating),
|
|
has_triggered: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct ContributorDisplay;
|
|
|
|
#[derive(Component)]
|
|
struct Contributor {
|
|
name: String,
|
|
hue: f32,
|
|
}
|
|
|
|
#[derive(Component)]
|
|
struct Velocity {
|
|
translation: Vec3,
|
|
rotation: f32,
|
|
}
|
|
|
|
const GRAVITY: f32 = 9.821 * 100.0;
|
|
const SPRITE_SIZE: f32 = 75.0;
|
|
|
|
const SATURATION_DESELECTED: f32 = 0.3;
|
|
const LIGHTNESS_DESELECTED: f32 = 0.2;
|
|
const SATURATION_SELECTED: f32 = 0.9;
|
|
const LIGHTNESS_SELECTED: f32 = 0.7;
|
|
const ALPHA: f32 = 0.92;
|
|
|
|
const SHOWCASE_TIMER_SECS: f32 = 3.0;
|
|
|
|
const CONTRIBUTORS_LIST: &[&str] = &["Carter Anderson", "And Many More"];
|
|
|
|
fn setup_contributor_selection(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
// Load contributors from the git history log or use default values from
|
|
// the constant array. Contributors must be unique, so they are stored in a HashSet
|
|
let contribs = contributors().unwrap_or_else(|_| {
|
|
CONTRIBUTORS_LIST
|
|
.iter()
|
|
.map(|name| name.to_string())
|
|
.collect()
|
|
});
|
|
|
|
let texture_handle = asset_server.load("branding/icon.png");
|
|
|
|
let mut contributor_selection = ContributorSelection {
|
|
order: Vec::with_capacity(contribs.len()),
|
|
idx: 0,
|
|
};
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
for name in contribs {
|
|
let pos = (rng.gen_range(-400.0..400.0), rng.gen_range(0.0..400.0));
|
|
let dir = rng.gen_range(-1.0..1.0);
|
|
let velocity = Vec3::new(dir * 500.0, 0.0, 0.0);
|
|
let hue = rng.gen_range(0.0..=360.0);
|
|
|
|
// some sprites should be flipped
|
|
let flipped = rng.gen_bool(0.5);
|
|
|
|
let transform = Transform::from_xyz(pos.0, pos.1, 0.0);
|
|
|
|
let entity = commands
|
|
.spawn((
|
|
Contributor { name, hue },
|
|
Velocity {
|
|
translation: velocity,
|
|
rotation: -dir * 5.0,
|
|
},
|
|
SpriteBundle {
|
|
sprite: Sprite {
|
|
custom_size: Some(Vec2::new(1.0, 1.0) * SPRITE_SIZE),
|
|
color: Color::hsla(hue, SATURATION_DESELECTED, LIGHTNESS_DESELECTED, ALPHA),
|
|
flip_x: flipped,
|
|
..default()
|
|
},
|
|
texture: texture_handle.clone(),
|
|
transform,
|
|
..default()
|
|
},
|
|
))
|
|
.id();
|
|
|
|
contributor_selection.order.push(entity);
|
|
}
|
|
|
|
contributor_selection.order.shuffle(&mut rng);
|
|
|
|
commands.insert_resource(contributor_selection);
|
|
}
|
|
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
commands.spawn(Camera2dBundle::default());
|
|
|
|
commands.spawn((
|
|
TextBundle::from_sections([
|
|
TextSection::new(
|
|
"Contributor showcase",
|
|
TextStyle {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
font_size: 60.0,
|
|
..default()
|
|
},
|
|
),
|
|
TextSection::from_style(TextStyle {
|
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
font_size: 60.0,
|
|
..default()
|
|
}),
|
|
])
|
|
.with_style(Style {
|
|
align_self: AlignSelf::FlexEnd,
|
|
..default()
|
|
}),
|
|
ContributorDisplay,
|
|
));
|
|
}
|
|
|
|
/// Finds the next contributor to display and selects the entity
|
|
fn select_system(
|
|
mut timer: ResMut<SelectionState>,
|
|
mut contributor_selection: ResMut<ContributorSelection>,
|
|
mut text_query: Query<&mut Text, With<ContributorDisplay>>,
|
|
mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>,
|
|
time: Res<Time>,
|
|
) {
|
|
if !timer.timer.tick(time.delta()).just_finished() {
|
|
return;
|
|
}
|
|
if !timer.has_triggered {
|
|
let mut text = text_query.single_mut();
|
|
text.sections[0].value = "Contributor: ".to_string();
|
|
|
|
timer.has_triggered = true;
|
|
}
|
|
|
|
let entity = contributor_selection.order[contributor_selection.idx];
|
|
if let Ok((contributor, mut sprite, mut transform)) = query.get_mut(entity) {
|
|
deselect(&mut sprite, contributor, &mut transform);
|
|
}
|
|
|
|
if (contributor_selection.idx + 1) < contributor_selection.order.len() {
|
|
contributor_selection.idx += 1;
|
|
} else {
|
|
contributor_selection.idx = 0;
|
|
}
|
|
|
|
let entity = contributor_selection.order[contributor_selection.idx];
|
|
|
|
if let Ok((contributor, mut sprite, mut transform)) = query.get_mut(entity) {
|
|
let mut text = text_query.single_mut();
|
|
select(&mut sprite, contributor, &mut transform, &mut text);
|
|
}
|
|
}
|
|
|
|
/// Change the tint color to the "selected" color, bring the object to the front
|
|
/// and display the name.
|
|
fn select(
|
|
sprite: &mut Sprite,
|
|
contributor: &Contributor,
|
|
transform: &mut Transform,
|
|
text: &mut Text,
|
|
) {
|
|
sprite.color = Color::hsla(
|
|
contributor.hue,
|
|
SATURATION_SELECTED,
|
|
LIGHTNESS_SELECTED,
|
|
ALPHA,
|
|
);
|
|
|
|
transform.translation.z = 100.0;
|
|
|
|
text.sections[1].value.clone_from(&contributor.name);
|
|
text.sections[1].style.color = sprite.color;
|
|
}
|
|
|
|
/// Change the modulate color to the "deselected" color and push
|
|
/// the object to the back.
|
|
fn deselect(sprite: &mut Sprite, contributor: &Contributor, transform: &mut Transform) {
|
|
sprite.color = Color::hsla(
|
|
contributor.hue,
|
|
SATURATION_DESELECTED,
|
|
LIGHTNESS_DESELECTED,
|
|
ALPHA,
|
|
);
|
|
|
|
transform.translation.z = 0.0;
|
|
}
|
|
|
|
/// Applies gravity to all entities with velocity
|
|
fn velocity_system(time: Res<Time>, mut velocity_query: Query<&mut Velocity>) {
|
|
let delta = time.delta_seconds();
|
|
|
|
for mut velocity in &mut velocity_query {
|
|
velocity.translation.y -= GRAVITY * delta;
|
|
}
|
|
}
|
|
|
|
/// Checks for collisions of contributor-birds.
|
|
///
|
|
/// On collision with left-or-right wall it resets the horizontal
|
|
/// velocity. On collision with the ground it applies an upwards
|
|
/// force.
|
|
fn collision_system(
|
|
windows: Query<&Window>,
|
|
mut query: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
|
|
) {
|
|
let window = windows.single();
|
|
|
|
let ceiling = window.height() / 2.;
|
|
let ground = -window.height() / 2.;
|
|
|
|
let wall_left = -window.width() / 2.;
|
|
let wall_right = window.width() / 2.;
|
|
|
|
// The maximum height the birbs should try to reach is one birb below the top of the window.
|
|
let max_bounce_height = (window.height() - SPRITE_SIZE * 2.0).max(0.0);
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
for (mut velocity, mut transform) in &mut query {
|
|
let left = transform.translation.x - SPRITE_SIZE / 2.0;
|
|
let right = transform.translation.x + SPRITE_SIZE / 2.0;
|
|
let top = transform.translation.y + SPRITE_SIZE / 2.0;
|
|
let bottom = transform.translation.y - SPRITE_SIZE / 2.0;
|
|
|
|
// clamp the translation to not go out of the bounds
|
|
if bottom < ground {
|
|
transform.translation.y = ground + SPRITE_SIZE / 2.0;
|
|
|
|
// How high this birb will bounce.
|
|
let bounce_height = rng.gen_range((max_bounce_height * 0.4)..=max_bounce_height);
|
|
|
|
// Apply the velocity that would bounce the birb up to bounce_height.
|
|
velocity.translation.y = (bounce_height * GRAVITY * 2.).sqrt();
|
|
}
|
|
if top > ceiling {
|
|
transform.translation.y = ceiling - SPRITE_SIZE / 2.0;
|
|
velocity.translation.y *= -1.0;
|
|
}
|
|
// on side walls flip the horizontal velocity
|
|
if left < wall_left {
|
|
transform.translation.x = wall_left + SPRITE_SIZE / 2.0;
|
|
velocity.translation.x *= -1.0;
|
|
velocity.rotation *= -1.0;
|
|
}
|
|
if right > wall_right {
|
|
transform.translation.x = wall_right - SPRITE_SIZE / 2.0;
|
|
velocity.translation.x *= -1.0;
|
|
velocity.rotation *= -1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Apply velocity to positions and rotations.
|
|
fn move_system(time: Res<Time>, mut query: Query<(&Velocity, &mut Transform)>) {
|
|
let delta = time.delta_seconds();
|
|
|
|
for (velocity, mut transform) in &mut query {
|
|
transform.translation += delta * velocity.translation;
|
|
transform.rotate_z(velocity.rotation * delta);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
enum LoadContributorsError {
|
|
#[error("An IO error occurred while reading the git log.")]
|
|
Io(#[from] io::Error),
|
|
#[error("The CARGO_MANIFEST_DIR environment variable was not set.")]
|
|
Var(#[from] VarError),
|
|
#[error("The git process did not return a stdout handle.")]
|
|
Stdout,
|
|
}
|
|
|
|
/// Get the names of all contributors from the git log.
|
|
///
|
|
/// The names are deduplicated.
|
|
/// This function only works if `git` is installed and
|
|
/// the program is run through `cargo`.
|
|
fn contributors() -> Result<Contributors, LoadContributorsError> {
|
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
|
|
|
|
let mut cmd = std::process::Command::new("git")
|
|
.args(["--no-pager", "log", "--pretty=format:%an"])
|
|
.current_dir(manifest_dir)
|
|
.stdout(Stdio::piped())
|
|
.spawn()?;
|
|
|
|
let stdout = cmd.stdout.take().ok_or(LoadContributorsError::Stdout)?;
|
|
|
|
let contributors = BufReader::new(stdout)
|
|
.lines()
|
|
.map_while(|x| x.ok())
|
|
.collect();
|
|
|
|
Ok(contributors)
|
|
}
|