Newer
Older
Scorers look at the world and boil down arbitrary characteristics into a range of 0.0..=1.0. This module includes the ScorerBuilder trait and some built-in Composite Scorers.
Score value between `0.0..=1.0` associated with a Scorer.
pub struct Score(pub(crate) f32);
impl Score {
/**
Returns the `Score`'s current value.
*/
/**
Set the `Score`'s value.
### Panics
Panics if `value` isn't within `0.0..=1.0`.
pub fn set(&mut self, value: f32) {
if !(0.0..=1.0).contains(&value) {
panic!("Score value must be between 0.0 and 1.0");
}
self.0 = value;
}
}
/**
Trait that must be defined by types in order to be `ScorerBuilder`s. `ScorerBuilder`s' job is to spawn new `Scorer` entities. In general, most of this is already done for you, and the only method you really have to implement is `.build()`.
The `build()` method MUST be implemented for any `ScorerBuilder`s you want to define.
*/
pub trait ScorerBuilder: std::fmt::Debug + Sync + Send {
/**
MUST insert your concrete Scorer component into the Scorer [`Entity`], using `cmd`. You _may_ use `actor`, but it's perfectly normal to just ignore it.
### Example
```no_run
struct MyBuilder;
struct MyScorer;
impl ScorerBuilder for MyBuilder {
fn build(&self, cmd: &mut Commands, action: Entity, actor: Entity) {
cmd.entity(action).insert(MyScorer);
}
}
```
*/
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity);
/**
Don't implement this yourself unless you know what you're doing.
*/
fn attach(&self, cmd: &mut Commands, actor: Entity) -> Entity {
let scorer_ent = cmd.spawn().id();
.insert(Score::default())
.insert(Actor(actor));
self.build(cmd, scorer_ent, actor);
scorer_ent
}
/**
Scorer that always returns the same, fixed score. Good for combining with things creatively!
*/
impl FixedScore {
pub fn build(score: f32) -> FixedScoreBuilder {
FixedScoreBuilder(score)
}
}
pub fn fixed_score_system(mut query: Query<(&FixedScore, &mut Score)>) {
for (FixedScore(fixed), mut score) in query.iter_mut() {
score.set(*fixed);
}
}
#[derive(Debug, Clone)]
pub struct FixedScoreBuilder(f32);
impl ScorerBuilder for FixedScoreBuilder {
fn build(&self, cmd: &mut Commands, action: Entity, _actor: Entity) {
cmd.entity(action).insert(FixedScore(self.0));
/**
Composite Scorer that takes any number of other Scorers and returns the sum of their [`Score`] values if each _individual_ [`Score`] is at or above the configured `threshold`.
### Example
```ignore
Thinker::build()
.when(
AllOrNothing::build()
.push(MyScorer)
.push(MyOtherScorer),
MyAction::build());
```
*/
#[derive(Debug)]
pub struct AllOrNothing {
threshold: f32,
scorers: Vec<ScorerEnt>,
}
impl AllOrNothing {
pub fn build(threshold: f32) -> AllOrNothingBuilder {
AllOrNothingBuilder {
threshold,
scorers: Vec::new(),
}
}
}
pub fn all_or_nothing_system(query: Query<(Entity, &AllOrNothing)>, mut scores: Query<&mut Score>) {
for (
aon_ent,
AllOrNothing {
threshold,
scorers: children,
},
) in query.iter()
{
let mut sum = 0.0;
for ScorerEnt(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
if score.0 < *threshold {
sum = 0.0;
break;
} else {
sum += score.0;
}
}
let mut score = scores.get_mut(aon_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[derive(Debug, Clone)]
pub struct AllOrNothingBuilder {
threshold: f32,
scorers: Vec<Arc<dyn ScorerBuilder>>,
}
/**
Add another Scorer to this [`ScorerBuilder`].
*/
pub fn push(&mut self, scorer: impl ScorerBuilder + 'static) -> &mut Self {
self.scorers.push(Arc::new(scorer));
self
impl ScorerBuilder for AllOrNothingBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| scorer.attach(cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Score::default())
threshold: self.threshold,
scorers: scorers.into_iter().map(ScorerEnt).collect(),
});
/**
Composite Scorer that takes any number of other Scorers and returns the sum of their [`Score`] values if the _total_ summed [`Score`] is at or above the configured `threshold`.
### Example
```ignore
Thinker::build()
.when(
SumOfScorers::build()
.push(MyScorer)
.push(MyOtherScorer),
MyAction::build());
```
*/
#[derive(Debug)]
pub struct SumOfScorers {
threshold: f32,
scorers: Vec<ScorerEnt>,
}
impl SumOfScorers {
pub fn build(threshold: f32) -> SumOfScorersBuilder {
SumOfScorersBuilder {
threshold,
scorers: Vec::new(),
}
}
}
pub fn sum_of_scorers_system(query: Query<(Entity, &SumOfScorers)>, mut scores: Query<&mut Score>) {
for (
sos_ent,
SumOfScorers {
threshold,
scorers: children,
},
) in query.iter()
{
let mut sum = 0.0;
for ScorerEnt(child) in children.iter() {
let score = scores.get_mut(*child).expect("where is it?");
sum += score.0;
}
if sum < *threshold {
sum = 0.0;
}
let mut score = scores.get_mut(sos_ent).expect("where did it go?");
score.set(crate::evaluators::clamp(sum, 0.0, 1.0));
#[derive(Debug, Clone)]
pub struct SumOfScorersBuilder {
threshold: f32,
scorers: Vec<Arc<dyn ScorerBuilder>>,
}
impl SumOfScorersBuilder {
pub fn when(&mut self, scorer: impl ScorerBuilder + 'static) -> &mut Self {
self.scorers.push(Arc::new(scorer));
self
impl ScorerBuilder for SumOfScorersBuilder {
fn build(&self, cmd: &mut Commands, scorer: Entity, actor: Entity) {
let scorers: Vec<_> = self
.scorers
.iter()
.map(|scorer| scorer.attach(cmd, actor))
.collect();
cmd.entity(scorer)
.insert(Transform::default())
.insert(GlobalTransform::default())
.insert(AllOrNothing {
threshold: self.threshold,
scorers: scorers.into_iter().map(ScorerEnt).collect(),
});