bevy/examples/games/contributors.rs
Mark Schmale 1ba7429371 Doc/module style doc blocks for examples (#4438)
# Objective

Provide a starting point for #3951, or a partial solution. 
Providing a few comment blocks to discuss, and hopefully find better one in the process. 

## Solution

Since I am pretty new to pretty much anything in this context, I figured I'd just start with a draft for some file level doc blocks. For some of them I found more relevant details (or at least things I considered interessting), for some others there is less. 

## Changelog

- Moved some existing comments from main() functions in the 2d examples to the file header level
- Wrote some more comment blocks for most other 2d examples

TODO: 
- [x] 2d/sprite_sheet, wasnt able to come up with something good yet 
- [x] all other example groups...


Also: Please let me know if the commit style is okay, or to verbose. I could certainly squash these things, or add more details if needed. 
I also hope its okay to raise this PR this early, with just a few files changed. Took me long enough and I dont wanted to let it go to waste because I lost motivation to do the whole thing. Additionally I am somewhat uncertain over the style and contents of the commets. So let me know what you thing please.
2022-05-16 13:53:20 +00:00

338 lines
10 KiB
Rust

//! 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::<SelectionState>()
.run();
}
// Store contributors in a collection that preserves the uniqueness
type Contributors = HashSet<String>;
struct ContributorSelection {
order: Vec<Entity>,
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<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()
.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<AssetServer>) {
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<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" colour 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 velocity_query.iter_mut() {
velocity.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(
windows: Res<Windows>,
mut query: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
) {
let mut rng = rand::thread_rng();
let window = windows.primary();
let ceiling = window.height() / 2.;
let ground = -(window.height() / 2.);
let wall_left = -(window.width() / 2.);
let wall_right = window.width() / 2.;
for (mut velocity, mut transform) in query.iter_mut() {
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;
// apply an impulse upwards
velocity.translation.y = rng.gen_range(700.0..1000.0);
}
if top > ceiling {
transform.translation.y = ceiling - SPRITE_SIZE / 2.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 query.iter_mut() {
transform.translation += delta * velocity.translation;
transform.rotate(Quat::from_rotation_z(velocity.rotation * delta));
}
}
enum LoadContributorsError {
IO(io::Error),
Var(VarError),
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").map_err(LoadContributorsError::Var)?;
let mut cmd = std::process::Command::new("git")
.args(&["--no-pager", "log", "--pretty=format:%an"])
.current_dir(manifest_dir)
.stdout(Stdio::piped())
.spawn()
.map_err(LoadContributorsError::IO)?;
let stdout = cmd.stdout.take().ok_or(LoadContributorsError::Stdout)?;
let contributors = BufReader::new(stdout)
.lines()
.filter_map(|x| x.ok())
.collect();
Ok(contributors)
}