big-brain
big-brain
is a Utility AI
library for games, built for the Bevy Game Engine
It lets you define complex, intricate AI behaviors for your entities based on their perception of the world. Definitions are heavily data-driven, using plain Rust, and you only need to program Scorers (entities that look at your game world and come up with a Score), and Actions (entities that perform actual behaviors upon the world). No other code is needed for actual AI behavior.
See the documentation for more details.
Features
- Highly concurrent/parallelizable evaluation.
- Integrates smoothly with Bevy.
- Easy AI definition using idiomatic Rust builders. You don't have to be some genius to define behavior that feels realistic to players.
- High performance--supports hundreds of thousands of concurrent AIs.
- Graceful degradation--can be configured such that the less frame time is available, the slower an AI might "seem", without dragging down framerates, by simply processing fewer events per tick.
- Proven game AI model.
- Low code overhead--you only define two types of application-dependent things, and everything else is building blocks!
- Highly composable and reusable.
- State machine-style continuous actions/behaviors.
- Action cancellation.
Example
As a developer, you write application-dependent code to define
Scorers
and Actions
, and then put it all together
like building blocks, using Thinkers
that will define the
actual behavior.
Scorers
Scorer
s are entities that look at the world and evaluate into Score
values. You can think of them as the "eyes" of the AI system. They're a highly-parallel way of being able to look at the World
and use it to make some decisions later.
use bevy::prelude::*;
use big_brain::prelude::*;
#[derive(Debug, Clone, Component)]
pub struct Thirsty;
pub fn thirsty_scorer_system(
thirsts: Query<&Thirst>,
mut query: Query<(&Actor, &mut Score), With<Thirsty>>,
) {
for (Actor(actor), mut score) in query.iter_mut() {
if let Ok(thirst) = thirsts.get(*actor) {
score.set(thirst.thirst);
}
}
}
Actions
Action
s are the actual things your entities will do. They are connected to
ActionState
s that represent the current execution
state of the state machine.
use bevy::prelude::*;
use big_brain::prelude::*;
#[derive(Debug, Clone, Component)]
pub struct Drink;
fn drink_action_system(
mut thirsts: Query<&mut Thirst>,
mut query: Query<(&Actor, &mut ActionState), With<Drink>>,
) {
for (Actor(actor), mut state) in query.iter_mut() {
if let Ok(mut thirst) = thirsts.get_mut(*actor) {
match *state {
ActionState::Requested => {
thirst.thirst = 10.0;
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
}
Thinkers
Finally, you can use it when define the Thinker
, which you can attach as a
regular Component:
cmd.spawn().insert(Thirst::new(70.0, 2.0)).insert(
Thinker::build()
.picker(FirstToScore { threshold: 0.8 })
.when(Thirsty, Drink),
);
App
Once all that's done, we just add our systems and off we go!
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(BigBrainPlugin)
.add_startup_system(init_entities)
.add_system(thirst_system)
.add_system_to_stage(BigBrainStage::Actions, drink_action_system)
.add_system_to_stage(BigBrainStage::Scorers, thirsty_scorer_system)
.run();
Contributing
- Install the latest Rust toolchain (stable supported).
cargo run --example thirst
- Happy hacking!
License
This project is licensed under the Apache-2.0 License.