Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ bevy_window = "0.13"
# Ryot dependencies
ryot = { path = "./crates/ryot", version = "0.2" }
ryot_assets = { path = "./crates/ryot_assets", version = "0.2" }
ryot_brain = { path = "./crates/ryot_brain", version = "0.2" }
ryot_core = { path = "./crates/ryot_core", version = "0.2" }
ryot_derive = { path = "./crates/ryot_derive", version = "0.2" }
ryot_internal = { path = "./crates/ryot_internal", version = "0.2" }
Expand Down
1 change: 1 addition & 0 deletions crates/ryot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ default = [
]

bevy = ["ryot_internal/bevy"]
brain = ["ryot_internal/brain"]

tibia = ["ryot_internal/ryot_tibia"]

Expand Down
40 changes: 24 additions & 16 deletions crates/ryot/src/plugins/game.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use bevy_app::{App, Last, Plugin, PostUpdate, Update};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use ryot_internal::prelude::*;
use std::marker::PhantomData;

pub struct GamePlugin;

Expand All @@ -26,21 +25,30 @@ impl Plugin for ElevationPlugin {
}
}

/// `NavigablePlugin` provides the necessary system and resource setup for managing `Navigable`
/// within the game world. It ensures that the flag cache is up-to-date and reflects the latest
/// flag state of the whole tile, per position. This avoids the need to iterate over each entity
/// within a tile to check its properties.
pub struct NavigablePlugin<N: Navigable + Copy + Default + Component>(PhantomData<N>);

impl<N: Navigable + Copy + Default + Component> Default for NavigablePlugin<N> {
fn default() -> Self {
Self(PhantomData)
}
/// `NavigableApp` inserts a `Navigable` to the game world, providing the necessary system and
/// resource setup for managing it. It ensures that the flag cache is up-to-date and reflects the
/// latest flag state of the whole tile, per position. This avoids the need to iterate over each
/// entity within a tile to check its properties.
pub trait NavigableApp {
fn add_navigable<N: Navigable + Copy + Default + Component>(&mut self) -> &mut Self;
}

impl<N: Navigable + Copy + Default + Component> Plugin for NavigablePlugin<N> {
fn build(&self, app: &mut App) {
app.init_resource_once::<Cache<TilePosition, N>>()
.add_systems(PostUpdate, update_tile_flag_cache::<N>);
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::<PreviousPosition>
.pipe(build_new_flags_for_map::<N>)
.pipe(update_tile_flag_cache::<N>)
.in_set(CacheSystems::UpdateCache),
)
}
}
2 changes: 1 addition & 1 deletion crates/ryot/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod prelude {
content_plugin,
plugins::{
content::{BaseContentPlugin, MetaContentPlugin, VisualContentPlugin},
game::{ElevationPlugin, GamePlugin, NavigablePlugin},
game::{ElevationPlugin, GamePlugin, NavigableApp},
sprites::{RyotDrawingPlugin, RyotSpritePlugin},
},
};
Expand Down
34 changes: 34 additions & 0 deletions crates/ryot_brain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "ryot_brain"
version = "0.2.2"
edition = "2021"
authors = [
"Lucas Grossi <[email protected]>",
"Luan Santos <[email protected]>",
]
license = "AGPL-3.0-only"
description = "Part of the Ryot game engine ecosystem, offering advanced NPC behaviors using big_brain and bevy."
homepage = "https://github.com/ry-ot/Ryot"
repository = "https://github.com/ry-ot/Ryot"
documentation = "https://docs.rs/ryot/"
keywords = ["bevy", "ai", "ryot"]
categories = ["game-development", "game-engines", "games"]

[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true

[dependencies]
bevy.workspace = true
big-brain = { git = "https://github.com/zkat/big-brain.git", branch = "main" }

ryot_core.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"
130 changes: 130 additions & 0 deletions crates/ryot_brain/examples/pathfinding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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, ThinkerBundle, WalkTo,
};
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_event::<WalkTo>()
.add_cooldown::<Thinker>()
.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, moves_randomly)
.in_set(BigBrainSet::Actions)
.after(CacheSystems::UpdateCache),
)
.add_systems(
Update,
(
walk_to.pipe(initiate_walk),
shuffle_target_positions_every_x_seconds,
),
)
.add_plugins((
FrameTimeDiagnosticsPlugin,
EntityCountDiagnosticsPlugin,
SystemInformationDiagnosticsPlugin,
LogDiagnosticsPlugin::default(),
))
.run();
}

fn walk_to(
mut walk_reader: EventReader<WalkTo>,
mut positions: Query<&mut TilePosition>,
) -> Vec<(Entity, OrdinalDirection)> {
walk_reader
.read()
.filter_map(|WalkTo(entity, next_pos)| {
Some((
*entity,
positions.get_mut(*entity).ok()?.direction_to(*next_pos),
))
})
.collect::<Vec<_>>()
}

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 _ in 0..=1 {
commands.spawn((
TilePosition::generate(
rand::random::<i32>() % 100 + 1,
rand::random::<i32>() % 100 + 1,
0,
),
ThinkerBundle::new(
Thinker::build()
.label("ChaserThinker")
.picker(FirstToScore::new(0.6))
.find_path::<Target>()
.follows_path()
.otherwise(MovesRandomly),
Cooldown::from_seconds(0.5),
),
));
}
}

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,
);
}
}
}
Loading