//! This example displays each contributor to the bevy source code as a bouncing bevy-ball. use bevy::{math::bounding::Aabb2d, prelude::*}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; use std::{ collections::HashMap, env::VarError, hash::{DefaultHasher, Hash, Hasher}, io::{self, BufRead, BufReader}, process::Stdio, }; fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() .init_resource::() .add_systems(Startup, (setup_contributor_selection, setup)) // Systems are chained for determinism only .add_systems(Update, (gravity, movement, collisions, selection).chain()) .run(); } type Contributors = Vec<(String, usize)>; #[derive(Resource)] struct ContributorSelection { order: Vec, idx: usize, } #[derive(Resource)] struct SelectionTimer(Timer); impl Default for SelectionTimer { fn default() -> Self { Self(Timer::from_seconds( SHOWCASE_TIMER_SECS, TimerMode::Repeating, )) } } #[derive(Component)] struct ContributorDisplay; #[derive(Component)] struct Contributor { name: String, num_commits: usize, hue: f32, } #[derive(Component)] struct Velocity { translation: Vec3, rotation: f32, } // We're using a shared seeded RNG 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. #[derive(Resource, Deref, DerefMut)] struct SharedRng(ChaCha8Rng); impl Default for SharedRng { fn default() -> Self { Self(ChaCha8Rng::seed_from_u64(10223163112)) } } const GRAVITY: f32 = 9.821 * 100.0; const SPRITE_SIZE: f32 = 75.0; const SELECTED: Hsla = Hsla::hsl(0.0, 0.9, 0.7); const DESELECTED: Hsla = Hsla::new(0.0, 0.3, 0.2, 0.92); const SELECTED_Z_OFFSET: f32 = 100.0; 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, mut rng: ResMut, ) { let contribs = contributors_or_fallback(); let texture_handle = asset_server.load("branding/icon.png"); let mut contributor_selection = ContributorSelection { order: Vec::with_capacity(contribs.len()), idx: 0, }; for (name, num_commits) in contribs { let transform = Transform::from_xyz( rng.gen_range(-400.0..400.0), rng.gen_range(0.0..400.0), rng.gen(), ); let dir = rng.gen_range(-1.0..1.0); let velocity = Vec3::new(dir * 500.0, 0.0, 0.0); let hue = name_to_hue(&name); // Some sprites should be flipped for variety let flipped = rng.gen(); let entity = commands .spawn(( Contributor { name, num_commits, hue, }, Velocity { translation: velocity, rotation: -dir * 5.0, }, Sprite { image: texture_handle.clone(), custom_size: Some(Vec2::splat(SPRITE_SIZE)), color: DESELECTED.with_hue(hue).into(), flip_x: flipped, ..default() }, transform, )) .id(); contributor_selection.order.push(entity); } commands.insert_resource(contributor_selection); } fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); let text_style = TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 60.0, ..default() }; commands .spawn(( Text::new("Contributor showcase"), text_style.clone(), ContributorDisplay, Node { position_type: PositionType::Absolute, top: Val::Px(12.), left: Val::Px(12.), ..default() }, )) .with_child(( TextSpan::default(), TextFont { font_size: 30., ..text_style }, )); } /// Finds the next contributor to display and selects the entity fn selection( mut timer: ResMut, mut contributor_selection: ResMut, contributor_root: Single, With)>, mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>, mut writer: TextUiWriter, time: Res