Skip to content

Commit

Permalink
add example that represents contributors as bevy icons
Browse files Browse the repository at this point in the history
  • Loading branch information
karroffel committed Nov 6, 2020
1 parent 26be22e commit ba4453a
Show file tree
Hide file tree
Showing 2 changed files with 319 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
315 changes: 315 additions & 0 deletions examples/2d/contributors.rs
Original file line number Diff line number Diff line change
@@ -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<String>;

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 = 6.0;

fn setup(
mut cmd: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
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<Assets<ColorMaterial>>,
mut sel: ResMut<ContributorSelection>,
mut dq: Query<(&ContributorDisplay, Mut<Text>)>,
mut tq: Query<(&SelectTimer, Mut<Timer>)>,
mut q: Query<(&Contributor, &Handle<ColorMaterial>, &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 >= sel.order.len() {
sel.idx = 0;
} else {
sel.idx += 1;
}

{
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<ColorMaterial>,
mat_handle: Handle<ColorMaterial>,
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<ColorMaterial>,
mat_handle: Handle<ColorMaterial>,
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<Time>, mut q: Query<Mut<Velocity>>) {
let delta = time.delta_seconds;

for mut v in q.iter_mut() {
v.translation += Vec3::new(0.0, GRAVITY * delta, 0.0);
}
}

/// 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(
wins: Res<Windows>,
mut q: Query<(&Contributor, Mut<Velocity>, Mut<Transform>)>,
) {
let mut rnd = rand::thread_rng();

let win = wins.get_primary().unwrap();

let ceiling = (win.height() / 2) as f32;
let ground = -((win.height() / 2) as f32);

let wall_left = -((win.width() / 2) as f32);
let wall_right = (win.width() / 2) as f32;

for (_, mut v, mut t) in q.iter_mut() {
let left = t.translation.x() - SPRITE_SIZE / 2.0;
let right = t.translation.x() + SPRITE_SIZE / 2.0;
let top = t.translation.y() + SPRITE_SIZE / 2.0;
let bottom = t.translation.y() - SPRITE_SIZE / 2.0;

// clamp the translation to not go out of the bounds
if bottom < ground {
t.translation.set_y(ground + SPRITE_SIZE / 2.0);
// apply an impulse upwards
*v.translation.y_mut() = rnd.gen_range(700.0, 1000.0);
}
if top > ceiling {
t.translation.set_y(ceiling - SPRITE_SIZE / 2.0);
}
// on side walls flip the horizontal velocity
if left < wall_left {
t.translation.set_x(wall_left + SPRITE_SIZE / 2.0);
*v.translation.x_mut() *= -1.0;
v.rotation *= -1.0;
}
if right > wall_right {
t.translation.set_x(wall_right - SPRITE_SIZE / 2.0);
*v.translation.x_mut() *= -1.0;
v.rotation *= -1.0;
}
}
}

/// Apply velocity to positions and rotations.
fn move_system(time: Res<Time>, mut q: Query<(&Velocity, Mut<Transform>)>) {
let delta = time.delta_seconds;

for (v, mut t) in q.iter_mut() {
t.translation += delta * v.translation;
t.rotate(Quat::from_rotation_z(v.rotation * delta));
}
}

/// 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() -> Contributors {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.expect("This example needs to run through `cargo run --example`");

let mut cmd = std::process::Command::new("git")
.args(&["--no-pager", "log", "--pretty=format:%an"])
.current_dir(manifest_dir)
.stdout(Stdio::piped())
.spawn()
.expect("git needs to be installed");

let stdout = cmd.stdout.take().expect("Child should have a stdout");

BufReader::new(stdout)
.lines()
.filter_map(|x| x.ok())
.collect()
}

/// Generate a color modulation
///
/// Because there is no `Mul<Color> for Color` instead `[f32; 3]` is
/// used.
fn gen_color(rng: &mut impl Rng) -> [f32; 3] {
let r = rng.gen_range(0.2, 1.0);
let g = rng.gen_range(0.2, 1.0);
let b = rng.gen_range(0.2, 1.0);
let v = Vec3::new(r, g, b);
v.normalize().into()
}

0 comments on commit ba4453a

Please sign in to comment.