diff --git a/Cargo.toml b/Cargo.toml index 8d18c2969c90a..2ce68bbdca142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,10 @@ path = "examples/2d/sprite_sheet.rs" name = "texture_atlas" path = "examples/2d/texture_atlas.rs" +[[example]] +name = "contributors" +path = "examples/2d/contributors.rs" + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" diff --git a/examples/2d/contributors.rs b/examples/2d/contributors.rs new file mode 100644 index 0000000000000..12e3489abf4ea --- /dev/null +++ b/examples/2d/contributors.rs @@ -0,0 +1,315 @@ +use bevy::prelude::*; +use rand::{prelude::SliceRandom, Rng}; +use std::{ + collections::BTreeSet, + io::{BufRead, BufReader}, + process::Stdio, +}; + +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_startup_system(setup.system()) + .add_system(velocity_system.system()) + .add_system(move_system.system()) + .add_system(collision_system.system()) + .add_system(select_system.system()) + .run(); +} + +type Contributors = BTreeSet; + +struct ContributorSelection { + order: Vec<(String, Entity)>, + idx: usize, +} + +struct SelectTimer; + +struct ContributorDisplay; + +struct Contributor { + color: [f32; 3], +} + +struct Velocity { + translation: Vec3, + rotation: f32, +} + +const GRAVITY: f32 = -9.821 * 100.0; +const SPRITE_SIZE: f32 = 75.0; + +const COL_DESELECTED: Color = Color::rgb_linear(0.03, 0.03, 0.03); +const COL_SELECTED: Color = Color::rgb_linear(5.0, 5.0, 5.0); + +const SHOWCASE_TIMER_SECS: f32 = 3.0; + +fn setup( + mut cmd: Commands, + asset_server: Res, + mut materials: ResMut>, +) { + let contribs = contributors(); + + let texture_handle = asset_server.load("branding/icon.png"); + + cmd.spawn(Camera2dComponents::default()) + .spawn(UiCameraComponents::default()); + + let mut sel = ContributorSelection { + order: vec![], + idx: 0, + }; + + let mut rnd = rand::thread_rng(); + + for name in contribs { + let pos = (rnd.gen_range(-400.0, 400.0), rnd.gen_range(0.0, 400.0)); + let dir = rnd.gen_range(-1.0, 1.0); + let velocity = Vec3::new(dir * 500.0, 0.0, 0.0); + let col = gen_color(&mut rnd); + + // some sprites should be flipped + let flipped = rnd.gen_bool(0.5); + + let mut transform = Transform::from_translation(Vec3::new(pos.0, pos.1, 0.0)); + *transform.scale.x_mut() *= if flipped { -1.0 } else { 1.0 }; + + cmd.spawn((Contributor { color: col },)) + .with(Velocity { + translation: velocity, + rotation: -dir * 5.0, + }) + .with_bundle(SpriteComponents { + sprite: Sprite { + size: Vec2::new(1.0, 1.0) * SPRITE_SIZE, + resize_mode: SpriteResizeMode::Manual, + }, + material: materials.add(ColorMaterial { + color: COL_DESELECTED * col, + texture: Some(texture_handle.clone()), + }), + ..Default::default() + }) + .with(transform); + + let e = cmd.current_entity().unwrap(); + + sel.order.push((name, e)); + } + + sel.order.shuffle(&mut rnd); + + cmd.spawn((SelectTimer, Timer::from_seconds(SHOWCASE_TIMER_SECS, true))); + + cmd.spawn((ContributorDisplay,)) + .with_bundle(TextComponents { + style: Style { + align_self: AlignSelf::FlexEnd, + ..Default::default() + }, + text: Text { + value: "Contributor showcase".to_string(), + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + style: TextStyle { + font_size: 60.0, + color: Color::WHITE, + }, + }, + ..Default::default() + }); + + cmd.insert_resource(sel); +} + +/// Finds the next contributor to display and selects the entity +fn select_system( + mut materials: ResMut>, + mut sel: ResMut, + mut dq: Query<(&ContributorDisplay, Mut)>, + mut tq: Query<(&SelectTimer, Mut)>, + mut q: Query<(&Contributor, &Handle, &mut Transform)>, +) { + let mut timer_fired = false; + for (_, mut t) in tq.iter_mut() { + if !t.just_finished { + continue; + } + t.reset(); + timer_fired = true; + } + + if !timer_fired { + return; + } + + let prev = sel.idx; + + if (sel.idx + 1) < sel.order.len() { + sel.idx += 1; + } else { + sel.idx = 0; + } + + { + let (_, e) = &sel.order[prev]; + if let Ok((c, handle, mut tr)) = q.get_mut(*e) { + deselect(&mut *materials, handle.clone(), c, &mut *tr); + } + } + + let (name, e) = &sel.order[sel.idx]; + + if let Ok((c, handle, mut tr)) = q.get_mut(*e) { + for (_, mut text) in dq.iter_mut() { + select( + &mut *materials, + handle.clone(), + c, + &mut *tr, + &mut *text, + name, + ); + } + } +} + +/// Change the modulate color to the "selected" colour, +/// bring the object to the front and display the name. +fn select( + materials: &mut Assets, + mat_handle: Handle, + cont: &Contributor, + trans: &mut Transform, + text: &mut Text, + name: &str, +) -> Option<()> { + let mat = materials.get_mut(mat_handle)?; + mat.color = COL_SELECTED * cont.color; + + trans.translation.set_z(100.0); + + text.value = format!("Contributor: {}", name); + + Some(()) +} + +/// Change the modulate color to the "deselected" colour and push +/// the object to the back. +fn deselect( + materials: &mut Assets, + mat_handle: Handle, + cont: &Contributor, + trans: &mut Transform, +) -> Option<()> { + let mat = materials.get_mut(mat_handle)?; + mat.color = COL_DESELECTED * cont.color; + + trans.translation.set_z(0.0); + + Some(()) +} + +/// Applies gravity to all entities with velocity +fn velocity_system(time: Res