Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df86f05876 | |||
| 68d6de9a83 | |||
| ccd8a12b79 | |||
| f2a71e712a | |||
| dca8bcdaa7 | |||
| e6e56506f8 | |||
| bc0630b4ae | |||
| 76a6b4f999 | |||
| 451311126d | |||
| 7b380196a5 | |||
| 8b61d38636 | |||
| b3cf47e684 | |||
| 69403bc6ca | |||
| a6240c844a | |||
| 3eb23fb4bf | |||
| 532025b42f | |||
| 4150f85ccc | |||
| 214da65db5 | |||
| 325e515a7b | |||
| e85114c4c8 | |||
| feeeb15d22 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
runner = "wasm-server-runner"
|
||||
2838
Cargo.lock
generated
2838
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
@@ -1,11 +1,10 @@
|
||||
[package]
|
||||
name = "another-boids-in-rust"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.14.0"
|
||||
bevy_spatial = "0.9.0"
|
||||
bevy = "0.16.0"
|
||||
|
||||
# Grand-dependency pins
|
||||
ab_glyph = "0.2.16"
|
||||
@@ -17,6 +16,16 @@ miniz-sys = "0.1.10"
|
||||
nonmax = "0.5.1"
|
||||
rand = "0.8.0"
|
||||
|
||||
# Use regular bevy_spatial on non-WASM builds
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
bevy_spatial = "0.11.0"
|
||||
|
||||
# Use bevy_spatial *without* the kdtree_rayon feature when building for WASM.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.bevy_spatial]
|
||||
version = "0.11.0"
|
||||
default-features = false
|
||||
features = ["kdtree"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
|
||||
pub mod physics;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_spatial::{
|
||||
kdtree::KDTree2, AutomaticUpdate, SpatialAccess, SpatialStructure, TransformMode,
|
||||
AutomaticUpdate, SpatialAccess, SpatialStructure, TransformMode, kdtree::KDTree2,
|
||||
};
|
||||
|
||||
use crate::birdoids::physics::{Force, Velocity, apply_velocity};
|
||||
|
||||
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 BOID_VIEW_RANGE: f32 = 15.0;
|
||||
const COHESION_FACTOR: f32 = 1.0;
|
||||
const SEPARATION_FACTOR: f32 = 1.0;
|
||||
const SEPARATION_FACTOR: f32 = 10.0;
|
||||
const ALIGNMENT_FACTOR: f32 = 1.0;
|
||||
const SPACEBRAKES_COEFFICIENT: f32 = 0.01;
|
||||
const SPACEBRAKES_COEFFICIENT: f32 = 0.5;
|
||||
const LOW_SPEED_THRESHOLD: f32 = 50.0;
|
||||
const HIGH_SPEED_THRESHOLD: f32 = 200.0;
|
||||
|
||||
pub struct BoidsPlugin;
|
||||
|
||||
@@ -33,13 +39,14 @@ impl Plugin for BoidsPlugin {
|
||||
cohesion,
|
||||
separation,
|
||||
alignment,
|
||||
// space_brakes,
|
||||
speed_controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[require(Velocity, Force, TrackedByKdTree)]
|
||||
pub(crate) struct Boid;
|
||||
|
||||
// It's a Boid, but with an extra component so the player
|
||||
@@ -47,36 +54,11 @@ pub(crate) struct Boid;
|
||||
#[derive(Component)]
|
||||
struct PlayerBoid;
|
||||
|
||||
#[derive(Component, Deref, DerefMut)]
|
||||
pub(crate) struct Velocity(Vec3);
|
||||
|
||||
#[derive(Component, Default, Deref, DerefMut, PartialEq, Debug)]
|
||||
pub(crate) struct Force(Vec3);
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct TrackedByKdTree;
|
||||
|
||||
#[derive(Bundle)]
|
||||
struct BoidBundle {
|
||||
boid: Boid,
|
||||
velocity: Velocity,
|
||||
accel: Force,
|
||||
spatial: TrackedByKdTree,
|
||||
}
|
||||
|
||||
impl BoidBundle {
|
||||
fn new(vel: Vec3) -> Self {
|
||||
Self {
|
||||
boid: Boid,
|
||||
velocity: Velocity(vel),
|
||||
accel: Force(Vec3::ZERO),
|
||||
spatial: TrackedByKdTree,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Component, Default)]
|
||||
pub struct TrackedByKdTree;
|
||||
|
||||
fn spawn_camera(mut commands: Commands) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands.spawn(Camera2d);
|
||||
}
|
||||
|
||||
fn spawn_boids(
|
||||
@@ -89,34 +71,32 @@ fn spawn_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) * 10.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()
|
||||
},
|
||||
Boid,
|
||||
Velocity(vel),
|
||||
Mesh2d(meshes.add(Circle::new(1.0))),
|
||||
MeshMaterial2d(materials.add(Color::srgb(1.0, 1.0, 1.0))),
|
||||
Transform::from_translation(vel * 20.0),
|
||||
));
|
||||
}
|
||||
|
||||
commands.spawn((
|
||||
BoidBundle::new(Vec3::new(0.0, 0.0, 0.0)),
|
||||
Boid,
|
||||
PlayerBoid,
|
||||
MaterialMesh2dBundle {
|
||||
mesh: meshes.add(Triangle2d::default()).into(),
|
||||
material: materials.add(PLAYERBOID_COLOR),
|
||||
..default()
|
||||
},
|
||||
Mesh2d(meshes.add(Triangle2d::default())),
|
||||
MeshMaterial2d(materials.add(PLAYERBOID_COLOR)),
|
||||
));
|
||||
}
|
||||
|
||||
fn space_brakes(mut mobs: Query<&mut Force, With<Boid>>) {
|
||||
for mut accel in &mut mobs {
|
||||
let braking_dir = -accel.0 * SPACEBRAKES_COEFFICIENT;
|
||||
accel.0 += braking_dir;
|
||||
/// Controls the boid's minimum and maximum speed according to a low- and
|
||||
/// high-threshold. Boids moving too slow are sped up, and boids moving too
|
||||
/// fast are slowed down.
|
||||
fn speed_controller(mut mobs: Query<(&Velocity, &mut Force), With<Boid>>) {
|
||||
for (vel, mut impulse) in &mut mobs {
|
||||
if vel.0.length() < LOW_SPEED_THRESHOLD {
|
||||
impulse.0 += vel.0 * SPACEBRAKES_COEFFICIENT;
|
||||
} else if vel.0.length() > HIGH_SPEED_THRESHOLD {
|
||||
impulse.0 += -vel.0 * SPACEBRAKES_COEFFICIENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +104,7 @@ fn turn_if_edge(
|
||||
mut query: Query<(&mut Transform, &mut Velocity), With<Boid>>,
|
||||
window: Query<&Window>,
|
||||
) {
|
||||
if let Ok(window) = window.get_single() {
|
||||
if let Ok(window) = window.single() {
|
||||
let (width, height) = (window.resolution.width(), window.resolution.height());
|
||||
for (transform, mut velocity) in &mut query {
|
||||
let boid_pos = transform.translation.xy();
|
||||
@@ -145,25 +125,18 @@ fn turn_if_edge(
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_velocity(mut query: Query<(&mut Transform, &Velocity, &mut Force)>, time: Res<Time>) {
|
||||
for (mut transform, velocity, mut acceleration) in &mut query {
|
||||
let delta_v = **acceleration * time.delta_seconds();
|
||||
**acceleration = Vec3::ZERO;
|
||||
let delta_position = (**velocity + delta_v) * time.delta_seconds();
|
||||
transform.translation += delta_position;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_keyboard(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
|
||||
mut query: Query<&mut Velocity, With<PlayerBoid>>,
|
||||
mut query: Query<&mut Force, With<PlayerBoid>>,
|
||||
) {
|
||||
if keyboard_input.just_pressed(KeyCode::KeyQ) {
|
||||
app_exit_events.send(bevy::app::AppExit::Success);
|
||||
}
|
||||
|
||||
let mut pvelocity = query.single_mut();
|
||||
let mut impulse = query
|
||||
.single_mut()
|
||||
.expect("[birdoids_plugin::check_keyboard()] ->> There seems to be more than one player... How did that happen?");
|
||||
let mut dir = Vec2::ZERO;
|
||||
if keyboard_input.pressed(KeyCode::ArrowLeft) {
|
||||
dir.x -= 1.0;
|
||||
@@ -178,53 +151,78 @@ fn check_keyboard(
|
||||
dir.y += 1.0;
|
||||
}
|
||||
|
||||
**pvelocity = **pvelocity + dir.extend(0.0);
|
||||
**impulse += dir.extend(0.0) * 50.0;
|
||||
}
|
||||
|
||||
fn cohesion(
|
||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
||||
mut boids: Query<(&Transform, &mut Force), With<Boid>>,
|
||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||
) {
|
||||
// for each boid
|
||||
// find neighbors
|
||||
// find center-of-mass of neighbors
|
||||
// find vector from boid to flock CoM
|
||||
// apply force
|
||||
for (transform, mut acceleration) in &mut boids {
|
||||
let neighbors = spatial_tree.within_distance(transform.translation.xy(), BOID_VIEW_RANGE);
|
||||
if let Some(center_mass) = center_of_boids(neighbors.iter().map(|boid| boid.0)) {
|
||||
let force = cohesive_force(center_mass, transform.translation.xy()).unwrap_or_default();
|
||||
acceleration.0 += *force * COHESION_FACTOR;
|
||||
}
|
||||
for (this_entt, transform, mut force) in &mut boids {
|
||||
let (len, sum) = spatial_tree
|
||||
.within_distance(transform.translation.xy(), BOID_VIEW_RANGE)
|
||||
.iter()
|
||||
.filter_map(|(pos, entt)| {
|
||||
// Skip self-comparison. A boid should not try to separate from itself.
|
||||
let entt = entt
|
||||
.expect("within_distance gave me an entity... with no entity ID... somehow");
|
||||
if this_entt == entt { None } else { Some(pos) }
|
||||
})
|
||||
.enumerate()
|
||||
.fold((0, Vec2::ZERO), |(_len, com), (idx, pos)| (idx, com + pos));
|
||||
|
||||
// Skip to next boid if the current one has no neighbors.
|
||||
let center_of_mass = if len > 0 {
|
||||
sum / ((len + 1) as f32)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let impulse = cohesive_force(center_of_mass, transform.translation.xy()).expect("damn");
|
||||
|
||||
force.0 -= impulse.0 * COHESION_FACTOR;
|
||||
}
|
||||
}
|
||||
|
||||
fn separation(
|
||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
||||
mut boids: Query<(&Transform, &mut Force), With<Boid>>,
|
||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||
) {
|
||||
// for each boid
|
||||
// find neighbors
|
||||
// sum force from neighbors
|
||||
// apply force
|
||||
for (boid_transform, mut boid_acceleration) in &mut boids {
|
||||
let neighbors =
|
||||
spatial_tree.within_distance(boid_transform.translation.xy(), BOID_VIEW_RANGE / 4.0);
|
||||
let accel = neighbors.iter().map(|(pos, _)| pos.extend(0.0)).fold(
|
||||
Vec3::ZERO,
|
||||
|accumulator, neighbor| {
|
||||
let force = separation_force(boid_transform.translation.xy(), neighbor.xy())
|
||||
.unwrap_or_default();
|
||||
accumulator + *force * SEPARATION_FACTOR
|
||||
},
|
||||
);
|
||||
boid_acceleration.0 += accel;
|
||||
for (this_entt, tsfm, mut force) in &mut boids {
|
||||
let impulse = spatial_tree
|
||||
.within_distance(tsfm.translation.xy(), BOID_VIEW_RANGE / 4.0)
|
||||
.iter()
|
||||
.filter_map(|(pos, entt)| {
|
||||
// Skip self-comparison. A boid should not try to separate from itself.
|
||||
let entt = entt
|
||||
.expect("within_distance gave me an entity... with no entity ID... somehow");
|
||||
if this_entt == entt {
|
||||
None
|
||||
} else {
|
||||
Some(pos.extend(0.0))
|
||||
}
|
||||
})
|
||||
.fold(Vec3::ZERO, |acc, other| {
|
||||
// let force = tsfm.translation - other;
|
||||
let force = separation_force(tsfm.translation.xy(), other.xy()).expect("angy");
|
||||
acc + force.0
|
||||
});
|
||||
force.0 += impulse * SEPARATION_FACTOR;
|
||||
}
|
||||
}
|
||||
|
||||
fn alignment(
|
||||
spatial_tree: Res<KDTree2<TrackedByKdTree>>,
|
||||
mut boids: Query<(&Transform, &Velocity, &mut Force), With<Boid>>,
|
||||
mut boids: Query<(Entity, &Transform, &mut Force), With<Boid>>,
|
||||
boid_velocities: Query<&Velocity, With<Boid>>,
|
||||
) {
|
||||
// for each boid
|
||||
@@ -234,27 +232,36 @@ fn alignment(
|
||||
// perpendicular so that magnitude is constant
|
||||
// apply steering force
|
||||
|
||||
for (transform, velocity, mut acceleration) in &mut boids {
|
||||
for (this_entt, transform, mut force) in &mut boids {
|
||||
let neighbors = spatial_tree.within_distance(transform.translation.xy(), BOID_VIEW_RANGE);
|
||||
// averaging divides by length. Guard against an empty set of neighbors
|
||||
// so that we don't divide by zero.
|
||||
if neighbors.len() > 0 {
|
||||
if let Some(avg_velocity) =
|
||||
velocity_of_boids(neighbors.iter().map(|(vel, opt_entity)| {
|
||||
// I've observed no panics in the old version, nor the debug_plugins version
|
||||
// I'm not clear on the conditions that cause a None option, but I want to
|
||||
// crash when I find one.
|
||||
let entity_id = opt_entity.unwrap_or_else(|| panic!("Boid has no Entity ID!"));
|
||||
let vel = boid_velocities
|
||||
.get(entity_id)
|
||||
.unwrap_or_else(|_| panic!("Boid has no velocity!"));
|
||||
(*vel).xy()
|
||||
}))
|
||||
{
|
||||
let deviation = -velocity.0 + avg_velocity.extend(0.0);
|
||||
acceleration.0 += deviation * ALIGNMENT_FACTOR;
|
||||
}
|
||||
}
|
||||
let (len, sum) = neighbors
|
||||
.iter()
|
||||
// Extract the velocities by `get()`ing from another query param.
|
||||
.filter_map(|(_pos, maybe_entt)| {
|
||||
let entt = maybe_entt
|
||||
.expect("Neighbor boid has no Entity ID. I don't know what this means");
|
||||
if this_entt == entt {
|
||||
None
|
||||
} else {
|
||||
let vel = boid_velocities.get(entt).expect("Boid has no velocity!");
|
||||
Some(vel.xy())
|
||||
}
|
||||
})
|
||||
.enumerate()
|
||||
.fold((0, Vec2::ZERO), |(_len, vel_acc), (idx, vel)| {
|
||||
(idx, vel_acc + vel)
|
||||
});
|
||||
|
||||
// Skip to next boid if the current one has no neighbors.
|
||||
let avg = if len > 0 {
|
||||
sum / ((len + 1) as f32)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let boid_vel = boid_velocities.get(this_entt).unwrap();
|
||||
force.0 += (avg.extend(0.0) - boid_vel.0) * ALIGNMENT_FACTOR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,22 +274,13 @@ pub(crate) fn velocity_of_boids(points: impl Iterator<Item = Vec2>) -> Option<Ve
|
||||
}
|
||||
|
||||
fn average_of_vec2s(points: impl Iterator<Item = Vec2>) -> Option<Vec2> {
|
||||
// Average the points by summing them all together, and dividing by
|
||||
// the total count.
|
||||
// Passing the points as an iterator means we lose the length of the
|
||||
// list. The `.enumerate()` iterator reintroduces that count.
|
||||
let mut points = points.enumerate();
|
||||
|
||||
// Empty iterators have no points and so no center of mass.
|
||||
// Try to get the first one, but exit with None if it doesn't yield.
|
||||
let init = points.next()?;
|
||||
|
||||
// if we get one, fold all the remaining values into it.
|
||||
let (len, sum) = points.fold(init, |(len, sum), (idx, point)| {
|
||||
// replace length with most recent index
|
||||
// add running sum & new point for new running sum
|
||||
(idx, sum + point)
|
||||
});
|
||||
let (len, sum) = points
|
||||
.enumerate()
|
||||
.fold((0, Vec2::ZERO), |(_len, sum), (idx, point)| {
|
||||
// replace length with most recent index
|
||||
// add running sum & new point for new running sum
|
||||
(idx, sum + point)
|
||||
});
|
||||
let avg = sum / ((len + 1) as f32);
|
||||
|
||||
Some(avg)
|
||||
@@ -329,9 +327,9 @@ fn separation_force(boid: Vec2, target: Vec2) -> Option<Force> {
|
||||
mod tests {
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::birdoids_plugin::{cohesive_force, separation_force};
|
||||
use crate::birdoids::{cohesive_force, separation_force};
|
||||
|
||||
use super::{Force, BOID_VIEW_RANGE};
|
||||
use super::{BOID_VIEW_RANGE, physics::Force};
|
||||
|
||||
// forces are relative to the boid's view range, so all
|
||||
// distances need to be fractions of that
|
||||
23
src/birdoids/physics.rs
Normal file
23
src/birdoids/physics.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Default, Deref, DerefMut)]
|
||||
pub struct Velocity(pub Vec3);
|
||||
|
||||
#[derive(Component, Default, Deref, DerefMut, PartialEq, Debug)]
|
||||
pub struct Force(pub Vec3);
|
||||
|
||||
pub fn apply_velocity(
|
||||
mut query: Query<(&mut Transform, &mut Velocity, &mut Force)>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for (mut transform, mut velocity, mut force) in &mut query {
|
||||
// integrate forces into new velocity (assume mass of 1kg)
|
||||
let delta_v = **force * time.delta_secs();
|
||||
velocity.0 += delta_v;
|
||||
force.0 = Vec3::ZERO; // clear force value
|
||||
|
||||
// integrate velocity into position
|
||||
let delta_p = **velocity * time.delta_secs();
|
||||
transform.translation += delta_p;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use bevy::{prelude::*, sprite::MaterialMesh2dBundle, window::PrimaryWindow};
|
||||
use bevy_spatial::{kdtree::KDTree2, SpatialAccess};
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use bevy_spatial::{SpatialAccess, kdtree::KDTree2};
|
||||
|
||||
use crate::birdoids_plugin::{
|
||||
center_of_boids, velocity_of_boids, Boid, Force, TrackedByKdTree, Velocity,
|
||||
use crate::birdoids::{
|
||||
Boid, TrackedByKdTree, center_of_boids, physics::Force, physics::Velocity, velocity_of_boids,
|
||||
};
|
||||
|
||||
const SCANRADIUS: f32 = 50.0;
|
||||
@@ -22,13 +22,8 @@ fn setup(
|
||||
) {
|
||||
commands.spawn((
|
||||
ScannerWidget::default(),
|
||||
MaterialMesh2dBundle {
|
||||
mesh: meshes
|
||||
.add(Annulus::new(SCANRADIUS - 1.0, SCANRADIUS))
|
||||
.into(),
|
||||
material: materials.add(Color::srgb(0.0, 0.0, 0.0)),
|
||||
..default()
|
||||
},
|
||||
Mesh2d(meshes.add(Annulus::new(SCANRADIUS - 1.0, SCANRADIUS))),
|
||||
MeshMaterial2d(materials.add(Color::srgb(0.0, 0.0, 0.0))),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -71,8 +66,8 @@ fn update_cursor(
|
||||
) {
|
||||
// I'm trusting that only one thing has the `Cursor` component
|
||||
// It's defined here in this module, so that *should* be the case...
|
||||
let win = window.get_single().unwrap();
|
||||
let (cam, cam_transform) = camera.get_single().unwrap();
|
||||
let win = window.single().unwrap();
|
||||
let (cam, cam_transform) = camera.single().unwrap();
|
||||
// the cursor might not be on the window. Only adjust position when it is.
|
||||
if let Some(cursor_pos_in_window) = win.cursor_position() {
|
||||
// transform the window position into world space
|
||||
@@ -81,7 +76,7 @@ fn update_cursor(
|
||||
let cursor_in_world = cam
|
||||
.viewport_to_world_2d(cam_transform, cursor_pos_in_window)
|
||||
.unwrap();
|
||||
let mut cursor = cursor_query.get_single_mut().unwrap();
|
||||
let mut cursor = cursor_query.single_mut().unwrap();
|
||||
cursor.translation = cursor_in_world.extend(0.0);
|
||||
}
|
||||
}
|
||||
@@ -94,7 +89,7 @@ fn update_scanner_mode(
|
||||
mut scanner_query: Query<(&mut SelectionMode, &mut ScannerMode), With<Cursor>>,
|
||||
) {
|
||||
// I'm making another assertion that there is exactly one scanner.
|
||||
let (mut select_mode, mut scan_mode) = scanner_query.get_single_mut().unwrap();
|
||||
let (mut select_mode, mut scan_mode) = scanner_query.single_mut().unwrap();
|
||||
|
||||
// Assign selection mode
|
||||
if keycodes.just_pressed(KeyCode::Digit1) {
|
||||
@@ -112,7 +107,7 @@ fn update_scanner_mode(
|
||||
}
|
||||
|
||||
fn print_gizmo_config(query: Query<(&SelectionMode, &ScannerMode), With<Cursor>>) {
|
||||
let (select, scan) = query.get_single().unwrap();
|
||||
let (select, scan) = query.single().unwrap();
|
||||
println!("Selection: {select:?}, Scanning: {scan:?}");
|
||||
}
|
||||
|
||||
@@ -123,7 +118,7 @@ fn do_scan(
|
||||
/* Push info to summary somewhere */
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
let (cursor_pos, select_mode, scan_mode) = scanner_query.get_single().unwrap();
|
||||
let (cursor_pos, select_mode, scan_mode) = scanner_query.single().unwrap();
|
||||
match select_mode {
|
||||
SelectionMode::NearestSingle => todo!(),
|
||||
SelectionMode::CircularArea => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
mod birdoids_plugin;
|
||||
mod birdoids;
|
||||
mod debug_plugin;
|
||||
|
||||
use birdoids_plugin::BoidsPlugin;
|
||||
use birdoids::BoidsPlugin;
|
||||
use debug_plugin::BoidsDebugPlugin;
|
||||
|
||||
fn main() {
|
||||
|
||||
Reference in New Issue
Block a user