Commit: 2e851a5
Parent: 8b46470

Refactor bunny "state machine"

Mårten Åsberg committed on 2026-04-14 at 22:04
src/bunny.rs +60 -45
diff --git a/src/bunny.rs b/src/bunny.rs
index 6027ee8..cd1e2d4 100644
@@ -1,10 +1,11 @@
use std::time::Duration;
use std::{ops::DerefMut, time::Duration};
use bevy::{
app::{App, Startup, Update},
asset::{AssetServer, Assets},
ecs::{
component::Component,
entity::Entity,
query::{With, Without},
system::{Commands, Query, Res, ResMut, Single},
},
@@ -25,26 +26,17 @@ pub trait BunnySystems {
impl BunnySystems for App {
fn add_bunny_systems(&mut self) -> &mut Self {
self.add_systems(Startup, setup)
.add_systems(Update, control)
.add_systems(Update, (calculate, jump))
}
}
#[derive(Component, Default)]
struct Bunny {
jump_state: JumpState,
}
#[derive(Component)]
struct Bunny;
#[derive(Default)]
#[derive(Component)]
enum JumpState {
#[default]
Sitting,
Jumping {
from: Vec3,
to: Vec3,
},
Cooldown {
ready: Duration,
},
Jumping { from: Vec3, to: Vec3 },
Cooldown { ready: Duration },
}
const DETECTION_DISTANCE: f32 = 3.0;
@@ -63,45 +55,42 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut meshes: Res
SceneRoot(model.clone()),
Transform::from_xyz(4.0, 0.0, 0.0),
Mesh3d(mesh.clone()),
Bunny::default(),
Bunny,
));
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(-4.0, 0.0, 0.0),
Mesh3d(mesh.clone()),
Bunny::default(),
Bunny,
));
}
fn control(
time: Res<Time>,
type IdleBunniesQuery<'w, 's, 't> =
Query<'w, 's, (Entity, &'t mut Transform), (With<Bunny>, Without<JumpState>)>;
fn calculate(
mut commands: Commands,
dog: Single<&Transform, (With<Dog>, Without<Bunny>)>,
bunnies: Query<(&mut Transform, &mut Bunny)>,
mut bunnies: IdleBunniesQuery,
mut obstacles: Obstacles,
) {
for (mut bunny_transform, mut bunny) in bunnies {
match &bunny.jump_state {
JumpState::Sitting => {
control_sitting(&dog, &mut bunny_transform, &mut bunny, &mut obstacles)
}
JumpState::Jumping { from, to } => {
let from = *from;
let to = *to;
animate_jumping(&time, &mut bunny_transform, &mut bunny, from, to)
}
JumpState::Cooldown { ready } => {
let ready = *ready;
cooldown(&time, &mut bunny, ready);
}
};
}
bunnies.iter_mut().for_each(|(bunny, mut bunny_transform)| {
calculate_next_move(
&mut commands,
&dog,
&mut bunny_transform,
bunny,
&mut obstacles,
);
});
}
fn control_sitting(
fn calculate_next_move(
commands: &mut Commands,
dog: &Transform,
bunny_transform: &mut Transform,
bunny: &mut Bunny,
bunny: Entity,
obstacles: &mut Obstacles,
) {
let direction = bunny_transform.translation - dog.translation;
@@ -117,17 +106,43 @@ fn control_sitting(
return;
};
bunny.jump_state = JumpState::Jumping {
commands.entity(bunny).insert(JumpState::Jumping {
from: bunny_transform.translation,
to,
};
});
bunny_transform.look_to(-(to - bunny_transform.translation), Vec3::Y);
}
fn jump(
mut commands: Commands,
time: Res<Time>,
mut bunnies: Query<(Entity, &mut Transform, &mut JumpState), With<Bunny>>,
) {
bunnies.iter_mut().for_each(
|(bunny, mut bunny_transform, ref mut jump_state)| match jump_state.deref_mut() {
JumpState::Jumping { from, to } => {
let from = *from;
let to = *to;
animate_jumping(
&time,
&mut bunny_transform,
jump_state.deref_mut(),
from,
to,
)
}
JumpState::Cooldown { ready } => {
let ready = *ready;
cooldown(&mut commands, &time, bunny, ready);
}
},
);
}
fn animate_jumping(
time: &Time,
bunny_transform: &mut Transform,
bunny: &mut Bunny,
jump_state: &mut JumpState,
from: Vec3,
to: Vec3,
) {
@@ -138,7 +153,7 @@ fn animate_jumping(
let (direction, length) = direction.normalize_and_length();
if length <= delta {
bunny_transform.translation = to;
bunny.jump_state = JumpState::Cooldown {
*jump_state = JumpState::Cooldown {
ready: time.elapsed() + Duration::from_secs_f32(JUMP_COOLDOWN),
};
return;
@@ -152,8 +167,8 @@ fn animate_jumping(
* (std::f32::consts::PI * from.distance(next) / total_distance).sin();
}
fn cooldown(time: &Time, bunny: &mut Bunny, ready: Duration) {
fn cooldown(commands: &mut Commands, time: &Time, bunny: Entity, ready: Duration) {
if time.elapsed() >= ready {
bunny.jump_state = JumpState::Sitting;
commands.entity(bunny).remove::<JumpState>();
}
}