Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
282 changes: 282 additions & 0 deletions examples/games/flappy_bevy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
//! 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;
const GAP_VARIABILITY: f32 = 0.9;

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) * GAP_VARIABILITY;
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 {
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() {
if collision_result.is_none() {
collision_result = 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(
q: Query<(&Transform, &Velocity), With<Bird>>,
time: Res<Time>,
mut spawn_bird_events: EventWriter<SpawnBird>,
) {
let bird_count = q.iter().count();
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;
}
}
}