From 0b7e184b14b3e5d33abc2434c6eb6c6dcb6134f0 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Wed, 25 Sep 2024 18:49:01 +1200 Subject: [PATCH 1/5] Ressurect flappy_bevy example --- examples/flappy-bevy.rs | 469 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 examples/flappy-bevy.rs diff --git a/examples/flappy-bevy.rs b/examples/flappy-bevy.rs new file mode 100644 index 0000000000000..2143a82ac7eac --- /dev/null +++ b/examples/flappy-bevy.rs @@ -0,0 +1,469 @@ +//! A simplified Flappy Bird but with many birds. Press space to flap. + +use bevy::ecs::system::RunSystemOnce; +use bevy::math::bounding::{Aabb2d, BoundingCircle, IntersectsVolume}; +use bevy::window::PrimaryWindow; +use bevy::{ecs::world::Command, prelude::*}; + +use rand::random; + +const CAMERA_SPEED: f32 = 120.0; + +#[derive(States, Debug, Hash, PartialEq, Eq, Clone, Default)] +enum GameState { + #[default] + Loading, + Over, + Playing, +} + +fn main() { + let mut app = App::new(); + + app.add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + // TODO: remove + resizable: false, + ..Default::default() + }), + ..Default::default() + })) + .add_systems(Startup, load_assets); + + app.add_plugins((asset_plugin, bird_plugin, physics_plugin, terrain_plugin)); + app.configure_sets( + FixedUpdate, + AppSet::Physics.run_if(in_state(GameState::Playing)), + ); + app.configure_sets(Update, AppSet::Loading.run_if(in_state(GameState::Loading))); + app.configure_sets( + Update, + (AppSet::RecordInput, AppSet::Playing) + .chain() + .run_if(in_state(GameState::Playing)), + ); + app.run(); +} + +// High-level groupings of systems for the game. +#[derive(SystemSet, Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)] +enum AppSet { + // Record player input. + RecordInput, + // Anything to do with gravity or velocity. + Physics, + // Asset loading updates. + Loading, + // In-game updates. + Playing, +} + +// Asset Plugin +// +// Normally, plugins would be separated into different modules. For the purposes of this example, +// everything is in one file but it's a valuable pattern to keep in mind. With the aid of `State`s +// and `SystemSet`s, we can often separate a Bevy app into areas of concern. It's not uncommon to +// nest plugins within plugins, so long as it makes sense for your overall design. +fn asset_plugin(app: &mut App) { + app.add_systems(Startup, load_assets); + app.add_systems(Update, wait_for_asset_load.in_set(AppSet::Loading)); +} + +#[derive(Resource)] +struct TextureAssets { + bird: Handle, +} + +fn load_assets(mut commands: Commands, asset_server: Res) { + commands.insert_resource(TextureAssets { + bird: asset_server.load("branding/icon.png"), + }); +} + +fn wait_for_asset_load( + asset_server: Res, + mut next_state: ResMut>, + texture_assets: Res, +) { + if asset_server.is_loaded_with_dependencies(&texture_assets.bird) { + next_state.set(GameState::Playing); + } +} + +// Bird Plugin +fn bird_plugin(app: &mut App) { + app.init_resource::(); + // This will run once we've finished loading assets and we know we're ready to go. + app.add_systems(OnEnter(GameState::Playing), setup); + app.add_systems(Update, input.in_set(AppSet::RecordInput)); + app.add_systems(Update, reproduction.in_set(AppSet::Playing)); +} + +#[derive(Component)] +struct Bird; + +#[derive(Resource)] +struct FlockSettings { + pub bird_size: f32, + pub drift: Vec2, + pub max_birds: usize, + pub reproduction_chance: f32, +} + +impl Default for FlockSettings { + fn default() -> Self { + Self { + bird_size: 24.0, + drift: Vec2::new(2.0, 2.0), + max_birds: 500, + reproduction_chance: 1.0, + } + } +} + +struct SpawnBird { + translation: Vec3, + velocity: Vec2, +} + +// This allows us to `queue` up a `Command` to spawn a `Bird`, with whatever configuration we might +// need to display it correctly. +impl Command for SpawnBird { + fn apply(self, world: &mut World) { + world.run_system_once_with(self, spawn_bird); + } +} + +fn spawn_bird( + In(config): In, + mut commands: Commands, + texture_assets: Res, +) { + commands.spawn(( + Name::new("Bird"), + Bird, + Gravity, + MovementController::default(), + SpriteBundle { + sprite: Sprite { + color: Color::srgb(random::(), random::(), random::()), + ..default() + }, + texture: texture_assets.bird.clone(), + transform: Transform::from_translation(config.translation).with_scale(Vec3::splat(0.1)), + ..default() + }, + Velocity(config.velocity), + )); +} + +fn setup(mut commands: Commands) { + commands.queue(SpawnBird { + translation: Vec3::ZERO, + velocity: Vec2::ZERO, + }); + + commands.spawn(( + Camera2dBundle::default(), + MovementController { + intent: Vec2::new(1.0, 0.0), + horizontal_speed: CAMERA_SPEED, + // We never need to move the camera vertically. + vertical_speed: 0.0, + }, + Velocity(Vec2::new(CAMERA_SPEED, 0.0)), + )); +} + +fn input(input: Res>, mut moveable: Query<&mut MovementController>) { + // Per the genre, flappy games characters continually move at a constant rate along the x axis. + // So our "intent" is always positive for `x`. + let mut intent = Vec2::new(1.0, 0.0); + + if input.just_pressed(KeyCode::Space) { + // We'd like all the birds to "flap". + intent.y = 1.0; + } + for mut controller in &mut moveable { + controller.intent = intent; + } +} + +fn reproduction( + mut commands: Commands, + birds: Query<(&Transform, &Velocity), With>, + flock_settings: Res, + time: Res