Skip to content
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

flappy bevy example #4923

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
278 changes: 278 additions & 0 deletions examples/games/flappy_bevy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
//! A simplified Flappy Bird but with many birds
af-games marked this conversation as resolved.
Show resolved Hide resolved

use bevy::prelude::*;

use bevy::sprite::collide_aabb::collide;
use bevy::window::PresentMode;

use rand::Rng;

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 CHAOS_AMOUNT_X: f32 = 250.0;
const CHAOS_AMOUNT_Y: f32 = 600.0;
const DRIFT_TO_CENTER_AMOUNT: f32 = 0.01;
const FLAP_STRENGTH: f32 = 250.0;
const BIRD_SIZE: f32 = 24.0;
const BIRD_REPRODUCTION_CHANCE: f32 = 1.0;
const MAX_BIRDS: usize = 500;
const GRAVITY: f32 = 400.0;

fn randf() -> f32 {
af-games marked this conversation as resolved.
Show resolved Hide resolved
rand::thread_rng().gen::<f32>()
}

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 chunk_index != new_chunk_index {
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() {
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 * (randf() - 0.5);
let pillar_width = 50.0 + 110.0 * randf();
// make the gap no narrower than the pillar is wide
let gap_size = (50.0 + 250.0 * randf()).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.
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(randf(), randf(), randf()),
af-games marked this conversation as resolved.
Show resolved Hide resolved
custom_size: Some(Vec2::splat(BIRD_SIZE)),
..default()
},
texture: art.bird_icon.clone(),
transform: Transform::from_translation(ev.new_bird_pos.extend(randf() * 100.0)),
..default()
})
.insert(Velocity {
velocity: ev.new_bird_velocity,
})
.insert(Gravity)
.insert(Bird)
.insert(BrownianDrift)
.insert(DriftToCenter);
}
}

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() {
for (obstacle_sprite, obstacle_transform) in obstacles.iter() {
let collision_result = collide(
bird_transform.translation,
bird_sprite.custom_size.unwrap(),
obstacle_transform.translation,
obstacle_sprite.custom_size.unwrap(),
);
if collision_result.is_some() {
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.
Show resolved Hide resolved
bird_count += 1;
}
for (t, v) in q.iter() {
if bird_count < MAX_BIRDS && randf() < BIRD_REPRODUCTION_CHANCE * time.delta().as_secs_f32()
af-games marked this conversation as resolved.
Show resolved Hide resolved
{
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(
(randf() - 0.5) * CHAOS_AMOUNT_X,
(randf() - 0.5) * CHAOS_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;
}
}
}