bevy_steering/behaviors/
avoid.rs

1use avian3d::prelude::*;
2use bevy::{
3    ecs::{lifecycle::HookContext, query::QueryData, world::DeferredWorld},
4    prelude::*,
5};
6use derivative::Derivative;
7#[cfg(feature = "serialize")]
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    agent::SteeringAgent,
12    control::{BehaviorType, SteeringOutputs, SteeringTarget},
13    prelude::{NearbyObstacles, TrackNearbyObstacles},
14};
15
16/// Avoid obstacles. This is not a replacement for navigation
17/// (e.g. A* or similar.) An agent can get stuck even with this
18/// behavior. Use this behavior so that the agent routes around
19/// obstacles that are in the way. Configure obstacle detection
20/// by adding a [TrackNearbyObstacles] component, otherwise it uses
21/// a default configuration.
22#[derive(Component, Debug, Clone, Reflect, Derivative)]
23#[derivative(Default)]
24#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
25#[cfg_attr(feature = "serialize", serde(default))]
26#[require(TrackNearbyObstacles)]
27#[component(on_remove = on_avoid_remove)]
28pub struct Avoid {
29    /// Only avoid obstacles whose nearest point is at most this distance away.
30    #[derivative(Default(value = "1.0"))]
31    pub distance: f32,
32}
33
34impl Avoid {
35    pub fn with_distance(mut self, distance: f32) -> Self {
36        self.distance = distance;
37        self
38    }
39}
40
41#[derive(QueryData)]
42#[query_data(mutable)]
43pub struct AvoidBehaviorAgentQuery {
44    entity: Entity,
45    agent: &'static SteeringAgent,
46    avoid: &'static Avoid,
47    velocity: &'static LinearVelocity,
48    global_transform: &'static GlobalTransform,
49    obstacles: &'static NearbyObstacles,
50    track: &'static TrackNearbyObstacles,
51    outputs: &'static mut SteeringOutputs,
52}
53
54pub(crate) fn run(mut query: Query<AvoidBehaviorAgentQuery>) {
55    for mut agent in query.iter_mut() {
56        let mut target = SteeringTarget::default();
57
58        for obstacle in agent.obstacles.values() {
59            if obstacle.distance > agent.avoid.distance {
60                continue;
61            }
62            let Some((impact_normal, _)) = obstacle.impact_normals else {
63                continue;
64            };
65            // Move away from the direction of impact
66            target.set_danger(-impact_normal);
67        }
68
69        agent.outputs.set(BehaviorType::Avoid, target);
70    }
71}
72
73fn on_avoid_remove(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
74    if let Some(mut outputs) = world.get_mut::<SteeringOutputs>(entity) {
75        outputs.clear(BehaviorType::Avoid);
76    }
77}