Skip to content

Commit

Permalink
feat: ScoreProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jul 11, 2024
1 parent ee88350 commit 54a6ba5
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 85 deletions.
2 changes: 1 addition & 1 deletion crates/beet_core/src/steer/forage_behavior.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
4 changes: 2 additions & 2 deletions crates/beet_core/src/steer/steer_actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
45 changes: 0 additions & 45 deletions crates/beet_core/src/steer/steer_actions/score_steer_target.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<RequestScore>,
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(),
);
}
2 changes: 1 addition & 1 deletion crates/beet_core/src/steer/steer_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Plugin for SteerPlugin {
Cohere<GroupSteerAgent>,
EndOnArrive,
FindSteerTarget,
ScoreSteerTarget,
SteerTargetScoreProvider,
DespawnSteerTarget,
RunOnSteerTargetInsert,
RunOnSteerTargetRemove,
Expand Down
32 changes: 32 additions & 0 deletions crates/beet_ecs/examples/test.rs
Original file line number Diff line number Diff line change
@@ -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<Event2>, query: Query<&Foo>| {
println!("Event2 triggered, num components: {}", query.iter().len());
});
world.observe(|_trigger: Trigger<Event1>, 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!");
}
3 changes: 3 additions & 0 deletions crates/beet_ecs/src/actions/flow/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
66 changes: 44 additions & 22 deletions crates/beet_ecs/src/actions/flow/score_flow.rs
Original file line number Diff line number Diff line change
@@ -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<ScoreValue>;

#[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<Entity, ScoreValue>);

fn on_start(
trigger: Trigger<OnRun>,
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::<Vec<_>>(),
);
}

fn get_highest(scores: Query<&Score>, children: &Children) -> Option<Entity> {
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<OnChildScore>,
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)]
Expand All @@ -42,24 +62,26 @@ 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);

let on_result = observe_trigger_names::<OnRunResult>(world);
let on_run = observe_triggers::<OnRun>(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(),
));
})
Expand Down
34 changes: 34 additions & 0 deletions crates/beet_ecs/src/actions/flow/score_provider.rs
Original file line number Diff line number Diff line change
@@ -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<RequestScore>,
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(),
);
}
2 changes: 1 addition & 1 deletion crates/beet_ecs/src/actions/flow/sequence_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion crates/beet_ecs/src/actions/global/bubble_run_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
}
Expand Down
3 changes: 3 additions & 0 deletions crates/beet_ecs/src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
13 changes: 13 additions & 0 deletions crates/beet_ecs/src/events/on_child_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use bevy::prelude::*;

#[derive(Debug, Clone, Copy, Event, Reflect)]
pub struct OnChildValue<T> {
child: Entity,
value: T,
}

impl<T> OnChildValue<T> {
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 }
}
11 changes: 1 addition & 10 deletions crates/beet_ecs/src/events/on_run_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RunResult>;
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 }
}
1 change: 1 addition & 0 deletions crates/beet_ecs/src/lifecycle/lifecycle_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ impl Plugin for LifecyclePlugin {
RemoveOnTrigger<OnRunResult, Running>,
SequenceFlow,
ScoreFlow,
ScoreProvider,
EndOnRun,
RunOnSpawn,
)>::default())
Expand Down
4 changes: 2 additions & 2 deletions crates/beet_examples/src/scenes/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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,
))
Expand Down

0 comments on commit 54a6ba5

Please sign in to comment.