diff --git a/src/components/ant.rs b/src/components/ant.rs new file mode 100644 index 0000000..09c7580 --- /dev/null +++ b/src/components/ant.rs @@ -0,0 +1,60 @@ +use super::common::{Position, RandomizedVelocity}; +use bevy::prelude::*; + +/// This bundle represents the ant entity. +/// The ``state`` determines the ant's behavior, it stores its current ``position`` +/// that is updated based on the ``randomized_velocity``. +/// The ``sprite`` is this entity's visual representation. +// Instead of calling commands.spawn(AntState::Searching, Position(Vec2::new(...)), ...), +// we can bundle-up the components that make up an Ant and simplify the spawning by +// using "impl" to define a "constructor" +#[derive(Bundle)] +#[allow(clippy::module_name_repetitions)] +pub struct AntBundle { + pub state: AntState, + pub position: Position, + pub velocity: RandomizedVelocity, + + // These are not components, but other bundles of components. Those can be nested. + pub sprite: SpriteBundle, +} + +/// The states an ``Ant`` can assume. +#[derive(Component)] +#[allow(clippy::module_name_repetitions)] +pub enum AntState { + /// The ant performs a random walk, searching for food. + /// While walking, it drops Anthill-pheromones, leading back to the hill. + /// Once food is found, the ant gets the Returning state. + /// If the ant finds Food-pheromones, it gets the Fetching state. + Searching, + + /// The ant found Food-pheromones and follows them to the food. + /// Once the food is reached, the ant gets the Returning state. + Fetching, + + /// The ant found food an is walking the path marked by the Anthill-pheromones. + /// While returning, it drops Food-pheromones, leading to the food. + /// Once the ant dropped off the food, it gets the Searching state. + Returning, +} + +// The "impl" keyword allows us to implement functions in a struct's namespace, i.e. "methods" +impl AntBundle { + /// Instantiate a new ``AntBundle`` with a color and a starting position. + pub fn new(position: Vec2, color: Color) -> Self { + Self { + state: AntState::Searching, + position: Position(position), + velocity: RandomizedVelocity(Vec2::ZERO), + sprite: SpriteBundle { + transform: Transform { + scale: Vec3::new(20., 20., 20.), + ..default() + }, + sprite: Sprite { color, ..default() }, + ..default() + }, + } + } +} diff --git a/src/components/common.rs b/src/components/common.rs new file mode 100644 index 0000000..5e14eef --- /dev/null +++ b/src/components/common.rs @@ -0,0 +1,10 @@ +use bevy::prelude::*; + +#[derive(Component)] +pub struct Position(pub Vec2); + +#[derive(Component)] +pub struct Velocity(pub Vec2); + +#[derive(Component)] +pub struct RandomizedVelocity(pub Vec2); diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..0034e54 --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,2 @@ +pub mod ant; +pub mod common; diff --git a/src/main.rs b/src/main.rs index e7a11a9..c4240e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,45 @@ +mod components; +mod systems; + +use bevy::prelude::*; +use std::f32::consts::PI; +use systems::{ + random_walk::{random_walk_system, randomized_velocity_system, wall_avoidance_system}, + startup::{hello_ants_system, setup_system}, +}; + +// TODO: Use Settings Resource +const MIN_POSITION: Vec2 = Vec2::ZERO; +const MAX_POSITION: Vec2 = Vec2::new(500., 500.); + +const ANT_COUNT: u32 = 25; +const ANT_SPEED: f32 = 0.25; +const RANDOM_WALK_CONE: f32 = PI / 180. * 20.; + +/// The app's entrypoint. fn main() { - println!("Hello, world!"); + let mut app = App::new(); + + // The DefaultPlugins contain the "Window" plugin. + app.add_plugins(DefaultPlugins); + + // Sets the color used to clear the screen, i.e. the background color. + app.insert_resource(ClearColor(Color::srgb(0.9, 0.9, 0.9))); + + // Startup systems are run once on startup. + // The chain() function guarantees execution in the declared order. + app.add_systems(Startup, (setup_system, hello_ants_system).chain()); + + // Update systems are ran each update cycle, i.e. each frame. + app.add_systems( + Update, + ( + randomized_velocity_system, + wall_avoidance_system, + random_walk_system, + ) + .chain(), + ); + + app.run(); } diff --git a/src/resources/common.rs b/src/resources/common.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/resources/common.rs @@ -0,0 +1 @@ + diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..34994bf --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1 @@ +pub mod common; diff --git a/src/systems/mod.rs b/src/systems/mod.rs new file mode 100644 index 0000000..5b0f762 --- /dev/null +++ b/src/systems/mod.rs @@ -0,0 +1,2 @@ +pub mod random_walk; +pub mod startup; diff --git a/src/systems/random_walk.rs b/src/systems/random_walk.rs new file mode 100644 index 0000000..89719a3 --- /dev/null +++ b/src/systems/random_walk.rs @@ -0,0 +1,65 @@ +use crate::{ + components::common::{Position, RandomizedVelocity}, + ANT_SPEED, MAX_POSITION, MIN_POSITION, RANDOM_WALK_CONE, +}; +use bevy::{math::VectorSpace, prelude::*}; + +/// Randomizes the ``RandomizedVelocity`` components of entities that have them. +#[allow(clippy::needless_pass_by_value)] // I can't specify &Res