bevy_combat/ai/
movement.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Implements AI for moving and steering entities.

use crate::ai::idle::IdleBehavior;
use crate::combat::Target;
use crate::constants::FIXED_TIME_STEP;
use crate::math_util::*;
use crate::movement::{Heading, MaxTurnSpeed, TurnSpeed};
use bevy::prelude::*;

/// Indicates that an entity should turn towards a destination.
#[derive(Default)]
pub struct TurnToDestinationBehavior {
    pub destination: Vec3,
}

#[derive(Default)]
pub struct PursueBehavior;
pub const PROXIMITY_RADIUS: f32 = 64.0;

/// Turns entities with a [TurnToDestinationBehavior](TurnToDestinationBehavior.struct.html) towards their destination.
pub fn turn_to_destination(
    mut query: Query<(
        &TurnToDestinationBehavior,
        &GlobalTransform,
        &MaxTurnSpeed,
        &Heading,
        &mut TurnSpeed,
    )>,
) {
    for (behavior, transform, max_turn_speed, heading, mut turn_speed) in query.iter_mut() {
        // // Determine desired heading to target
        let delta = behavior.destination - transform.translation;
        let desired_heading = get_heading_to_point(delta);

        // Adjust rotation speed to aim for desired heading.
        let diff = get_angle_difference(desired_heading, heading.radians);
        turn_speed.radians_per_second = diff.signum()
            * max_turn_speed
                .radians_per_second
                .min(diff.abs() / FIXED_TIME_STEP);
        //println!("destination: {:?}, delta: {:?}.", behavior.destination, delta);
    }
    //println!("turn_to_destination: {:?} entities.", query.iter_mut().len());
}

/// Entity pursues their target.
pub fn pursue(
    mut commands: Commands,
    mut query: Query<(
        Entity,
        &PursueBehavior,
        &Target,
        &GlobalTransform,
        &mut TurnToDestinationBehavior,
    )>,
    pos_query: Query<&GlobalTransform>,
) {
    for (entity, _pursue, target, transform, mut turn_to) in query.iter_mut() {
        
        if target.0.is_none() {
            continue;
        }

        let result = pos_query.get_component::<GlobalTransform>(target.0.expect("target is none"));
        
        match result {
            Err(_) => {
                // target does not have position. Go to idle state
                commands.entity(entity).remove::<PursueBehavior>();
                commands.entity(entity).insert(IdleBehavior);
                continue;
            }
            Ok(target_transform) => {
                turn_to.destination = target_transform.translation;
                
                // if too close to target, evasive manoeuvre
                let delta = target_transform.translation - transform.translation;
                //println!("entity: {:?}, destination: {:?}, delta: {:?}.", target.0.expect("target"), turn_to.destination, delta);
                if delta.length_squared() < PROXIMITY_RADIUS * PROXIMITY_RADIUS {
                    commands
                        .entity(entity)
                        .remove_bundle::<(TurnToDestinationBehavior, PursueBehavior)>();
                    commands.entity(entity).insert(PeelManoeuvreBehavior);
                }
            }
        }
    }
    //println!("pursue: {:?} entities, {:?} err, {:?} ok.", query.iter_mut().len(), err_count, ok_count);
}

/// A 'peel' manoeuvre causes an entity to move away from its target.
/// 
/// It is usually triggered when the entity gets too close.
pub struct PeelManoeuvreBehavior;
const ENGAGEMENT_RADIUS: f32 = 128.0;

pub fn peel_manoeuvre(
    mut commands: Commands,
    mut query: Query<(
        Entity,
        &PeelManoeuvreBehavior,
        &Target,
        &GlobalTransform,
        &Heading,
        &MaxTurnSpeed,
        &mut TurnSpeed,
    )>,
    pos_query: Query<&GlobalTransform>
) {
    for (entity, _peel, target, transform, heading, max_turn_speed, mut turn_speed) in query.iter_mut() {
        
        if target.0.is_none() {
            continue;
        }

        let result = pos_query.get_component::<GlobalTransform>(target.0.expect("target is none"));
        match result {
            Err(_) => {
                // target does not have position. Disengage.
                commands.entity(entity).remove::<PeelManoeuvreBehavior>();
                commands.entity(entity).insert(IdleBehavior);
                commands.entity(entity).insert(TurnToDestinationBehavior::default());
                continue;
            }
            Ok(target_transform) => {
                // Turn away from the enemy.
                let mut delta = target_transform.translation - transform.translation;
                delta.z = 0.0;
                let angle_diff = get_angle_difference(get_heading_to_point(delta), heading.radians);

                if angle_diff.abs() < 0.3 * std::f32::consts::PI {
                    turn_speed.radians_per_second = -max_turn_speed.radians_per_second * angle_diff.signum();
                }
                else {
                    turn_speed.radians_per_second = 0.0;
                }
                
                // Remain in evasive manoeuvre until a certain distance to target is reached.               
                if delta.length_squared() > ENGAGEMENT_RADIUS * ENGAGEMENT_RADIUS {
                    commands
                        .entity(entity)
                        .remove::<PeelManoeuvreBehavior>();
                    commands.entity(entity).insert(PursueBehavior);
                    commands.entity(entity).insert(TurnToDestinationBehavior::default());
                }
            }
        }
    }
    //println!("peel: {:?} entities, {:?} err, {:?} ok.", query.iter_mut().len(), err_count, ok_count);
}