Newer
Older
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use bevy::prelude::*;
use big_brain::{evaluators::*, *};
// First, we define a "Thirst" component and associated system. This is NOT
// THE AI. It's a plain old system that just makes an entity "thirstier" over
// time. This is what the AI will later interact with.
//
// There's nothing special here. It's a plain old Bevy component.
#[derive(Debug)]
pub struct Thirst {
pub per_second: f32,
pub thirst: f32,
}
impl Thirst {
pub fn new(thirst: f32, per_second: f32) -> Self {
Self { thirst, per_second }
}
}
pub fn thirst_system(time: Res<Time>, mut thirsts: Query<&mut Thirst>) {
for mut thirst in thirsts.iter_mut() {
thirst.thirst +=
thirst.per_second * (time.delta().as_micros() as f32 / 1000000.0);
println!("Getting thirstier...{}%", thirst.thirst);
}
}
// The second step is to define an action. What can the AI do, and how does it
// do it? This is the first bit involving Big Brain itself, and there's a few
// pieces you need:
// First, you need an Action struct, and derive Action.
//
// These actions will be spawned and queued by the game engine when their
// conditions trigger (we'll configure what these are later).
#[derive(Debug, Action)]
pub struct DrinkAction {}
// Associated with that DrinkAction, you then need to have a system that will
// actually execute those actions when they're "spawned" by the Big Brain
// engine.
//
// In our case, we want the Thirst components, since we'll be changing those.
// Additionally, we want to pick up the DrinkAction components, as well as
// their associated ActionState. Note that the DrinkAction belongs to a
// *separate entity* from the owner of the Thirst component!
fn drink_action_system(
mut thirsts: Query<&mut Thirst>,
// We grab the Parent here, because individual Actions are parented to the
// entity "doing" the action.
//
// ActionState is an enum that described the specific run-state the action
// is in. You can think of Actions as state machines. They get requested,
// they can be cancelled, they can run to completion, etc. Cancellations
// usually happen because the target action changed (due to a different
// Consideration winning). But you can also cancel the actions yourself by
// setting the state in the Action system.
mut query: Query<(&Parent, &DrinkAction, &mut ActionState)>,
) {
for (Parent(actor), _drink_action, mut state) in query.iter_mut() {
// Use the drink_action's actor to look up the corresponding Thirst.
if let Ok(mut thirst) = thirsts.get_mut(*actor) {
match *state {
ActionState::Requested => {
thirst.thirst = 10.0;
println!("drank some water");
*state = ActionState::Success;
}
ActionState::Cancelled => {
*state = ActionState::Failure;
}
_ => {}
}
}
}
}
// Then, we have something called "Considerations". These are special
// components that run in the background, calculating a "Utility" value, which
// is what Big Brain will use to pick which actions to execute.
//
// Additionally, though, we pull in an evaluator and define a weight. Which is
// just mathy stuff you can tweak to get the behavior you want. More on this
// in the docs (later), but for now, just put them in there and trust the
// system. :)
#[derive(Debug, Consideration)]
pub struct ThirstConsideration {
#[consideration(default)]
pub evaluator: PowerEvaluator,
#[consideration(param)]
pub weight: f32,
}
// Look familiar? Similar dance to Actions here.
pub fn thirst_consideration_system(
thirsts: Query<&Thirst>,
// Same dance with the Parent here, but now we've added a Utility!
mut query: Query<(&Parent, &ThirstConsideration, &mut Utility)>,
) {
for (Parent(actor), conser, mut util) in query.iter_mut() {
if let Ok(thirst) = thirsts.get(*actor) {
// This is really what the job of a Consideration is. To calculate
// a generic Utility value that the Big Brain engine will compare
// against others, over time, and use to make decisions. This is
// generally "the higher the better", and "first across the finish
// line", but that's all configurable! You can customize that to
// your heart's extent using Measures and Pickers.
//
// A note: You don't actually *need* evaluators/weights. You can
// literally just use linear values here and set thresholds
// accordingly. The evaluator is just there to give the value a
// bit of a curve.
*util = Utility {
value: conser.evaluator.evaluate(thirst.thirst),
weight: conser.weight,
};
}
}
}
// Now that we hav eall that defined, it's time to add a Thinker to an entity!
// The Thinker is the actual "brain" behind all the AI. Every entity you want
// to have AI behavior should have one *or more* Thinkers attached to it.
// Thinkers are configured using RON right now, with a DSL that makes it easy
// to define, in data, the actual behavior you want.
pub fn init_entities(mut cmd: Commands) {
let actor = cmd.spawn().insert(Thirst::new(80.0, 2.0)).id();
// Here's a very simple one that only has one consideration and one
// associated action. But you can have more of them, and even nest them by
// using more Thinkers (which are actually themselves Actions). See
// basic.ron in examples/ for a more involved Thinker definition.
//
// Ultimately, these Thinkers are meant to be usable by non-programmers:
// You, the developer, create Actions and Considerations, and someone else
// is then able to put them all together like LEGOs into all sorts of
// intricate logic.
Thinker::load_from_str(
r#"
(
picker: {"FirstToScore": (threshold: 80.0)},
choices: [(
consider: [{"ThirstConsideration": (weight: 2.0)}],
then: {"DrinkAction": ()},
)],
)
"#,
)
.build(actor, &mut cmd);
}
fn main() {
// Once all that's done, we just add our systems and off we go!
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(init_entities.system())
.add_system(thirst_system.system())
.add_system(thirst_consideration_system.system())
.add_system(drink_action_system.system())
// Don't forget the Thinker system itself! This is the heart of it all!
.add_system(big_brain::thinker_system.system())
.run();
}