Skip to content

Commit a684cf5

Browse files
StarArawnStarArawn
authored andcommitted
Added a more advanced AI that collects potions and gets bored.
1 parent f82dc8b commit a684cf5

File tree

2 files changed

+275
-1
lines changed

2 files changed

+275
-1
lines changed

examples/basic_ai.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ fn drink_potion_action_system(
9292
mut query: Query<(&Actor, &mut ActionState), With<DrinkPotion>>,
9393
) {
9494
for (Actor(actor), mut state) in query.iter_mut() {
95-
// Use the drink_action's actor to look up the corresponding Thirst.
9695
if let Ok(mut health) = health.get_mut(*actor) {
9796
match *state {
9897
ActionState::Requested => {

examples/visual_ai.rs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
use bevy::{prelude::*};
2+
use big_brain::prelude::*;
3+
use rand::{thread_rng, Rng};
4+
5+
const HALF_WIDTH: f32 = (1270.0 / 2.0) - 32.0;
6+
const HALF_HEIGHT: f32 = (720.0 / 2.0) - 32.0;
7+
8+
struct Moving;
9+
10+
#[derive(Default)]
11+
struct Boredom {
12+
value: f32,
13+
}
14+
15+
fn tick_boredom(
16+
time: Res<Time>,
17+
mut query: Query<&mut Boredom, Without<Moving>>,
18+
) {
19+
for mut boredom in query.iter_mut() {
20+
boredom.value += 0.5 * time.delta_seconds();
21+
if boredom.value > 1.0 {
22+
boredom.value = 1.0;
23+
}
24+
}
25+
}
26+
27+
#[derive(Debug, Clone)]
28+
struct Idle;
29+
30+
impl Idle {
31+
fn build() -> IdleBuilder {
32+
IdleBuilder
33+
}
34+
}
35+
36+
#[derive(Debug, Clone)]
37+
struct IdleBuilder;
38+
39+
impl ActionBuilder for IdleBuilder {
40+
fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
41+
cmd.entity(action).insert(Idle);
42+
}
43+
}
44+
45+
fn idle_system(mut query: Query<&mut ActionState, With<Idle>>) {
46+
for mut state in query.iter_mut() {
47+
match *state {
48+
ActionState::Requested => {
49+
*state = ActionState::Executing;
50+
}
51+
ActionState::Cancelled => {
52+
*state = ActionState::Success;
53+
}
54+
ActionState::Executing => {}
55+
_ => {}
56+
}
57+
}
58+
}
59+
60+
#[derive(Debug, Clone)]
61+
struct IsBored;
62+
63+
impl IsBored {
64+
fn build() -> IsBoredBuilder {
65+
IsBoredBuilder
66+
}
67+
}
68+
69+
#[derive(Debug, Clone)]
70+
struct IsBoredBuilder;
71+
72+
impl ScorerBuilder for IsBoredBuilder {
73+
fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
74+
cmd.entity(scorer).insert(IsBored);
75+
}
76+
}
77+
78+
fn is_bored_system(
79+
boredom_query: Query<&Boredom>,
80+
mut query: Query<(&Actor, &mut Score), With<IsBored>>,
81+
) {
82+
for (Actor(actor), mut score) in query.iter_mut() {
83+
if let Ok(boredom) = boredom_query.get(*actor) {
84+
score.set(boredom.value);
85+
}
86+
}
87+
}
88+
#[derive(Default)]
89+
struct Target {
90+
value: Vec2,
91+
potion: Option<Entity>,
92+
}
93+
94+
#[derive(Debug, Clone)]
95+
struct Move;
96+
97+
impl Move {
98+
fn build() -> MoveBuilder {
99+
MoveBuilder
100+
}
101+
}
102+
103+
#[derive(Debug, Clone)]
104+
struct MoveBuilder;
105+
106+
impl ActionBuilder for MoveBuilder {
107+
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
108+
cmd.entity(actor).insert(Moving);
109+
cmd.entity(action).insert(Move);
110+
}
111+
}
112+
113+
fn move_system(
114+
mut commands: Commands,
115+
time: Res<Time>,
116+
mut target_query: Query<&mut Target>,
117+
mut boredom_query: Query<&mut Boredom>,
118+
mut transform_query: Query<&mut Transform>,
119+
mut query: Query<(&Actor, &mut ActionState), With<Move>>
120+
) {
121+
for (actor, mut state) in query.iter_mut() {
122+
match *state {
123+
ActionState::Requested => {
124+
if let Ok(mut target) = target_query.get_mut(actor.0) {
125+
let mut random = thread_rng();
126+
let random_target = Vec2::new(
127+
random.gen_range(-HALF_WIDTH..HALF_WIDTH),
128+
random.gen_range(-HALF_HEIGHT..HALF_HEIGHT),
129+
);
130+
if target.value == Vec2::ZERO {
131+
target.value = random_target;
132+
}
133+
}
134+
135+
*state = ActionState::Executing;
136+
}
137+
ActionState::Cancelled => {
138+
*state = ActionState::Success;
139+
}
140+
ActionState::Executing => {
141+
if let Ok(mut target) = target_query.get_mut(actor.0) {
142+
if let Ok(mut transform) = transform_query.get_mut(actor.0) {
143+
let mut position = transform.translation.truncate();
144+
let direction = (target.value - position).normalize();
145+
position += direction * 100.0 * time.delta_seconds();
146+
transform.translation = position.extend(transform.translation.z);
147+
148+
let distance = position.distance_squared(target.value);
149+
if distance < 1.0 {
150+
if let Ok(mut boredom) = boredom_query.get_mut(actor.0) {
151+
boredom.value = 0.0;
152+
}
153+
commands.entity(actor.0).remove::<Moving>();
154+
if let Some(potion_entity) = target.potion {
155+
commands.entity(potion_entity).despawn();
156+
}
157+
target.value = Vec2::ZERO;
158+
target.potion = None;
159+
*state = ActionState::Success;
160+
}
161+
}
162+
}
163+
}
164+
_ => {}
165+
}
166+
}
167+
}
168+
169+
struct Potion;
170+
171+
#[derive(Debug, Clone)]
172+
struct SeesPotion;
173+
174+
impl SeesPotion {
175+
fn build() -> SeesPotionBuilder {
176+
SeesPotionBuilder
177+
}
178+
}
179+
180+
#[derive(Debug, Clone)]
181+
struct SeesPotionBuilder;
182+
183+
impl ScorerBuilder for SeesPotionBuilder {
184+
fn build(&self, cmd: &mut Commands, scorer: Entity, _actor: Entity) {
185+
cmd.entity(scorer).insert(SeesPotion);
186+
}
187+
}
188+
189+
fn sees_potion_system(
190+
mut target_query: Query<&mut Target>,
191+
transform_query: Query<&Transform>,
192+
potion_query: Query<(Entity, &Transform), With<Potion>>,
193+
mut query: Query<(&Actor, &mut Score), With<SeesPotion>>,
194+
) {
195+
for (Actor(actor), mut score) in query.iter_mut() {
196+
if let Ok(transform) = transform_query.get(*actor) {
197+
for (entity, potion_transform) in potion_query.iter() {
198+
let distance = transform.translation.distance_squared(potion_transform.translation);
199+
if distance < 4096.0 {
200+
if let Ok(mut target) = target_query.get_mut(*actor) {
201+
target.value = potion_transform.translation.truncate();
202+
target.potion = Some(entity);
203+
204+
let new_score = (4096.0 / distance).min(1.0);
205+
206+
score.set(new_score);
207+
return;
208+
}
209+
}
210+
}
211+
score.set(0.0);
212+
}
213+
}
214+
}
215+
216+
217+
fn startup(
218+
mut commands: Commands,
219+
asset_server: Res<AssetServer>,
220+
mut materials: ResMut<Assets<ColorMaterial>>,
221+
) {
222+
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
223+
224+
let player_texture = asset_server.load("textures/player_sprite.png");
225+
let player_material = materials.add(player_texture.into());
226+
227+
commands
228+
.spawn_bundle(SpriteBundle {
229+
material: player_material,
230+
..SpriteBundle::default()
231+
})
232+
.insert(Boredom::default())
233+
.insert(Target::default())
234+
.insert(
235+
Thinker::build()
236+
.picker(FirstToScore::new(0.2))
237+
.when(IsBored::build(), Move::build())
238+
.when(SeesPotion::build(), Move::build())
239+
.otherwise(Idle::build())
240+
);
241+
242+
let potion_texture = asset_server.load("textures/health_potion.png");
243+
let potion_material = materials.add(potion_texture.into());
244+
245+
let mut random = thread_rng();
246+
247+
for _ in 0..500 {
248+
let x = random.gen_range(-HALF_WIDTH..HALF_WIDTH);
249+
let y = random.gen_range(-HALF_HEIGHT..HALF_HEIGHT);
250+
commands.spawn_bundle(SpriteBundle {
251+
material: potion_material.clone(),
252+
transform: Transform::from_xyz(x, y, 0.0),
253+
..SpriteBundle::default()
254+
})
255+
.insert(Potion);
256+
}
257+
}
258+
fn main() {
259+
App::build()
260+
.insert_resource(WindowDescriptor {
261+
width: 1270.0,
262+
height: 720.0,
263+
title: String::from("basic_ai"),
264+
..Default::default()
265+
})
266+
.add_plugins(DefaultPlugins)
267+
.add_plugin(BigBrainPlugin)
268+
.add_startup_system(startup.system())
269+
.add_system(tick_boredom.system())
270+
.add_system(is_bored_system.system())
271+
.add_system(move_system.system())
272+
.add_system(idle_system.system())
273+
.add_system(sees_potion_system.system())
274+
.run();
275+
}

0 commit comments

Comments
 (0)