use std::time::Duration; use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; use bevy_spatial::{ kdtree::KDTree2, AutomaticUpdate, SpatialAccess, SpatialStructure, TransformMode }; const BACKGROUND_COLOR: Color = Color::srgb(0.4, 0.4, 0.4); const PLAYERBOID_COLOR: Color = Color::srgb(1.0, 0.0, 0.0); const TURN_FACTOR: f32 = 1.0; const BOID_VIEW_RANGE: f32 = 50.0; const COHESION_FACTOR: f32 = 1.0; const SEPARATION_FACTOR: f32 = 1.0; const ALIGNMENT_FACTOR: f32 = 1.0; pub struct BoidsPlugin; impl Plugin for BoidsPlugin { fn build(&self, app: &mut App) { app .add_plugins(AutomaticUpdate::::new() .with_frequency(Duration::from_secs_f32(0.3)) .with_transform(TransformMode::GlobalTransform) .with_spatial_ds(SpatialStructure::KDTree2)) .insert_resource(ClearColor(BACKGROUND_COLOR)) .add_systems(Startup, (spawn_camera, spawn_boids)) .add_systems(FixedUpdate, ( apply_velocity, turn_if_edge, check_keyboard, // cohesion, separation, // alignment, space_brakes, )); } } #[derive(Component)] pub(crate) struct Boid; // It's a Boid, but with an extra component so the player // can control it from the keyboard #[derive(Component)] struct PlayerBoid; #[derive(Component, Deref, DerefMut)] pub(crate) struct Velocity(Vec3); #[derive(Component, Deref, DerefMut)] pub(crate) struct Acceleration(Vec3); #[derive(Component)] pub(crate) struct TrackedByKdTree; #[derive(Bundle)] struct BoidBundle { boid: Boid, velocity: Velocity, accel: Acceleration, spatial: TrackedByKdTree, } impl BoidBundle { fn new(vel: Vec3) -> Self { Self { boid: Boid, velocity: Velocity(vel), accel: Acceleration(Vec3::ZERO), spatial: TrackedByKdTree, } } } fn spawn_camera(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } fn spawn_boids( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let num_boids = 50; for i in 0..num_boids { let frac = 2.0 * std::f32::consts::PI / (num_boids as f32) * (i as f32); let vel = Vec3::new(frac.cos() * 1.0, frac.sin() * 1.0, 0.0); commands.spawn(( BoidBundle::new(vel), MaterialMesh2dBundle { mesh: meshes.add(Circle::default()).into(), material: materials.add(Color::srgb(1.0, 1.0, 1.0)), transform: Transform { translation: vel * 20.0, ..default() }, ..default() }, )); } commands.spawn(( BoidBundle::new(Vec3::new(0.0, 0.0, 0.0)), PlayerBoid, MaterialMesh2dBundle { mesh: meshes.add(Triangle2d::default()).into(), material: materials.add(PLAYERBOID_COLOR), ..default() }, )); } fn space_brakes(mut mobs: Query<&mut Acceleration, With>) { for mut accel in &mut mobs { let braking_dir = -accel.0 * 0.01; accel.0 += braking_dir; } } fn turn_if_edge( mut query: Query<(&mut Transform, &mut Velocity), With>, window: Query<&Window>, ) { if let Ok(window) = window.get_single() { let (width, height) = (window.resolution.width(), window.resolution.height()); for (transform, mut velocity) in &mut query { let boid_pos = transform.translation.xy(); if boid_pos.x <= -width / 2. + 50. { velocity.x += TURN_FACTOR; } else if boid_pos.x >= width / 2. - 50. { velocity.x -= TURN_FACTOR; } if boid_pos.y <= -height / 2. + 50. { velocity.y += TURN_FACTOR; } else if boid_pos.y >= height / 2. - 50. { velocity.y -= TURN_FACTOR; } } } else { panic!("System turn_if_edge(...) got an Err(_) when getting the window properties"); } } fn apply_velocity( mut query: Query<(&mut Transform, &Velocity, &Acceleration)>, time: Res