//! This example displays each contributor to the bevy source code as a bouncing bevy-ball. use bevy::{prelude::*, utils::HashSet}; use rand::{prelude::SliceRandom, Rng}; use std::{ env::VarError, io::{self, BufRead, BufReader}, process::Stdio, }; fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup_contributor_selection) .add_startup_system(setup) .add_system(velocity_system) .add_system(move_system) .add_system(collision_system) .add_system(select_system) .init_resource::() .run(); } // Store contributors in a collection that preserves the uniqueness type Contributors = HashSet; struct ContributorSelection { order: Vec, idx: usize, } struct SelectionState { timer: Timer, has_triggered: bool, } impl Default for SelectionState { fn default() -> Self { Self { timer: Timer::from_seconds(SHOWCASE_TIMER_SECS, true), 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) { // 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() .insert_bundle(( Contributor { name, hue }, Velocity { translation: velocity, rotation: -dir * 5.0, }, )) .insert_bundle(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) { commands.spawn_bundle(OrthographicCameraBundle::new_2d()); commands.spawn_bundle(UiCameraBundle::default()); commands .spawn() .insert(ContributorDisplay) .insert_bundle(TextBundle { style: Style { align_self: AlignSelf::FlexEnd, ..default() }, text: Text { sections: vec![ TextSection { value: "Contributor showcase".to_string(), style: TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 60.0, color: Color::WHITE, }, }, TextSection { value: "".to_string(), style: TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 60.0, color: Color::WHITE, }, }, ], ..default() }, ..default() }); } /// Finds the next contributor to display and selects the entity fn select_system( mut timer: ResMut, mut contributor_selection: ResMut, mut text_query: Query<&mut Text, With>, mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>, time: Res