diff --git a/Cargo.toml b/Cargo.toml index 999157fea460d..4e6d7f17f6851 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -432,6 +432,10 @@ path = "examples/games/breakout.rs" name = "contributors" path = "examples/games/contributors.rs" +[[example]] +name = "flappy_bevy" +path = "examples/games/flappy_bevy.rs" + [[example]] name = "game_menu" path = "examples/games/game_menu.rs" diff --git a/examples/README.md b/examples/README.md index 7e83f3fd9dbe8..a7a615e34ee8b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -205,6 +205,7 @@ Example | File | Description `alien_cake_addict` | [`games/alien_cake_addict.rs`](./games/alien_cake_addict.rs) | Eat the cakes. Eat them all. An example 3D game `breakout` | [`games/breakout.rs`](./games/breakout.rs) | An implementation of the classic game "Breakout" `contributors` | [`games/contributors.rs`](./games/contributors.rs) | Displays each contributor as a bouncy bevy-ball! +`flappy_bevy` | [`games/flappy_bevy.rs`](./games/flappy_bevy.rs) | A flappy bird clone but with many birds `game_menu` | [`games/game_menu.rs`](./games/game_menu.rs) | A simple game menu ## Input diff --git a/examples/games/flappy_bevy.rs b/examples/games/flappy_bevy.rs new file mode 100644 index 0000000000000..0b720e3af9b2e --- /dev/null +++ b/examples/games/flappy_bevy.rs @@ -0,0 +1,382 @@ +//! 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.05; +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; +const GAP_VARIABILITY: f32 = 0.9; +const AUTO_FLAP_INTERVAL_SECS: f32 = 1.1; +const MOURN_TIME_SECS: f32 = 0.5; + +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_event::() + .add_event::() + .add_event::() + .insert_resource(AutoFlapState::default()) + .insert_resource(MourningState::default()) + .add_startup_system(load_art) + .add_startup_system(bird_startup) // generates a SpawnBird event + .add_system(spawn_bird) // responds to SpawnBird events + .add_system(input) // generates Flap events + .add_system_to_stage(CoreStage::PostUpdate, flap) // responds to Flap events, ordering prevents physics bug + .add_system(auto_flap) // play the game automatically at the start + .add_system(bird_collision) // despawn birds that collide with pillars or floor + .add_system(bird_reproduction) // slowly grow the flock + .add_system(brownian_drift) // make the flock drift apart + .add_system(velocity) // integrates velocity over time, mutating translation + .add_system(gravity) // makes gravity influence to velocity + .add_system(drift_to_center) // gently return birds to (0, 0) + .add_system(mourn.before(spawn_bird)) // respawn a bird when all die. Ordering necessary because counting birds + .add_system(terrain_gen) // generate pillars off-screen to the right + .add_system(terrain_cleanup) // remove pillars off-screen to the left + .add_startup_system(spawn_camera) + .add_system(advance_camera); // move the camera right at a constant speed + + app.run(); +} + +/// Event that causes a new bird to spawn +struct SpawnBird { + new_bird_pos: Option, + new_bird_velocity: Vec2, +} + +/// Event that causes all birds on screen to flap +struct Flap; + +/// Resource +struct Art { + bird_icon: Handle, +} + +/// Resource +struct AutoFlapState { + active: bool, + timer: Timer, +} + +impl Default for AutoFlapState { + fn default() -> AutoFlapState { + AutoFlapState { + active: true, + timer: Timer::from_seconds(AUTO_FLAP_INTERVAL_SECS, true), + } + } +} + +/// Resource +struct MourningState { + active: bool, + timer: Timer, +} + +impl Default for MourningState { + fn default() -> MourningState { + MourningState { + active: false, + timer: Timer::from_seconds(MOURN_TIME_SECS, false), + } + } +} + +struct GenerateChunk { + new_chunk_index: i32, +} + +#[derive(Component)] +struct Obstacle; + +#[derive(Component, Default)] +struct Velocity { + velocity: Vec2, +} + +#[derive(Component)] +struct BrownianDrift; + +#[derive(Component)] +struct Gravity; + +#[derive(Component)] +struct Bird; + +#[derive(Component)] +struct DriftToCenter; + +fn load_art(mut commands: Commands, asset_server: Res) { + commands.insert_resource(Art { + bird_icon: asset_server.load("branding/icon.png"), + }); +} + +fn bird_startup(mut spawn_bird_events: EventWriter) { + spawn_bird_events.send(SpawnBird { + new_bird_pos: Some(Vec2::ZERO), + new_bird_velocity: Vec2::new(CAMERA_SPEED, 0.0), + }); +} + +fn spawn_bird( + mut commands: Commands, + mut spawn_bird_events: EventReader, + cam: Query<&Transform, With>, + art: Res, +) { + for ev in spawn_bird_events.iter() { + let camera_pos = cam.single().translation.truncate(); + // if a bird position is not supplied, spawn in the center of the view + let new_bird_pos = ev.new_bird_pos.unwrap_or(camera_pos); + commands + .spawn_bundle(SpriteBundle { + sprite: Sprite { + color: Color::rgb(random::(), random::(), random::()), + custom_size: Some(Vec2::splat(BIRD_SIZE)), + ..default() + }, + texture: art.bird_icon.clone(), + transform: Transform::from_translation( + new_bird_pos.extend(random::() * 100.0), + ), + ..default() + }) + .insert(Velocity { + velocity: ev.new_bird_velocity, + }) + .insert(Gravity) + .insert(Bird) + .insert(BrownianDrift) + .insert(DriftToCenter); + } +} + +fn input( + input: Res>, + mut flap_events: EventWriter, + mut auto_flap_state: ResMut, +) { + if input.just_pressed(KeyCode::Space) { + flap_events.send(Flap {}); + auto_flap_state.active = false; + } +} + +fn flap(flap_events: EventReader, mut birds: Query<&mut Velocity, With>) { + if !flap_events.is_empty() { + for mut v in birds.iter_mut() { + v.velocity.y = FLAP_STRENGTH; + } + } +} + +fn auto_flap( + time: Res