📄 src/goal.rs
use bevy::{
    app::{App, Startup, Update},
    asset::Assets,
    color::Color,
    ecs::{
        component::Component,
        query::With,
        system::{Commands, Query, Res, ResMut, Single},
    },
    math::{Quat, primitives::Circle},
    mesh::{Mesh, Mesh3d},
    pbr::{MeshMaterial3d, StandardMaterial},
    text::{TextFont, TextSpan},
    transform::components::Transform,
    ui::widget::Text,
};
use rand::RngExt;

use crate::{
    Rng,
    bunnies::{Bunny, BunnyLocator},
};

pub trait GoalSystems {
    fn add_goal_systems(&mut self) -> &mut Self;
}

impl GoalSystems for App {
    fn add_goal_systems(&mut self) -> &mut Self {
        self.add_systems(Startup, setup).add_systems(Update, update)
    }
}

const GOAL_RADIUS: f32 = 5.0;

#[derive(Component)]
struct Goal;

#[derive(Component)]
struct GoalText;

fn setup(
    mut rng: ResMut<Rng>,
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn((
        Mesh3d(meshes.add(Circle::new(GOAL_RADIUS))),
        MeshMaterial3d(materials.add(Color::linear_rgb(0.0, 1.0, 0.0))),
        Transform::from_xyz(
            rng.random_range(-25.0..=25.0),
            0.0,
            rng.random_range(-25.0..=25.0),
        )
        .with_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
        Goal,
    ));
    commands
        .spawn((
            Text::new("Bunnies in goal: "),
            TextFont {
                font_size: 42.0,
                ..Default::default()
            },
        ))
        .with_child((
            TextSpan::default(),
            TextFont {
                font_size: 33.0,
                ..Default::default()
            },
            GoalText,
        ));
}

fn update(
    locator: Res<BunnyLocator>,
    bunnies: Query<(), With<Bunny>>,
    goal: Single<&Transform, With<Goal>>,
    mut goal_text: Single<&mut TextSpan, With<GoalText>>,
) {
    let count = bunnies.count();
    let in_goal = locator.get_nearby_count(goal.translation, GOAL_RADIUS);
    **goal_text = format!("{in_goal}/{count}").into();
}