Implement ant random walk
This commit is contained in:
60
src/components/ant.rs
Normal file
60
src/components/ant.rs
Normal file
@ -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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
10
src/components/common.rs
Normal file
10
src/components/common.rs
Normal file
@ -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);
|
2
src/components/mod.rs
Normal file
2
src/components/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod ant;
|
||||
pub mod common;
|
44
src/main.rs
44
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();
|
||||
}
|
||||
|
1
src/resources/common.rs
Normal file
1
src/resources/common.rs
Normal file
@ -0,0 +1 @@
|
||||
|
1
src/resources/mod.rs
Normal file
1
src/resources/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod common;
|
2
src/systems/mod.rs
Normal file
2
src/systems/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod random_walk;
|
||||
pub mod startup;
|
65
src/systems/random_walk.rs
Normal file
65
src/systems/random_walk.rs
Normal file
@ -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<Time>, so pass by value
|
||||
pub fn randomized_velocity_system(mut query: Query<&mut RandomizedVelocity>) {
|
||||
for mut velocity in &mut query {
|
||||
let mut new_velocity: Vec2 = Vec2::from_angle(
|
||||
// We first multiply with CONE to get the angle-delta to [0, CONE].
|
||||
// Then, we subtract CONE/2 to bring the angle-delta to [-CONE/2, CONE/2].
|
||||
// Lastly, we add the angle-delta to the current angle.
|
||||
// This should vary the direction in a front-facing CONE-degree cone.
|
||||
rand::random::<f32>().mul_add(
|
||||
RANDOM_WALK_CONE,
|
||||
velocity.0.to_angle() - RANDOM_WALK_CONE / 2.,
|
||||
),
|
||||
);
|
||||
new_velocity = new_velocity.normalize() * ANT_SPEED;
|
||||
|
||||
velocity.0 = new_velocity;
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates ``Position`` and ``Transform`` components of entities that also have
|
||||
/// the ``RandomizedVelocity`` component, like an ``Ant``.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn random_walk_system(
|
||||
// We query for each entity that has a Position, Transform and Velocity component.
|
||||
// The Transform component comes from the SpriteBundle.
|
||||
mut query: Query<(&mut Position, &mut Transform, &RandomizedVelocity)>,
|
||||
) {
|
||||
for (mut position, mut transform, velocity) in &mut query {
|
||||
let new_position: Vec2 = position.0 + velocity.0;
|
||||
|
||||
transform.translation = new_position.clamp(MIN_POSITION, MAX_POSITION).extend(0.);
|
||||
position.0 = new_position.clamp(MIN_POSITION, MAX_POSITION);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an ant's velocity perpendicular to a wall, if touched.
|
||||
pub fn wall_avoidance_system(mut query: Query<(&Position, &mut RandomizedVelocity)>) {
|
||||
let touches_left_wall = |position: Vec2| -> bool { position.x <= 0. };
|
||||
let touches_right_wall = |position: Vec2| -> bool { position.x >= MAX_POSITION.x };
|
||||
let touches_bottom_wall = |position: Vec2| -> bool { position.y <= 0. };
|
||||
let touches_top_wall = |position: Vec2| -> bool { position.y >= MAX_POSITION.y };
|
||||
|
||||
for (position, mut velocity) in &mut query {
|
||||
let mut new_velocity: Vec2 = velocity.0;
|
||||
|
||||
if touches_left_wall(position.0) {
|
||||
new_velocity = Vec2::new(1., 0.);
|
||||
} else if touches_right_wall(position.0) {
|
||||
new_velocity = Vec2::new(-1., 0.);
|
||||
} else if touches_bottom_wall(position.0) {
|
||||
new_velocity = Vec2::new(0., 1.);
|
||||
} else if touches_top_wall(position.0) {
|
||||
new_velocity = Vec2::new(0., -1.);
|
||||
}
|
||||
|
||||
velocity.0 = new_velocity;
|
||||
}
|
||||
}
|
20
src/systems/startup.rs
Normal file
20
src/systems/startup.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use crate::{components::ant::AntBundle, ANT_COUNT};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Signal that the app has started by printing a message to the terminal.
|
||||
pub fn hello_ants_system() {
|
||||
println!("Hello, fellow 🐜");
|
||||
}
|
||||
|
||||
/// Prepares the environment before the Update schedule by spawning required entities.
|
||||
pub fn setup_system(mut commands: Commands) {
|
||||
// Using the default camera, world space coordinates correspond 1:1 with screen pixels.
|
||||
// The point (0, 0) is in the center of the screen.
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
// Spawn cute ants
|
||||
for _ in 0..ANT_COUNT {
|
||||
commands.spawn(AntBundle::new(Vec2::new(0., 0.), Color::srgb(1., 0., 0.)));
|
||||
}
|
||||
println!("Spawned {ANT_COUNT} cute ants 😍");
|
||||
}
|
Reference in New Issue
Block a user