-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
flappy bevy example #4923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
flappy bevy example #4923
Changes from 6 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
a4e2809
flappy bevy example
spamulo 714a954
updated README.md for flappy_bevy example
spamulo d7ec550
flappy bevy example: satisfying clippy
spamulo 2964849
flappy bevy example style fix
spamulo 35c5f44
bevy example - format
spamulo 79a034b
remove randf, floor bird cull, tuning, comments
spamulo 38a8f72
enforce MAX_BIRDS with iter().count()
spamulo edb0bce
cargo fmt
spamulo 627f872
satisfy clippy
spamulo 23857ac
gap variability into constant
spamulo 2e4f50a
autoflap, respawn, fix birds dropping, comments
spamulo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,281 @@ | ||
| //! A simplified Flappy Bird but with many birds. Press space to flap. | ||
|
|
||
| use bevy::prelude::*; | ||
|
|
||
| use bevy::sprite::collide_aabb::{collide, Collision}; | ||
| use bevy::window::PresentMode; | ||
|
|
||
| use rand::random; | ||
|
|
||
| const CHUNK_SIZE: f32 = 300.0; | ||
| const CAMERA_SPEED: f32 = 120.0; | ||
| const GAME_HEIGHT: f32 = 500.0; | ||
| const SCREEN_HEIGHT: f32 = 1500.0; | ||
| const CLEANUP_X_DIST: f32 = 1500.0; | ||
| const BROWNIAN_DRIFT_AMOUNT_X: f32 = 250.0; | ||
| const BROWNIAN_DRIFT_AMOUNT_Y: f32 = 600.0; | ||
| const DRIFT_TO_CENTER_AMOUNT: f32 = 0.1; | ||
| const FLAP_STRENGTH: f32 = 240.0; | ||
| const BIRD_SIZE: f32 = 24.0; | ||
| const BIRD_REPRODUCTION_CHANCE: f32 = 1.0; | ||
| const MAX_BIRDS: usize = 500; | ||
| const GRAVITY: f32 = 400.0; | ||
|
|
||
| pub fn main() { | ||
| let mut app = App::new(); | ||
|
|
||
| app.insert_resource(WindowDescriptor { | ||
| width: 1600., | ||
| height: 900., | ||
| title: "Flappy Bevy".to_string(), | ||
| present_mode: PresentMode::Immediate, // smooth but power hungry | ||
| resizable: true, | ||
| ..Default::default() | ||
| }) | ||
| .add_plugins(DefaultPlugins) | ||
| .add_startup_system(spawn_camera) | ||
| .add_startup_system(load_art) | ||
| .add_startup_system(bird_startup) | ||
| .add_system(spawn_bird) | ||
| .add_system(bird_collision) | ||
| .add_system(bird_reproduction) | ||
| .add_system(bird_control.after(gravity)) | ||
| .add_system(terrain_gen) | ||
| .add_system(advance_camera) | ||
| .add_system(brownian_drift) | ||
| .add_system(velocity) | ||
| .add_system(gravity) | ||
| .add_system(terrain_cleanup) | ||
| .add_system(drift_to_center) | ||
| .add_event::<GenerateChunk>() | ||
| .add_event::<SpawnBird>(); | ||
|
|
||
| app.run(); | ||
| } | ||
|
|
||
| pub struct Art { | ||
| bird_icon: Handle<Image>, | ||
| } | ||
|
|
||
| fn load_art(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
| commands.insert_resource(Art { | ||
| bird_icon: asset_server.load("branding/icon.png"), | ||
| }); | ||
| } | ||
| struct GenerateChunk { | ||
| new_chunk_index: i32, | ||
| } | ||
|
|
||
| fn advance_camera( | ||
| mut q: Query<&mut Transform, With<Camera>>, | ||
| time: Res<Time>, | ||
| mut outgoing_generate_chunk_events: EventWriter<GenerateChunk>, | ||
| ) { | ||
| if let Ok(mut transform) = q.get_single_mut() { | ||
| let chunk_index = (transform.translation.x / CHUNK_SIZE).floor() as i32; | ||
| transform.translation.x += time.delta().as_secs_f32() * CAMERA_SPEED; | ||
| let new_chunk_index = (transform.translation.x / CHUNK_SIZE).floor() as i32; | ||
| // if the camera has moved over a chunk boundary | ||
| if chunk_index != new_chunk_index { | ||
| // generate a new chunk offscreen to the right | ||
| outgoing_generate_chunk_events.send(GenerateChunk { | ||
| new_chunk_index: new_chunk_index + 3, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Component)] | ||
| struct Obstacle; | ||
|
|
||
| fn terrain_cleanup( | ||
| mut commands: Commands, | ||
| q: Query<(Entity, &Transform), With<Obstacle>>, | ||
| cam: Query<&Transform, With<Camera>>, | ||
| ) { | ||
| let cam_x = cam.single().translation.x; | ||
| for (e, t) in q.iter() { | ||
| // remove obstacles at the left | ||
| if t.translation.x < cam_x - CLEANUP_X_DIST { | ||
| commands.entity(e).despawn(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn terrain_gen( | ||
| mut commands: Commands, | ||
| mut incoming_generate_chunk_events: EventReader<GenerateChunk>, | ||
| ) { | ||
| for ev in incoming_generate_chunk_events.iter() { | ||
| let x_pos = CHUNK_SIZE * ev.new_chunk_index as f32; | ||
| // generate some terrain within x_pos..x_pos+width | ||
| let gap_y_pos = GAME_HEIGHT * (random::<f32>() - 0.5) * 0.9; | ||
| let pillar_width = 50.0 + 110.0 * random::<f32>(); | ||
| // make the gap no narrower than the pillar is wide | ||
| let gap_size = (65.0 + 250.0 * random::<f32>()).max(pillar_width); | ||
| for (top_y_pos, bottom_y_pos) in [ | ||
| (-SCREEN_HEIGHT * 0.5, gap_y_pos - gap_size * 0.5), | ||
| (gap_y_pos + gap_size * 0.5, SCREEN_HEIGHT * 0.5), | ||
| ] { | ||
| let pillar_origin = Vec2::new(x_pos, (top_y_pos + bottom_y_pos) * 0.5); | ||
| let pillar_size = Vec2::new(pillar_width, bottom_y_pos - top_y_pos); | ||
| commands | ||
| .spawn_bundle(SpriteBundle { | ||
| sprite: Sprite { | ||
| color: Color::rgb(0.25, 0.25, 0.75), | ||
| custom_size: Some(pillar_size), | ||
| ..default() | ||
| }, | ||
| transform: Transform::from_translation(pillar_origin.extend(0.0)), | ||
| ..default() | ||
| }) | ||
| .insert(Obstacle); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn spawn_camera(mut commands: Commands) { | ||
| commands.spawn_bundle(Camera2dBundle::default()); | ||
| } | ||
|
|
||
| #[derive(Component, Default)] | ||
| struct Velocity { | ||
| velocity: Vec2, | ||
| } | ||
|
|
||
| #[derive(Component)] | ||
| struct BrownianDrift; | ||
|
|
||
| #[derive(Component)] | ||
| struct Gravity; | ||
|
|
||
| #[derive(Component)] | ||
| struct Bird; | ||
|
|
||
| #[derive(Component)] | ||
| struct DriftToCenter; | ||
|
|
||
| /// Event that causes a new bird to spawn | ||
| struct SpawnBird { | ||
af-games marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| new_bird_pos: Vec2, | ||
| new_bird_velocity: Vec2, | ||
| } | ||
|
|
||
| fn bird_startup(mut spawn_bird_events: EventWriter<SpawnBird>) { | ||
| spawn_bird_events.send(SpawnBird { | ||
| new_bird_pos: Vec2::ZERO, | ||
| new_bird_velocity: Vec2::new(CAMERA_SPEED, 0.0), | ||
| }); | ||
| } | ||
|
|
||
| fn spawn_bird( | ||
| mut commands: Commands, | ||
| mut spawn_bird_events: EventReader<SpawnBird>, | ||
| art: Res<Art>, | ||
| ) { | ||
| for ev in spawn_bird_events.iter() { | ||
| commands | ||
| .spawn_bundle(SpriteBundle { | ||
| sprite: Sprite { | ||
| color: Color::rgb(random::<f32>(), random::<f32>(), random::<f32>()), | ||
| custom_size: Some(Vec2::splat(BIRD_SIZE)), | ||
| ..default() | ||
| }, | ||
| texture: art.bird_icon.clone(), | ||
| transform: Transform::from_translation(ev.new_bird_pos.extend(random::<f32>() * 100.0)), | ||
| ..default() | ||
| }) | ||
| .insert(Velocity { | ||
| velocity: ev.new_bird_velocity, | ||
| }) | ||
| .insert(Gravity) | ||
| .insert(Bird) | ||
| .insert(BrownianDrift) | ||
| .insert(DriftToCenter); | ||
| } | ||
| } | ||
|
|
||
| /// The flock has a tendency to drift offscreen - gently bring it back to the center | ||
| fn drift_to_center( | ||
| mut q: Query<(&mut Velocity, &Transform)>, | ||
| cam: Query<&Transform, With<Camera>>, | ||
| time: Res<Time>, | ||
| ) { | ||
| for (mut v, t) in q.iter_mut() { | ||
| v.velocity.x -= (t.translation.x - cam.single().translation.x) | ||
| * DRIFT_TO_CENTER_AMOUNT | ||
| * time.delta().as_secs_f32(); | ||
| } | ||
| } | ||
|
|
||
| fn velocity(mut q: Query<(&Velocity, &mut Transform)>, time: Res<Time>) { | ||
| for (v, mut t) in q.iter_mut() { | ||
| t.translation += (v.velocity * time.delta().as_secs_f32()).extend(0.0); | ||
| } | ||
| } | ||
|
|
||
| fn gravity(mut q: Query<&mut Velocity, With<Gravity>>, time: Res<Time>) { | ||
| for mut v in q.iter_mut() { | ||
| v.velocity.y -= time.delta().as_secs_f32() * GRAVITY; | ||
| } | ||
| } | ||
|
|
||
| fn bird_collision( | ||
| mut commands: Commands, | ||
| birds: Query<(Entity, &Sprite, &Transform), With<Bird>>, | ||
| obstacles: Query<(&Sprite, &Transform), With<Obstacle>>, | ||
| ) { | ||
| for (bird_entity, bird_sprite, bird_transform) in birds.iter() { | ||
| let mut collision_result: Option<Collision> = None; | ||
| for (obstacle_sprite, obstacle_transform) in obstacles.iter() { | ||
| collision_result = collision_result.or(collide( | ||
| bird_transform.translation, | ||
| bird_sprite.custom_size.unwrap(), | ||
| obstacle_transform.translation, | ||
| obstacle_sprite.custom_size.unwrap(), | ||
| )); | ||
| } | ||
| if collision_result.is_some() || bird_transform.translation.y < -SCREEN_HEIGHT * 0.5 { | ||
| commands.entity(bird_entity).despawn(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn bird_reproduction( | ||
af-games marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| q: Query<(&Transform, &Velocity), With<Bird>>, | ||
| time: Res<Time>, | ||
| mut spawn_bird_events: EventWriter<SpawnBird>, | ||
| ) { | ||
| let mut bird_count = 0; | ||
| for (_t, _v) in q.iter() { | ||
af-games marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| bird_count += 1; | ||
| } | ||
| if bird_count < MAX_BIRDS { | ||
| for (t, v) in q.iter() { | ||
| if random::<f32>() < BIRD_REPRODUCTION_CHANCE * time.delta().as_secs_f32() | ||
| { | ||
| spawn_bird_events.send(SpawnBird { | ||
| new_bird_pos: t.translation.truncate(), | ||
| new_bird_velocity: v.velocity, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn brownian_drift(mut q: Query<&mut Velocity, With<BrownianDrift>>, time: Res<Time>) { | ||
| for mut v in q.iter_mut() { | ||
| v.velocity += Vec2::new( | ||
| (random::<f32>() - 0.5) * BROWNIAN_DRIFT_AMOUNT_X, | ||
| (random::<f32>() - 0.5) * BROWNIAN_DRIFT_AMOUNT_Y, | ||
| ) * time.delta().as_secs_f32(); | ||
| } | ||
| } | ||
|
|
||
| fn bird_control(input: Res<Input<KeyCode>>, mut birds: Query<&mut Velocity, With<Bird>>) { | ||
| if input.just_pressed(KeyCode::Space) { | ||
| for mut v in birds.iter_mut() { | ||
| v.velocity.y = FLAP_STRENGTH; | ||
| } | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.