Skip to content

Commit

Permalink
feat: add example to ryot_brain
Browse files Browse the repository at this point in the history
  • Loading branch information
lgrossi committed May 8, 2024
1 parent f4181ff commit 518e4cd
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 34 deletions.
9 changes: 8 additions & 1 deletion crates/ryot/src/plugins/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ pub trait NavigableApp {
impl NavigableApp for App {
fn add_navigable<N: Navigable + Copy + Default + Component>(&mut self) -> &mut Self {
self.init_resource_once::<Cache<TilePosition, N>>()
.add_systems(
PreUpdate,
collect_updatable_positions::<TilePosition>
.pipe(build_new_flags_for_map::<N>)
.pipe(update_tile_flag_cache::<N>)
.in_set(CacheSystems::UpdateCache),
)
.add_systems(
PostUpdate,
collect_updatable_positions
collect_updatable_positions::<PreviousPosition>
.pipe(build_new_flags_for_map::<N>)
.pipe(update_tile_flag_cache::<N>)
.in_set(CacheSystems::UpdateCache),
Expand Down
5 changes: 4 additions & 1 deletion crates/ryot_brain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ bevy.workspace = true
big-brain = { git = "https://github.com/zkat/big-brain.git", branch = "main" }

ryot_core.workspace = true
ryot_tiled.workspace = true
ryot_tiled = { workspace = true, features = ["bevy", "pathfinding"] }
ryot_pathfinder.workspace = true
ryot_utils.workspace = true

derive_more.workspace = true
rand = "0.8.5"

[[example]]
name = "pathfinding"
107 changes: 107 additions & 0 deletions crates/ryot_brain/examples/pathfinding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use bevy::app::App;
use bevy::diagnostic::*;
use bevy::log::LogPlugin;
use bevy::prelude::*;
use bevy::MinimalPlugins;
use big_brain::pickers::FirstToScore;
use big_brain::prelude::Thinker;
use big_brain::{BigBrainPlugin, BigBrainSet};
use ryot_brain::prelude::{
find_closest_target, find_path_scorer, follow_path, follow_path_scorer, moves_randomly,
MovesRandomly, PathFindingThinker, PathFollowingThinker,
};
use ryot_core::game::Point;
use ryot_core::prelude::Flags;
use ryot_pathfinder::pathable::PathableApp;
use ryot_tiled::prelude::{OrdinalDirection, TilePosition};
use ryot_utils::prelude::*;

#[derive(Component, Copy, Debug, Clone)]
pub struct Target;

fn main() {
let mut app = App::new();

app.add_plugins(MinimalPlugins)
.add_plugins(LogPlugin {
// Use `RUST_LOG=big_brain=trace,sequence=trace cargo run --example sequence --features=trace` to see extra tracing output.
filter: "ryot_brain=debug".to_string(),
..default()
})
.add_pathable::<TilePosition, Flags>()
.add_systems(Startup, (spawn_basic))
.add_plugins(BigBrainPlugin::new(PreUpdate))
.add_systems(
PreUpdate,
(find_path_scorer::<Target>, follow_path_scorer).in_set(BigBrainSet::Scorers),
)
.add_systems(
PreUpdate,
(
find_closest_target::<Target>,
follow_path.pipe(initiate_walk),
moves_randomly.pipe(initiate_walk),
)
.in_set(BigBrainSet::Actions)
.after(CacheSystems::UpdateCache),
)
.add_systems(Update, shuffle_target_positions_every_x_seconds)
.add_plugins((
FrameTimeDiagnosticsPlugin,
EntityCountDiagnosticsPlugin,
SystemInformationDiagnosticsPlugin,
LogDiagnosticsPlugin::default(),
))
.run();
}

fn initiate_walk(
In(walks): In<Vec<(Entity, OrdinalDirection)>>,
mut positions: Query<&mut TilePosition>,
) {
for (entity, direction) in walks {
if let Ok(mut position) = positions.get_mut(entity) {
*position = TilePosition(position.0 + IVec2::from(direction).extend(0))
}
}
}

fn spawn_basic(mut commands: Commands) {
commands.spawn((
TilePosition::generate(
rand::random::<i32>() % 100 + 1,
rand::random::<i32>() % 100 + 1,
0,
),
Target,
));
for i in 0..=400_000 {
commands.spawn((
TilePosition::generate(
rand::random::<i32>() % 100 + 1,
rand::random::<i32>() % 100 + 1,
0,
),
Thinker::build()
.label("ChaserThinker")
.picker(FirstToScore::new(0.6))
.find_path::<Target>()
.follows_path_with_fallback(MovesRandomly),
));
}
}

fn shuffle_target_positions_every_x_seconds(
time: Res<Time>,
mut query: Query<&mut TilePosition, With<Target>>,
) {
for mut position in &mut query.iter_mut() {
if time.elapsed_seconds() % 5. < 0.0001 {
*position = TilePosition::generate(
rand::random::<i32>() % 100 + 1,
rand::random::<i32>() % 100 + 1,
0,
);
}
}
}
35 changes: 27 additions & 8 deletions crates/ryot_brain/src/find_closest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub trait PathFindingThinker {
impl PathFindingThinker for ThinkerBuilder {
fn find_path<T: Thinking>(self) -> Self {
self.when(
FindTargetScorer::<T>::default(),
FindTargetScorer::<T>::with_duration_in_seconds(1.),
Steps::build()
.label("FindClosestTarget")
.step(FindClosestTarget::<T>::default()),
Expand All @@ -35,7 +35,16 @@ pub struct FindTargetScorer<T: Thinking>(Timer, PhantomData<T>);

impl<T: Thinking> Default for FindTargetScorer<T> {
fn default() -> Self {
Self(Timer::from_seconds(0.5, TimerMode::Repeating), PhantomData)
Self::with_duration_in_seconds(0.5)
}
}

impl<T: Thinking> FindTargetScorer<T> {
fn with_duration_in_seconds(duration: f32) -> Self {
Self(
Timer::from_seconds(duration, TimerMode::Repeating),
PhantomData,
)
}
}

Expand All @@ -50,10 +59,13 @@ impl<T: Thinking> Default for FindClosestTarget<T> {

pub fn find_path_scorer<T: Thinking + Component>(
time: Res<Time>,
targets: Query<&Target>,
mut query: Query<(&Actor, &mut Score, &mut FindTargetScorer<T>)>,
) {
for (Actor(_actor), mut score, mut scorer) in &mut query {
if scorer.0.tick(time.delta()).just_finished() {
let tick = if targets.get(*_actor).is_err() { 3 } else { 1 } * time.delta();

if scorer.0.tick(tick).just_finished() {
score.set(0.7);
} else {
score.set(0.);
Expand All @@ -68,7 +80,7 @@ pub fn find_closest_target<T: Component + Thinking>(
q_target_positions: Query<(Entity, &TilePosition), With<T>>,
mut action_query: Query<(&Actor, &mut ActionState, &FindClosestTarget<T>, &ActionSpan)>,
) {
for (actor, mut action_state, _, span) in &mut action_query {
for (Actor(actor), mut action_state, _, span) in &mut action_query {
let _guard = span.span().enter();

match *action_state {
Expand All @@ -77,12 +89,12 @@ pub fn find_closest_target<T: Component + Thinking>(
*action_state = ActionState::Executing;
}
ActionState::Executing => {
let actor_position = positions.get(actor.0).expect("actor has no position");
let actor_position = positions.get(*actor).expect("actor has no position");
let closest_target = get_closest_target(actor_position, &q_target_positions);

let Some((target, closest_target)) = closest_target else {
debug!("No closest target found, failing.");
*action_state = ActionState::Failure;
*action_state = ActionState::Cancelled;
continue;
};

Expand All @@ -96,7 +108,13 @@ pub fn find_closest_target<T: Component + Thinking>(

let Some(target_pos) = target_pos else {
debug!("Unreachable path, failing.");
*action_state = ActionState::Failure;
*action_state = ActionState::Cancelled;
continue;
};

if closest_target.distance(actor_position) > 300. {
debug!("Target is too far away, failing.");
*action_state = ActionState::Cancelled;
continue;
};

Expand All @@ -106,7 +124,7 @@ pub fn find_closest_target<T: Component + Thinking>(
continue;
};

cmd.entity(actor.0).insert((
cmd.entity(*actor).insert((
Target(target),
TiledPathFindingQuery::new(target_pos)
.with_success_range((0., 0.))
Expand All @@ -117,6 +135,7 @@ pub fn find_closest_target<T: Component + Thinking>(
*action_state = ActionState::Success;
}
ActionState::Cancelled => {
cmd.entity(*actor).remove::<Target>();
*action_state = ActionState::Failure;
}
_ => {}
Expand Down
8 changes: 6 additions & 2 deletions crates/ryot_brain/src/follow_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub fn follow_path_scorer(
}

pub fn follow_path<T: From<OrdinalDirection>>(
mut cmd: Commands,
positions: Query<&TilePosition>,
mut paths: Query<&mut TiledPath>,
flags_cache: Res<Cache<TilePosition, Flags>>,
Expand All @@ -91,12 +92,14 @@ pub fn follow_path<T: From<OrdinalDirection>>(
let actor_position = positions.get(*actor).expect("actor has no position");
let Ok(mut path) = paths.get_mut(*actor) else {
debug!("Actor has no path.");
cmd.entity(*actor).remove::<Target>();
*action_state = ActionState::Success;
continue;
};

let Some(next_pos) = path.first().copied() else {
debug!("Actor path is empty.");
cmd.entity(*actor).remove::<Target>();
*action_state = ActionState::Success;
continue;
};
Expand All @@ -105,22 +108,23 @@ pub fn follow_path<T: From<OrdinalDirection>>(

if next_pos == *actor_position {
if path.is_empty() {
*action_state = ActionState::Failure;
*action_state = ActionState::Cancelled;
}

continue;
}

if !next_pos.is_navigable(&flags_cache) {
debug!("Next position is not valid, failing.");
*action_state = ActionState::Failure;
*action_state = ActionState::Cancelled;
} else {
debug!("Moving to {:?}", next_pos);
result.push((*actor, T::from(actor_position.direction_to(next_pos))));
*action_state = ActionState::Failure;
}
}
ActionState::Cancelled => {
cmd.entity(*actor).remove::<Target>();
*action_state = ActionState::Failure;
}
_ => {}
Expand Down
7 changes: 5 additions & 2 deletions crates/ryot_ray_casting/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::prelude::*;
use crate::systems::{remove_stale_requests, remove_stale_results};
use bevy_app::{App, PostUpdate, Update};
use bevy_app::{App, PostUpdate, PreUpdate, Update};
use bevy_ecs::prelude::*;
use ryot_core::prelude::Navigable;
use ryot_utils::prelude::*;
Expand All @@ -27,10 +27,13 @@ impl RayCastingApp for App {
) -> &mut Self {
self.init_resource_once::<Cache<P, N>>()
.init_resource::<SimpleCache<RadialArea<P>, Vec<Vec<P>>>>()
.add_systems(
PreUpdate,
update_intersection_cache::<Marker, P>.in_set(CacheSystems::UpdateCache),
)
.add_systems(
Update,
(
update_intersection_cache::<Marker, P>.in_set(CacheSystems::UpdateCache),
process_ray_casting::<Marker, P, N>
.in_set(RayCastingSystems::Process)
.after(CacheSystems::UpdateCache),
Expand Down
34 changes: 14 additions & 20 deletions crates/ryot_tiled/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use bevy_utils::HashMap;
use ryot_core::prelude::*;
use ryot_utils::prelude::*;

pub fn collect_updatable_positions(
pub fn collect_updatable_positions<P: Into<TilePosition> + Copy + Component>(
map_tiles: Res<MapTiles<Entity>>,
q_updated_entities: Query<
(Option<&PreviousPosition>, &TilePosition),
&P,
Or<(
Changed<ContentId>,
Changed<Visibility>,
Expand All @@ -18,27 +18,21 @@ pub fn collect_updatable_positions(
) -> HashMap<TilePosition, (Vec<Entity>, bool)> {
let mut pos_map: HashMap<TilePosition, (Vec<Entity>, bool)> = HashMap::new();

for (previous_pos, new_pos) in q_updated_entities.iter() {
match previous_pos {
Some(PreviousPosition(previous_pos)) => vec![*new_pos, *previous_pos],
None => vec![*new_pos],
for pos in q_updated_entities.iter() {
let tile_pos: TilePosition = (*pos).into();

if pos_map.contains_key(&tile_pos) {
continue;
}
.iter()
.enumerate()
.for_each(|(i, pos)| {
if pos_map.contains_key(pos) {
return;
}

let Some(tile) = map_tiles.get(pos) else {
return;
};
let Some(tile) = map_tiles.get(&tile_pos) else {
continue;
};

pos_map.insert(
*pos,
(tile.into_iter().map(|(_, entity)| entity).collect(), i == 0),
);
});
pos_map.insert(
tile_pos,
(tile.into_iter().map(|(_, entity)| entity).collect(), true),
);
}

pos_map
Expand Down
6 changes: 6 additions & 0 deletions crates/ryot_tiled/src/map/position/previous.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ use bevy_reflect::prelude::*;
#[cfg_attr(feature = "bevy", derive(Component, Reflect, Deref, DerefMut))]
pub struct PreviousPosition(pub TilePosition);

impl From<PreviousPosition> for TilePosition {
fn from(pos: PreviousPosition) -> Self {
pos.0
}
}

/// System to track changes in the position of entities. Needs to be run after the position
/// component has been changed, so it can track the previous position.
/// Better to run it during the [`Last`](Last) or [`PostUpdate`](PostUpdate) stages.
Expand Down

0 comments on commit 518e4cd

Please sign in to comment.