Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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
281 changes: 281 additions & 0 deletions examples/games/flappy_bevy.rs
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 {
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(
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() {
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;
}
}
}