diff --git a/crates/beet_core/src/steer/forage_behavior.rs b/crates/beet_core/src/steer/forage_behavior.rs index d6372a91..46788f6b 100644 --- a/crates/beet_core/src/steer/forage_behavior.rs +++ b/crates/beet_core/src/steer/forage_behavior.rs @@ -24,7 +24,7 @@ pub fn forage_behavior(world: &mut World) -> Entity { .spawn(( Name::new("Seek"), Score::default(), - ScoreSteerTarget::new(awareness_radius), + SteerTargetScoreProvider::new(awareness_radius), SequenceSelector::default(), )) .with_children(|parent| { diff --git a/crates/beet_core/src/steer/steer_actions/mod.rs b/crates/beet_core/src/steer/steer_actions/mod.rs index 3e8e698a..210d9198 100644 --- a/crates/beet_core/src/steer/steer_actions/mod.rs +++ b/crates/beet_core/src/steer/steer_actions/mod.rs @@ -16,9 +16,9 @@ pub use self::end_on_arrive::*; pub mod find_steer_target; #[allow(unused_imports)] pub use self::find_steer_target::*; -pub mod score_steer_target; +pub mod steer_target_score_provider; #[allow(unused_imports)] -pub use self::score_steer_target::*; +pub use self::steer_target_score_provider::*; pub mod seek; #[allow(unused_imports)] pub use self::seek::*; diff --git a/crates/beet_core/src/steer/steer_actions/score_steer_target.rs b/crates/beet_core/src/steer/steer_actions/score_steer_target.rs deleted file mode 100644 index 5e82b9e3..00000000 --- a/crates/beet_core/src/steer/steer_actions/score_steer_target.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::prelude::*; -use beet_ecs::prelude::*; -use bevy::prelude::*; - -#[derive(Debug, Clone, PartialEq, Component, Action, Reflect)] -#[reflect(Default, Component, ActionMeta)] -#[category(ActionCategory::Behavior)] -#[systems(score_steer_target.in_set(PreTickSet))] -/// Adjusts the [`Score`] based on distance to the [`SteerTarget`] -pub struct ScoreSteerTarget { - pub radius: f32, -} - -impl Default for ScoreSteerTarget { - fn default() -> Self { Self { radius: 0.5 } } -} - -impl ScoreSteerTarget { - pub fn new(radius: f32) -> Self { Self { radius } } -} - -fn score_steer_target( - transforms: Query<&Transform>, - agents: Query<(&Transform, &SteerTarget)>, - mut query: Query<(&TargetAgent, &ScoreSteerTarget, &mut Score)>, -) { - for (agent, scorer, mut score) in query.iter_mut() { - if let Ok((transform, target)) = agents.get(**agent) { - if let Ok(target) = target.position(&transforms) { - if Vec3::distance(transform.translation, target) - <= scorer.radius - { - *score = Score::Pass; - continue; - } - } - } - *score = Score::Fail; - } -} -// Or<( -// Changed, -// Changed, -// Changed, -// )>, diff --git a/crates/beet_core/src/steer/steer_actions/steer_target_score_provider.rs b/crates/beet_core/src/steer/steer_actions/steer_target_score_provider.rs new file mode 100644 index 00000000..51ecf50a --- /dev/null +++ b/crates/beet_core/src/steer/steer_actions/steer_target_score_provider.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; +use beet_ecs::prelude::*; +use bevy::prelude::*; + +#[derive(Debug, Clone, PartialEq, Component, Action, Reflect)] +#[reflect(Default, Component, ActionMeta)] +#[category(ActionCategory::Behavior)] +#[observers(provide_score)] +/// Provides a [`ScoreValue`] based on distance to the [`SteerTarget`] +pub struct SteerTargetScoreProvider { + pub radius: f32, +} + +impl Default for SteerTargetScoreProvider { + fn default() -> Self { Self { radius: 0.5 } } +} + +impl SteerTargetScoreProvider { + pub fn new(radius: f32) -> Self { Self { radius } } +} + +fn provide_score( + trigger: Trigger, + mut commands: Commands, + transforms: Query<&Transform>, + agents: Query<(&Transform, &SteerTarget)>, + query: Query<(&SteerTargetScoreProvider, &TargetAgent, &Parent)>, +) { + let (action, agent, parent) = query + .get(trigger.entity()) + .expect(expect_action::ACTION_QUERY_MISSING); + + let score = if let Ok((transform, target)) = agents.get(**agent) + && let Ok(target) = target.position(&transforms) + && Vec3::distance(transform.translation, target) <= action.radius + { + 1. + } else { + 0. + }; + commands.trigger_targets( + OnChildScore::new(trigger.entity(), score), + parent.get(), + ); +} diff --git a/crates/beet_core/src/steer/steer_plugin.rs b/crates/beet_core/src/steer/steer_plugin.rs index e92d675c..32b1a5f8 100644 --- a/crates/beet_core/src/steer/steer_plugin.rs +++ b/crates/beet_core/src/steer/steer_plugin.rs @@ -18,7 +18,7 @@ impl Plugin for SteerPlugin { Cohere, EndOnArrive, FindSteerTarget, - ScoreSteerTarget, + SteerTargetScoreProvider, DespawnSteerTarget, RunOnSteerTargetInsert, RunOnSteerTargetRemove, diff --git a/crates/beet_ecs/examples/test.rs b/crates/beet_ecs/examples/test.rs new file mode 100644 index 00000000..20085f5b --- /dev/null +++ b/crates/beet_ecs/examples/test.rs @@ -0,0 +1,32 @@ +use bevy::prelude::*; + + + +#[derive(Component)] +pub struct Foo; + +#[derive(Event)] +pub struct Event1; + +#[derive(Event)] +pub struct Event2; + + + + +fn main() { + let mut world = World::new(); + world.observe(|_trigger: Trigger, query: Query<&Foo>| { + println!("Event2 triggered, num components: {}", query.iter().len()); + }); + world.observe(|_trigger: Trigger, mut commands: Commands| { + println!("Event1 triggered"); + // must spawn Foo before trigger + commands.spawn(Foo); + commands.trigger(Event2); + }); + world.flush(); + world.trigger(Event1); + world.flush(); + println!("Hello, world!"); +} diff --git a/crates/beet_ecs/src/actions/flow/mod.rs b/crates/beet_ecs/src/actions/flow/mod.rs index c8ad3590..eed9e241 100644 --- a/crates/beet_ecs/src/actions/flow/mod.rs +++ b/crates/beet_ecs/src/actions/flow/mod.rs @@ -1,6 +1,9 @@ pub mod score_flow; #[allow(unused_imports)] pub use self::score_flow::*; +pub mod score_provider; +#[allow(unused_imports)] +pub use self::score_provider::*; pub mod sequence_flow; #[allow(unused_imports)] pub use self::sequence_flow::*; diff --git a/crates/beet_ecs/src/actions/flow/score_flow.rs b/crates/beet_ecs/src/actions/flow/score_flow.rs index 1f01b9b0..438f949b 100644 --- a/crates/beet_ecs/src/actions/flow/score_flow.rs +++ b/crates/beet_ecs/src/actions/flow/score_flow.rs @@ -1,35 +1,55 @@ use crate::prelude::*; use bevy::prelude::*; +use bevy::utils::HashMap; use std::cmp::Ordering; -#[derive(Default, Component, Action, Reflect)] + +#[derive(Event)] +pub struct RequestScore; + +pub type OnChildScore = OnChildValue; + +#[derive(Default, Deref, DerefMut, Component, Action, Reflect)] #[reflect(Default, Component)] #[category(ActionCategory::ChildBehaviors)] -#[observers(on_start, passthrough_run_result)] -pub struct ScoreFlow; +#[observers(on_start, on_receive_score, passthrough_run_result)] +pub struct ScoreFlow(HashMap); fn on_start( trigger: Trigger, mut commands: Commands, - query: Query<&Children>, - scores: Query<&Score>, + mut query: Query<(&mut ScoreFlow, &Children)>, ) { - let children = query - .get(trigger.entity()) + let (mut score_flow, children) = query + .get_mut(trigger.entity()) .expect(child_expect::NO_CHILDREN); - if let Some(highest) = get_highest(scores, children) { - commands.trigger_targets(OnRun, highest); - } else { - commands.trigger_targets(OnRunResult::success(), trigger.entity()); - } + + score_flow.clear(); + + commands.trigger_targets( + RequestScore, + children.iter().map(|c| c.clone()).collect::>(), + ); } -fn get_highest(scores: Query<&Score>, children: &Children) -> Option { - children - .iter() - .filter_map(|&child| scores.get(child).ok().map(|score| (child, score))) - .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(Ordering::Equal)) - .map(|(entity, _)| entity) +fn on_receive_score( + trigger: Trigger, + mut commands: Commands, + mut query: Query<(&mut ScoreFlow, &Children)>, +) { + let (mut flow, children) = query + .get_mut(trigger.entity()) + .expect(child_expect::NO_CHILDREN); + + flow.insert(trigger.event().child(), *trigger.event().value()); + + if flow.len() == children.iter().len() { + let (highest, _) = flow + .iter() + .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(Ordering::Equal)) + .expect(child_expect::NO_CHILDREN); + commands.entity(*highest).trigger(OnRun); + } } #[cfg(test)] @@ -42,7 +62,9 @@ mod test { #[test] fn works() -> Result<()> { let mut app = App::new(); - app.add_plugins(ActionPlugin::<(ScoreFlow, EndOnRun)>::default()); + app.add_plugins( + ActionPlugin::<(ScoreFlow, ScoreProvider, EndOnRun)>::default(), + ); let world = app.world_mut(); world.observe(bubble_run_result); @@ -50,16 +72,16 @@ mod test { let on_run = observe_triggers::(world); world - .spawn((Name::new("root"), ScoreFlow)) + .spawn((Name::new("root"), ScoreFlow::default())) .with_children(|parent| { parent.spawn(( Name::new("child1"), - Score::neutral(), + ScoreProvider::NEUTRAL, EndOnRun::success(), )); parent.spawn(( Name::new("child2"), - Score::Pass, + ScoreProvider::PASS, EndOnRun::success(), )); }) diff --git a/crates/beet_ecs/src/actions/flow/score_provider.rs b/crates/beet_ecs/src/actions/flow/score_provider.rs new file mode 100644 index 00000000..35f8892a --- /dev/null +++ b/crates/beet_ecs/src/actions/flow/score_provider.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; +use bevy::prelude::*; + +pub type ScoreValue = f32; + +/// A constant score provider. +#[derive(Default, Deref, DerefMut, Component, Action, Reflect)] +#[reflect(Default, Component)] +#[category(ActionCategory::ChildBehaviors)] +#[observers(provide_score)] +pub struct ScoreProvider(pub ScoreValue); + +impl ScoreProvider { + pub const FAIL: ScoreProvider = ScoreProvider(0.0); + pub const PASS: ScoreProvider = ScoreProvider(1.0); + pub const NEUTRAL: ScoreProvider = ScoreProvider(0.5); + + pub fn new(score: ScoreValue) -> Self { Self(score) } +} + +fn provide_score( + trigger: Trigger, + mut commands: Commands, + query: Query<(&ScoreProvider, &Parent)>, +) { + let (score_provider, parent) = query + .get(trigger.entity()) + .expect(expect_action::ACTION_QUERY_MISSING); + + commands.trigger_targets( + OnChildScore::new(trigger.entity(), **score_provider), + parent.get(), + ); +} diff --git a/crates/beet_ecs/src/actions/flow/sequence_flow.rs b/crates/beet_ecs/src/actions/flow/sequence_flow.rs index 1f13008a..d3a86629 100644 --- a/crates/beet_ecs/src/actions/flow/sequence_flow.rs +++ b/crates/beet_ecs/src/actions/flow/sequence_flow.rs @@ -30,7 +30,7 @@ fn sequence_next( mut commands: Commands, query: Query<&Children>, ) { - if trigger.event().result() == RunResult::Failure { + if *trigger.event().value() == RunResult::Failure { commands.trigger_targets(OnRunResult::failure(), trigger.entity()); return; } diff --git a/crates/beet_ecs/src/actions/global/bubble_run_result.rs b/crates/beet_ecs/src/actions/global/bubble_run_result.rs index 5cda2eab..ad09250f 100644 --- a/crates/beet_ecs/src/actions/global/bubble_run_result.rs +++ b/crates/beet_ecs/src/actions/global/bubble_run_result.rs @@ -27,7 +27,7 @@ pub fn passthrough_run_result( mut commands: Commands, ) { commands.trigger_targets( - OnRunResult::new(trigger.event().result()), + OnRunResult::new(*trigger.event().value()), trigger.entity(), ); } diff --git a/crates/beet_ecs/src/events/mod.rs b/crates/beet_ecs/src/events/mod.rs index 7d2f530a..2843dfef 100644 --- a/crates/beet_ecs/src/events/mod.rs +++ b/crates/beet_ecs/src/events/mod.rs @@ -1,3 +1,6 @@ +pub mod on_child_value; +#[allow(unused_imports)] +pub use self::on_child_value::*; pub mod on_run; #[allow(unused_imports)] pub use self::on_run::*; diff --git a/crates/beet_ecs/src/events/on_child_value.rs b/crates/beet_ecs/src/events/on_child_value.rs new file mode 100644 index 00000000..faa2b7c0 --- /dev/null +++ b/crates/beet_ecs/src/events/on_child_value.rs @@ -0,0 +1,13 @@ +use bevy::prelude::*; + +#[derive(Debug, Clone, Copy, Event, Reflect)] +pub struct OnChildValue { + child: Entity, + value: T, +} + +impl OnChildValue { + pub fn new(child: Entity, value: T) -> Self { Self { child, value } } + pub fn value(&self) -> &T { &self.value } + pub fn child(&self) -> Entity { self.child } +} diff --git a/crates/beet_ecs/src/events/on_run_result.rs b/crates/beet_ecs/src/events/on_run_result.rs index 12e5dcc3..20ba9ae4 100644 --- a/crates/beet_ecs/src/events/on_run_result.rs +++ b/crates/beet_ecs/src/events/on_run_result.rs @@ -20,21 +20,12 @@ impl OnRunResult { pub fn result(&self) -> RunResult { **self } } -#[derive(Debug, Clone, Copy, Event, Reflect)] -pub struct OnChildResult { - child: Entity, - result: RunResult, -} +pub type OnChildResult = OnChildValue; impl OnChildResult { - pub fn new(child: Entity, result: RunResult) -> Self { - Self { child, result } - } pub fn success(child: Entity) -> Self { Self::new(child, RunResult::Success) } pub fn failure(child: Entity) -> Self { Self::new(child, RunResult::Failure) } - pub fn result(&self) -> RunResult { self.result } - pub fn child(&self) -> Entity { self.child } } diff --git a/crates/beet_ecs/src/lifecycle/lifecycle_plugin.rs b/crates/beet_ecs/src/lifecycle/lifecycle_plugin.rs index 9118d47e..f0a2d727 100644 --- a/crates/beet_ecs/src/lifecycle/lifecycle_plugin.rs +++ b/crates/beet_ecs/src/lifecycle/lifecycle_plugin.rs @@ -31,6 +31,7 @@ impl Plugin for LifecyclePlugin { RemoveOnTrigger, SequenceFlow, ScoreFlow, + ScoreProvider, EndOnRun, RunOnSpawn, )>::default()) diff --git a/crates/beet_examples/src/scenes/fetch.rs b/crates/beet_examples/src/scenes/fetch.rs index 983aa591..437aab29 100644 --- a/crates/beet_examples/src/scenes/fetch.rs +++ b/crates/beet_examples/src/scenes/fetch.rs @@ -48,7 +48,7 @@ pub fn fetch_npc(mut commands: Commands) { .with_children(|parent| { parent.spawn(( Name::new("Idle"), - Score::neutral(), + ScoreProvider::NEUTRAL, TargetAgent(agent), SetAgentOnRun(Velocity::default()), PlayAnimation::new(idle_index).repeat_forever(), @@ -59,7 +59,7 @@ pub fn fetch_npc(mut commands: Commands) { Name::new("Fetch"), Score::default(), TargetAgent(agent), - ScoreSteerTarget::new(10.), + SteerTargetScoreProvider::new(10.), PlayAnimation::new(walk_index).repeat_forever(), SequenceFlow, ))