big_brain/lib.rs
1//! [](https://crates.io/crates/big-brain)
2//! [](https://docs.rs/big-brain)
3//! [](./LICENSE.md)
5//!
6//! `big-brain` is a [Utility
7//! AI](https://en.wikipedia.org/wiki/Utility_system) library for games, built
8//! for the [Bevy Game Engine](https://bevyengine.org/)
9//!
10//! It lets you define complex, intricate AI behaviors for your entities based
11//! on their perception of the world. Definitions are heavily data-driven,
12//! using plain Rust, and you only need to program Scorers (entities that look
13//! at your game world and come up with a Score), and Actions (entities that
14//! perform actual behaviors upon the world). No other code is needed for
15//! actual AI behavior.
16//!
17//! See [the documentation](https://docs.rs/big-brain) for more details.
18//!
19//! ### Features
20//!
21//! * Highly concurrent/parallelizable evaluation.
22//! * Integrates smoothly with Bevy.
23//! * Proven game AI model.
24//! * Highly composable and reusable.
25//! * State machine-style continuous actions/behaviors.
26//! * Action cancellation.
27//!
28//! ### Example
29//!
30//! As a developer, you write application-dependent code to define
31//! [`Scorers`](#scorers) and [`Actions`](#actions), and then put it all
32//! together like building blocks, using [`Thinkers`](#thinkers) that will
33//! define the actual behavior.
34//!
35//! #### Scorers
36//!
37//! `Scorer`s are entities that look at the world and evaluate into `Score`
38//! values. You can think of them as the "eyes" of the AI system. They're a
39//! highly-parallel way of being able to look at the `World` and use it to
40//! make some decisions later.
41//!
42//! ```rust
43//! use bevy::prelude::*;
44//! use big_brain::prelude::*;
45//! # #[derive(Component, Debug)]
46//! # struct Thirst { thirst: f32 }
47//!
48//! #[derive(Debug, Clone, Component, ScorerBuilder)]
49//! pub struct Thirsty;
50//!
51//! pub fn thirsty_scorer_system(
52//! thirsts: Query<&Thirst>,
53//! mut query: Query<(&Actor, &mut Score), With<Thirsty>>,
54//! ) {
55//! for (Actor(actor), mut score) in query.iter_mut() {
56//! if let Ok(thirst) = thirsts.get(*actor) {
57//! score.set(thirst.thirst);
58//! }
59//! }
60//! }
61//! ```
62//!
63//! #### Actions
64//!
65//! `Action`s are the actual things your entities will _do_. They are
66//! connected to `ActionState`s that represent the current execution state of
67//! the state machine.
68//!
69//! ```rust
70//! use bevy::prelude::*;
71//! use big_brain::prelude::*;
72//! # #[derive(Component, Debug)]
73//! # struct Thirst { thirst: f32 }
74//!
75//! #[derive(Debug, Clone, Component, ActionBuilder)]
76//! pub struct Drink;
77//!
78//! fn drink_action_system(
79//! mut thirsts: Query<&mut Thirst>,
80//! mut query: Query<(&Actor, &mut ActionState), With<Drink>>,
81//! ) {
82//! for (Actor(actor), mut state) in query.iter_mut() {
83//! if let Ok(mut thirst) = thirsts.get_mut(*actor) {
84//! match *state {
85//! ActionState::Requested => {
86//! thirst.thirst = 10.0;
87//! *state = ActionState::Success;
88//! }
89//! ActionState::Cancelled => {
90//! *state = ActionState::Failure;
91//! }
92//! _ => {}
93//! }
94//! }
95//! }
96//! }
97//! ```
98//!
99//! #### Thinkers
100//!
101//! Finally, you can use it when define the `Thinker`, which you can attach as
102//! a regular Component:
103//!
104//! ```rust
105//! # use bevy::prelude::*;
106//! # use big_brain::prelude::*;
107//! # #[derive(Debug, Component)]
108//! # struct Thirst(f32, f32);
109//! # #[derive(Debug, Clone, Component, ScorerBuilder)]
110//! # struct Thirsty;
111//! # #[derive(Debug, Clone, Component, ActionBuilder)]
112//! # struct Drink;
113//! fn spawn_entity(cmd: &mut Commands) {
114//! cmd.spawn((
115//! Thirst(70.0, 2.0),
116//! Thinker::build()
117//! .picker(FirstToScore { threshold: 0.8 })
118//! .when(Thirsty, Drink),
119//! ));
120//! }
121//! ```
122//!
123//! #### App
124//!
125//! Once all that's done, we just add our systems and off we go!
126//!
127//! ```no_run
128//! # use bevy::prelude::*;
129//! # use big_brain::prelude::*;
130//! # fn init_entities() {}
131//! # fn thirst_system() {}
132//! # fn drink_action_system() {}
133//! # fn thirsty_scorer_system() {}
134//! fn main() {
135//! App::new()
136//! .add_plugins(DefaultPlugins)
137//! .add_plugins(BigBrainPlugin::new(PreUpdate))
138//! .add_systems(Startup, init_entities)
139//! .add_systems(Update, thirst_system)
140//! .add_systems(PreUpdate, drink_action_system.in_set(BigBrainSet::Actions))
141//! .add_systems(PreUpdate, thirsty_scorer_system.in_set(BigBrainSet::Scorers))
142//! .run();
143//! }
144//! ```
145//!
146//! ### bevy version and MSRV
147//!
148//! The current version of `big-brain` is compatible with `bevy` 0.12.1.
149//!
150//! The Minimum Supported Rust Version for `big-brain` should be considered to
151//! be the same as `bevy`'s, which as of the time of this writing was "the
152//! latest stable release".
153//!
154//! ### Reflection
155//!
156//! All relevant `big-brain` types implement the bevy `Reflect` trait, so you
157//! should be able to get some useful display info while using things like
158//! [`bevy_inspector_egui`](https://crates.io/crates/bevy_inspector_egui).
159//!
160//! This implementation should **not** be considered stable, and individual
161//! fields made visible may change at **any time** and not be considered
162//! towards semver. Please use this feature **only for debugging**.
163//!
164//! ### Contributing
165//!
166//! 1. Install the latest Rust toolchain (stable supported).
167//! 2. `cargo run --example thirst`
168//! 3. Happy hacking!
169//!
170//! ### License
171//!
172//! This project is licensed under [the Apache-2.0 License](LICENSE.md).
173
174pub mod evaluators;
175pub mod pickers;
176
177pub mod actions;
178pub mod choices;
179pub mod measures;
180pub mod scorers;
181pub mod thinker;
182
183pub mod prelude {
184 /*!
185 Convenience module with the core types you're most likely to use when working with Big Brain. Mean to be used like `use big_brain::prelude::*;`
186 */
187 use super::*;
188
189 pub use super::BigBrainPlugin;
190 pub use super::BigBrainSet;
191 pub use actions::{ActionBuilder, ActionState, ConcurrentMode, Concurrently, Steps};
192 pub use big_brain_derive::{ActionBuilder, ScorerBuilder};
193 pub use evaluators::{Evaluator, LinearEvaluator, PowerEvaluator, SigmoidEvaluator};
194 pub use measures::{ChebyshevDistance, Measure, WeightedProduct, WeightedSum};
195 pub use pickers::{FirstToScore, Highest, HighestToScore, Picker};
196 pub use scorers::{
197 AllOrNothing, EvaluatingScorer, FixedScore, MeasuredScorer, ProductOfScorers, Score,
198 ScorerBuilder, SumOfScorers, WinningScorer,
199 };
200 pub use thinker::{
201 Action, ActionSpan, Actor, HasThinker, Scorer, ScorerSpan, Thinker, ThinkerBuilder,
202 };
203}
204
205use bevy::{
206 ecs::{intern::Interned, schedule::ScheduleLabel},
207 prelude::*,
208};
209
210/// Core [`Plugin`] for Big Brain behavior. Required for any of the
211/// [`Thinker`](thinker::Thinker)-related magic to work.
212///
213/// ### Example
214///
215/// ```no_run
216/// use bevy::prelude::*;
217/// use big_brain::prelude::*;
218///
219/// App::new()
220/// .add_plugins((DefaultPlugins, BigBrainPlugin::new(PreUpdate)))
221/// // ...insert entities and other systems.
222/// .run();
223#[derive(Debug, Clone, Reflect)]
224#[reflect(from_reflect = false)]
225pub struct BigBrainPlugin {
226 #[reflect(ignore)]
227 schedule: Interned<dyn ScheduleLabel>,
228 #[reflect(ignore)]
229 cleanup_schedule: Interned<dyn ScheduleLabel>,
230}
231
232impl BigBrainPlugin {
233 /// Create the BigBrain plugin which runs the scorers, thinker and actions in the specified
234 /// schedule
235 pub fn new(schedule: impl ScheduleLabel) -> Self {
236 Self {
237 schedule: schedule.intern(),
238 cleanup_schedule: Last.intern(),
239 }
240 }
241
242 /// Overwrite the Schedule that is used to run cleanup tasks. By default this happens in Last.
243 pub fn set_cleanup_schedule(mut self, cleanup_schedule: impl ScheduleLabel) -> Self {
244 self.cleanup_schedule = cleanup_schedule.intern();
245 self
246 }
247}
248
249impl Plugin for BigBrainPlugin {
250 fn build(&self, app: &mut App) {
251 app.configure_sets(
252 self.schedule.intern(),
253 (
254 BigBrainSet::Scorers,
255 BigBrainSet::Thinkers,
256 BigBrainSet::Actions,
257 )
258 .chain(),
259 )
260 .configure_sets(self.cleanup_schedule.intern(), BigBrainSet::Cleanup)
261 .add_systems(
262 self.schedule.intern(),
263 (
264 scorers::fixed_score_system,
265 scorers::measured_scorers_system,
266 scorers::all_or_nothing_system,
267 scorers::sum_of_scorers_system,
268 scorers::product_of_scorers_system,
269 scorers::winning_scorer_system,
270 scorers::evaluating_scorer_system,
271 )
272 .in_set(BigBrainSet::Scorers),
273 )
274 .add_systems(
275 self.schedule.intern(),
276 thinker::thinker_system.in_set(BigBrainSet::Thinkers),
277 )
278 .add_systems(
279 self.schedule.intern(),
280 (actions::steps_system, actions::concurrent_system).in_set(BigBrainSet::Actions),
281 )
282 .add_systems(
283 self.cleanup_schedule.intern(),
284 (
285 thinker::thinker_component_attach_system,
286 thinker::thinker_component_detach_system,
287 thinker::actor_gone_cleanup,
288 )
289 .in_set(BigBrainSet::Cleanup),
290 );
291 }
292}
293
294/// [`BigBrainPlugin`] system sets. Use these to schedule your own
295/// actions/scorers/etc.
296#[derive(Clone, Debug, Hash, Eq, PartialEq, SystemSet, Reflect)]
297pub enum BigBrainSet {
298 /// Scorers are evaluated in this set.
299 Scorers,
300 /// Actions are executed in this set.
301 Actions,
302 /// Thinkers run their logic in this set.
303 Thinkers,
304 /// Various internal cleanup items run in this final set.
305 Cleanup,
306}