big_brain/
lib.rs

1//! [![crates.io](https://img.shields.io/crates/v/big-brain.svg)](https://crates.io/crates/big-brain)
2//! [![docs.rs](https://docs.rs/big-brain/badge.svg)](https://docs.rs/big-brain)
3//! [![Apache
4//! 2.0](https://img.shields.io/badge/license-Apache-blue.svg)](./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}