pub mod circle;
use crate::linear::circle::circle_travel;
use crate::{
Arrived,
Destination,
NextDes,
};
#[cfg(all(feature = "collider_2d"))]
use avian2d::{
math::Vector,
prelude::{
LinearVelocity,
PhysicsSchedulePlugin,
},
};
#[cfg(all(feature = "collider_3d"))]
use avian3d::{
math::Vector,
prelude::{
LinearVelocity,
PhysicsSchedulePlugin,
},
};
use bevy::app::App;
#[cfg(feature = "path_finding")]
use bevy::prelude::Resource;
use bevy::prelude::{
in_state,
Commands,
Component,
Entity,
IntoScheduleConfigs,
On,
Plugin,
Query,
Res,
States,
Time,
Transform,
Update,
Vec3,
Vec3Swizzles,
};
#[cfg(feature = "path_finding")]
use bevy_northstar::prelude::{
AgentPos,
NextPos,
};
pub(crate) struct LinearMovementPlugin<T>
where
T: States,
{
pub states: Vec<T>,
}
impl<T> LinearMovementPlugin<T>
where
T: States,
{
pub(crate) fn new(states: Vec<T>) -> Self {
Self { states }
}
}
impl<T> Plugin for LinearMovementPlugin<T>
where
T: States,
{
fn build(&self, app: &mut App) {
#[cfg(any(feature = "collider_2d", feature = "collider_3d"))]
if !app.is_plugin_added::<PhysicsSchedulePlugin>() {
panic!("LinearMovementPlugin with 'physic' feature requires avian PhysicsPlugins. Add it first!");
}
#[cfg(not(feature = "path_finding"))]
let systems = (circle_travel, check_arrived, straight_travel);
#[cfg(feature = "path_finding")]
let systems = (circle_travel, check_arrived, straight_travel, update_travel_stop);
#[cfg(feature = "path_finding")]
app.insert_resource(GridInfo::default());
app.add_observer(next_des);
if self.states.is_empty() {
app.add_systems(Update, systems);
} else {
for state in &self.states {
app.add_systems(Update, systems.run_if(in_state(state.clone())));
}
}
}
}
#[derive(Component)]
pub struct LinearMovement {
pub speed: f32,
pub des: Vec<Destination>,
pub is_repeated: bool,
pub is_freezed: bool,
pub is_stopped: bool,
pub epsilon: f32,
pub offset: Vec3,
}
impl Default for LinearMovement {
fn default() -> Self {
Self {
speed: 0.,
des: Vec::new(),
is_repeated: false,
is_freezed: false,
is_stopped: false,
epsilon: 1e-4,
offset: Vec3::ZERO,
}
}
}
impl LinearMovement {
pub fn freeze(&mut self) {
self.is_freezed = true;
}
pub fn go(&mut self) {
self.is_freezed = false;
}
pub fn stop(&mut self) {
self.des = Vec::new();
self.is_stopped = true;
}
}
#[cfg(feature = "path_finding")]
#[derive(Resource, Default)]
pub struct GridInfo {
pub tile_size: Vec3,
pub grid_offset: Vec3,
}
#[cfg(not(any(feature = "collider_2d", feature = "collider_3d")))]
fn straight_travel(time: Res<Time>, mut query: Query<(&mut Transform, &LinearMovement)>) {
for (mut transform, movement) in query.iter_mut() {
if movement.des.is_empty() || movement.is_freezed {
continue;
}
let des = movement.des.first().unwrap();
let velocity = if let Some(custom_v) = des.custom_velocity { custom_v } else { movement.speed };
let v = velocity * time.delta_secs();
let next_stop = movement.des.first().unwrap().pos + movement.offset;
if cfg!(feature = "2d") {
let xy = transform.translation.xy().move_towards(next_stop.xy(), v);
transform.translation.x = xy.x;
transform.translation.y = xy.y;
} else {
let xyz = transform.translation.move_towards(next_stop, v);
transform.translation = xyz;
}
}
}
#[cfg(any(feature = "collider_2d", feature = "collider_3d"))]
fn straight_travel(mut query: Query<(&mut Transform, &mut LinearMovement, &mut LinearVelocity)>, time: Res<Time>) {
for (mut transform, mut movement, mut velocity) in query.iter_mut() {
if movement.is_stopped {
**velocity = Vector::ZERO;
movement.is_stopped = false;
continue;
}
if movement.des.is_empty() || movement.is_freezed {
continue;
}
let des = movement.des.first().unwrap();
let flat_vel = if let Some(custom_v) = des.custom_velocity { custom_v } else { movement.speed };
let next_stop = movement.des.first().unwrap().pos + movement.offset;
#[cfg(feature = "collider_3d")]
let direction = next_stop - transform.translation;
#[cfg(feature = "collider_2d")]
let direction = next_stop.xy() - transform.translation.xy();
let len = direction.length();
if len <= flat_vel * time.delta_secs() {
**velocity = Vector::ZERO;
transform.translation = next_stop;
} else {
**velocity = direction / len * flat_vel;
}
}
}
fn check_arrived(
mut commands: Commands,
#[cfg(not(feature = "path_finding"))] mut query: Query<(&Transform, &mut LinearMovement, Entity, Entity, Entity)>,
#[cfg(feature = "path_finding")] mut query: Query<(
&Transform,
&mut LinearMovement,
Entity,
&mut AgentPos,
&NextPos,
)>,
) {
for (transform, mut movement, e, mut _agent_pos, _next_pos) in query.iter_mut() {
if movement.des.is_empty() || movement.is_freezed {
continue;
}
let next_stop = movement.des.first().unwrap().pos + movement.offset;
let mut arrived = false;
if cfg!(feature = "2d") {
if transform.translation.xy().distance(next_stop.xy()) <= movement.epsilon {
arrived = true;
}
} else {
if transform.translation.distance(next_stop) <= movement.epsilon {
arrived = true;
}
}
if arrived {
commands.trigger(Arrived {
entity: e,
pos: next_stop,
});
if movement.is_repeated {
let first_des = movement.as_ref().des.first().unwrap().clone();
movement.des.push(first_des);
}
movement.des.remove(0);
#[cfg(feature = "path_finding")]
{
_agent_pos.0 = _next_pos.0;
commands.entity(e).remove::<NextPos>();
}
}
}
}
#[cfg(feature = "path_finding")]
fn update_travel_stop(mut query: Query<(&NextPos, &mut LinearMovement)>, grid_info: Res<GridInfo>) {
for (next_pos, mut movement) in query.iter_mut() {
let next_pos_f = next_pos.0.as_vec3() * grid_info.tile_size + grid_info.grid_offset;
if let Some(des) = movement.des.first() {
if next_pos_f != des.pos {
movement.des = vec![Destination::from_pos(next_pos_f)];
}
} else {
movement.des = vec![Destination::from_pos(next_pos_f)];
}
}
}
fn next_des(trigger: On<NextDes>, mut query: Query<&mut LinearMovement>) {
if let Ok(mut movement) = query.get_mut(trigger.entity) {
if !trigger.is_chain {
movement.des.clear();
}
movement.des.push(trigger.des.clone());
}
}