📄 src/bunny.rs
use bevy::{
    app::{App, Startup, Update},
    asset::AssetServer,
    ecs::{
        component::Component,
        query::{With, Without},
        system::{Commands, Query, Res, Single},
    },
    gltf::GltfAssetLabel,
    math::Vec3,
    scene::SceneRoot,
    time::Time,
    transform::components::Transform,
};

use crate::dog::Dog;

pub trait BunnySystems {
    fn add_bunny_systems(&mut self) -> &mut Self;
}

impl BunnySystems for App {
    fn add_bunny_systems(&mut self) -> &mut Self {
        self.add_systems(Startup, setup)
            .add_systems(Update, control)
    }
}

#[derive(Component, Default)]
struct Bunny;

const DETECTION_DISTANCE: f32 = 2.5;
const SPEED: f32 = 10.0;

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let model = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animal-bunny.glb"));

    commands.spawn((
        SceneRoot(model.clone()),
        Transform::from_xyz(5.0, 0.0, 0.0),
        Bunny::default(),
    ));

    commands.spawn((
        SceneRoot(model.clone()),
        Transform::from_xyz(-5.0, 0.0, 0.0),
        Bunny::default(),
    ));
}

fn control(
    time: Res<Time>,
    dog: Single<&Transform, (With<Dog>, Without<Bunny>)>,
    bunnies: Query<&mut Transform, With<Bunny>>,
) {
    for mut bunny in bunnies {
        let direction = bunny.translation - dog.translation;
        if direction.length_squared() < DETECTION_DISTANCE * DETECTION_DISTANCE {
            bunny.look_to(-direction, Vec3::Y);
            bunny.translation += direction.normalize() * SPEED * time.delta_secs();
        }
    }
}