|
| 1 | +//! This example describes how to create an action that takes multiple steps. |
| 2 | +//! |
| 3 | +//! It is similar to the thirst example, but instead of just magically quenching a thirst, |
| 4 | +//! the actor must be near a water source in order to drink. |
| 5 | +//! |
| 6 | +//! Note that it does not matter if the actor is already near a water source: |
| 7 | +//! the MoveToWaterSource action will simply terminate immediately. |
| 8 | +
|
| 9 | +use bevy::prelude::*; |
| 10 | +use big_brain::actions::StepsBuilder; |
| 11 | +use big_brain::prelude::*; |
| 12 | + |
| 13 | +/// First, we make a simple Position component. |
| 14 | +#[derive(Component, Debug, Copy, Clone)] |
| 15 | +pub struct Position { |
| 16 | + pub position: Vec2, |
| 17 | +} |
| 18 | + |
| 19 | +/// A marker component for an entity that describes a water source. |
| 20 | +#[derive(Component, Debug)] |
| 21 | +pub struct WaterSource; |
| 22 | + |
| 23 | +/// We steal the Thirst component from the thirst example. |
| 24 | +#[derive(Component, Debug)] |
| 25 | +pub struct Thirst { |
| 26 | + /// How much thirstier the entity gets over time. |
| 27 | + pub per_second: f32, |
| 28 | + /// How much thirst the entity currently has. |
| 29 | + pub thirst: f32, |
| 30 | +} |
| 31 | + |
| 32 | +impl Thirst { |
| 33 | + pub fn new(thirst: f32, per_second: f32) -> Self { |
| 34 | + Self { thirst, per_second } |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +/// A simple system that just pushes the thirst value up over time. |
| 39 | +/// Just a plain old Bevy system, big-brain is not involved yet. |
| 40 | +pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) { |
| 41 | + |
| 42 | + for mut thirst in thirsts.iter_mut() { |
| 43 | + |
| 44 | + thirst.thirst += thirst.per_second * time.delta_seconds(); |
| 45 | + |
| 46 | + // Thirst is capped at 100.0 |
| 47 | + if thirst.thirst >= 100.0 { |
| 48 | + thirst.thirst = 100.0; |
| 49 | + } |
| 50 | + |
| 51 | + println!("Thirst: {}", thirst.thirst); |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +/// An action where the actor moves to the closest water source |
| 56 | +#[derive(Clone, Component, Debug)] |
| 57 | +pub struct MoveToWaterSource { |
| 58 | + // The movement speed of the actor. |
| 59 | + speed: f32, |
| 60 | +} |
| 61 | + |
| 62 | +/// Closest distance to a water source to be able to drink from it. |
| 63 | +const MAX_DISTANCE: f32 = 0.1; |
| 64 | + |
| 65 | +fn move_to_water_source_action_system( |
| 66 | + time: Res<Time>, |
| 67 | + // Find all water sources |
| 68 | + mut waters: Query<&Position, With<WaterSource>>, |
| 69 | + // We use Without to make disjoint queries. |
| 70 | + mut positions: Query<&mut Position, Without<WaterSource>>, |
| 71 | + // A query on all current MoveToWaterSource actions. |
| 72 | + mut action_query: Query<(&Actor, &mut ActionState, &MoveToWaterSource)>, |
| 73 | +) { |
| 74 | + // Loop through all actions, just like you'd loop over all entities in any other query. |
| 75 | + for (actor, mut action_state, move_to) in action_query.iter_mut() { |
| 76 | + |
| 77 | + // Different behavior depending on action state. |
| 78 | + match *action_state { |
| 79 | + // Action was just requested; it hasn't been seen before. |
| 80 | + ActionState::Requested => { |
| 81 | + println!("Let's go find some water!"); |
| 82 | + // We don't really need any initialization code here, since the queries are cheap enough. |
| 83 | + *action_state = ActionState::Executing; |
| 84 | + }, |
| 85 | + ActionState::Executing => { |
| 86 | + |
| 87 | + // Look up the actor's position. |
| 88 | + let mut actor_position = |
| 89 | + positions |
| 90 | + .get_mut(actor.0) |
| 91 | + .expect("actor has no position"); |
| 92 | + |
| 93 | + println!("Actor position: {:?}", actor_position.position); |
| 94 | + |
| 95 | + // Look up the water source closest to them. |
| 96 | + let closest_water_source = |
| 97 | + find_closest_water_source(&waters, &actor_position); |
| 98 | + |
| 99 | + // Find how far we are from it. |
| 100 | + let delta = closest_water_source.position - actor_position.position; |
| 101 | + |
| 102 | + let distance = delta.length(); |
| 103 | + |
| 104 | + println!("Distance: {}", distance); |
| 105 | + |
| 106 | + if distance > MAX_DISTANCE { |
| 107 | + // We're still too far, take a step toward it! |
| 108 | + |
| 109 | + println!("Stepping closer."); |
| 110 | + |
| 111 | + // How far can we travel during this frame? |
| 112 | + let step_size = time.delta_seconds() * move_to.speed; |
| 113 | + |
| 114 | + let step = if step_size > distance { |
| 115 | + // We'll move by the full step |
| 116 | + delta / distance * step_size |
| 117 | + } else { |
| 118 | + // Move only the remaining distance, or we might overshoot. |
| 119 | + delta |
| 120 | + }; |
| 121 | + |
| 122 | + // Move the actor. |
| 123 | + actor_position.position += step; |
| 124 | + |
| 125 | + } else { |
| 126 | + // We're within the required distance! We can declare success. |
| 127 | + |
| 128 | + println!("We got there!"); |
| 129 | + |
| 130 | + // The action will be cleaned up automatically. |
| 131 | + *action_state = ActionState::Success; |
| 132 | + } |
| 133 | + } |
| 134 | + ActionState::Cancelled => { |
| 135 | + // Always treat cancellations, or we might keep doing this forever! |
| 136 | + // You don't need to terminate immediately, by the way, this is only a flag that |
| 137 | + // the cancellation has been requested. If the actor is balancing on a tightrope, |
| 138 | + // for instance, you may let them walk off before ending the action. |
| 139 | + *action_state = ActionState::Failure; |
| 140 | + } |
| 141 | + _ => {} |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | +} |
| 146 | + |
| 147 | +/// A utility function that finds the closest water source to the actor. |
| 148 | +fn find_closest_water_source(waters: &Query<&Position, With<WaterSource>>, actor_position: &Position) -> Position { |
| 149 | + waters |
| 150 | + .iter() |
| 151 | + .min_by(|a, b| { |
| 152 | + let da = (a.position - actor_position.position).length_squared(); |
| 153 | + let db = (b.position - actor_position.position).length_squared(); |
| 154 | + da.partial_cmp(&db).unwrap() |
| 155 | + }) |
| 156 | + .expect("no water sources") |
| 157 | + .clone() |
| 158 | +} |
| 159 | + |
| 160 | +/// A simple action: the actor's thirst shall decrease, but only if they are near a water source. |
| 161 | +#[derive(Clone, Component, Debug)] |
| 162 | +pub struct Drink { |
| 163 | + per_second: f32, |
| 164 | +} |
| 165 | + |
| 166 | +fn drink_action_system( |
| 167 | + time: Res<Time>, |
| 168 | + mut thirsts: Query<(&Position, &mut Thirst), Without<WaterSource>>, |
| 169 | + mut waters: Query<&Position, With<WaterSource>>, |
| 170 | + mut query: Query<(&Actor, &mut ActionState, &Drink)>, |
| 171 | +) { |
| 172 | + // Loop through all actions, just like you'd loop over all entities in any other query. |
| 173 | + for (Actor(actor), mut state, drink) in query.iter_mut() { |
| 174 | + |
| 175 | + // Look up the actor's position and thirst from the Actor component in the action entity. |
| 176 | + let (actor_position, mut thirst) = thirsts.get_mut(*actor).expect("actor has no thirst"); |
| 177 | + |
| 178 | + match *state { |
| 179 | + ActionState::Requested => { |
| 180 | + // We'll start drinking as soon as we're requested to do so. |
| 181 | + *state = ActionState::Executing; |
| 182 | + } |
| 183 | + ActionState::Executing => { |
| 184 | + |
| 185 | + // Look up the closest water source. |
| 186 | + // Note that there is no explicit passing of a selected water source from the GoToWaterSource action, |
| 187 | + // so we look it up again. Note that this decouples the actions from each other, |
| 188 | + // so if the actor is already close to a water source, the GoToWaterSource action |
| 189 | + // will not be necessary (though it will not harm either). |
| 190 | + // |
| 191 | + // Essentially, being close to a water source would be a precondition for the Drink action. |
| 192 | + // How this precondition was fulfilled is not this code's concern. |
| 193 | + let closest_water_source = |
| 194 | + find_closest_water_source(&waters, &*actor_position); |
| 195 | + |
| 196 | + // Find how far we are from it. |
| 197 | + let distance = (closest_water_source.position - actor_position.position).length(); |
| 198 | + |
| 199 | + // Are we close enough? |
| 200 | + if distance < MAX_DISTANCE { |
| 201 | + println!("Drinking!"); |
| 202 | + |
| 203 | + // Start reducing the thirst. Alternatively, you could send out some kind of |
| 204 | + // DrinkFromSource event that indirectly decreases thirst. |
| 205 | + thirst.thirst -= drink.per_second * time.delta_seconds(); |
| 206 | + |
| 207 | + // Once we hit 0 thirst, we stop drinking and report success. |
| 208 | + if thirst.thirst <= 0.0 { |
| 209 | + thirst.thirst = 0.0; |
| 210 | + *state = ActionState::Success; |
| 211 | + } |
| 212 | + } else { |
| 213 | + // The actor was told to drink, but they can't drink when they're so far away! |
| 214 | + // The action doesn't know how to deal with this case, it's the overarching system's |
| 215 | + // to fulfill the precondition. |
| 216 | + println!("We're too far away!"); |
| 217 | + *state = ActionState::Failure; |
| 218 | + } |
| 219 | + } |
| 220 | + // All Actions should make sure to handle cancellations! |
| 221 | + // Drinking is not a complicated action, so we can just interrupt it immediately. |
| 222 | + ActionState::Cancelled => { |
| 223 | + *state = ActionState::Failure; |
| 224 | + } |
| 225 | + _ => {} |
| 226 | + } |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +// Scorers are the same as in the thirst example. |
| 231 | +#[derive(Clone, Component, Debug)] |
| 232 | +pub struct Thirsty; |
| 233 | + |
| 234 | +pub fn thirsty_scorer_system( |
| 235 | + thirsts: Query<&Thirst>, |
| 236 | + mut query: Query<(&Actor, &mut Score), With<Thirsty>>, |
| 237 | +) { |
| 238 | + for (Actor(actor), mut score) in query.iter_mut() { |
| 239 | + if let Ok(thirst) = thirsts.get(*actor) { |
| 240 | + score.set(thirst.thirst / 100.); |
| 241 | + } |
| 242 | + } |
| 243 | +} |
| 244 | + |
| 245 | +pub fn init_entities(mut cmd: Commands) { |
| 246 | + |
| 247 | + // Spawn two water sources. |
| 248 | + cmd.spawn() |
| 249 | + .insert(WaterSource) |
| 250 | + .insert(Position { |
| 251 | + position: Vec2::new(10.0, 10.0), |
| 252 | + }); |
| 253 | + |
| 254 | + cmd.spawn() |
| 255 | + .insert(WaterSource) |
| 256 | + .insert(Position { |
| 257 | + position: Vec2::new(-10.0, 0.0), |
| 258 | + }); |
| 259 | + |
| 260 | + // We use the Steps struct to essentially build a "MoveAndDrink" action by composing |
| 261 | + // the MoveToWaterSource and Drink actions. |
| 262 | + // |
| 263 | + // If either of the steps fails, the whole action fails. That is: if the actor somehow fails |
| 264 | + // to move to the water source (which is not possible in our case) they will not attempt to |
| 265 | + // drink either. Getting them un-stuck from that situation is then up to other possible actions. |
| 266 | + // |
| 267 | + // We build up a list of steps that make it so that the actor will... |
| 268 | + let move_and_drink = |
| 269 | + Steps::build() |
| 270 | + // ...move to the water source... |
| 271 | + .step(MoveToWaterSource { speed: 1.0 }) |
| 272 | + // ...and then drink. |
| 273 | + .step(Drink { per_second: 10.0 }); |
| 274 | + |
| 275 | + // Build the thinker |
| 276 | + let thinker = Thinker::build() |
| 277 | + // We don't do anything unless we're thirsty enough. |
| 278 | + .picker(FirstToScore { threshold: 0.8 }) |
| 279 | + .when( |
| 280 | + Thirsty, |
| 281 | + move_and_drink, |
| 282 | + ); |
| 283 | + |
| 284 | + cmd.spawn() |
| 285 | + .insert(Thirst::new(75.0, 2.0)) |
| 286 | + .insert(Position { |
| 287 | + position: Vec2::new(0.0, 0.0), |
| 288 | + }) |
| 289 | + .insert( |
| 290 | + thinker, |
| 291 | + ); |
| 292 | +} |
| 293 | + |
| 294 | +fn main() { |
| 295 | + // Once all that's done, we just add our systems and off we go! |
| 296 | + App::new() |
| 297 | + .add_plugins(DefaultPlugins) |
| 298 | + .add_plugin(BigBrainPlugin) |
| 299 | + .add_startup_system(init_entities) |
| 300 | + .add_system(thirst_system) |
| 301 | + .add_system_to_stage(BigBrainStage::Actions, drink_action_system) |
| 302 | + .add_system_to_stage(BigBrainStage::Actions, move_to_water_source_action_system) |
| 303 | + .add_system_to_stage(BigBrainStage::Scorers, thirsty_scorer_system) |
| 304 | + .run(); |
| 305 | +} |
0 commit comments