Commit: a6ff2db
Parent: 99b7f6e

Jumping bunnies

Mårten Åsberg committed on 2026-04-14 at 13:55
docs/screenshot.png +0 -0
diff --git a/docs/screenshot.png b/docs/screenshot.png
index d5c50a1..28d0b41 100644
Binary files a/docs/screenshot.png and b/docs/screenshot.png differ
src/bunny.rs +85 -10
diff --git a/src/bunny.rs b/src/bunny.rs
index 8351de9..457a5b1 100644
@@ -1,3 +1,5 @@
use std::time::Duration;
use bevy::{
app::{App, Startup, Update},
asset::AssetServer,
@@ -27,10 +29,28 @@ impl BunnySystems for App {
}
#[derive(Component, Default)]
struct Bunny;
struct Bunny {
jump_state: JumpState,
}
enum JumpState {
Sitting,
Jumping { from: Vec3, to: Vec3 },
Cooldown { ready: Duration },
}
impl Default for JumpState {
fn default() -> Self {
JumpState::Sitting
}
}
const DETECTION_DISTANCE: f32 = 2.5;
const SPEED: f32 = 10.0;
const DETECTION_DISTANCE: f32 = 3.0;
const SCARED_JUMP_DISTANCE: f32 = 2.0;
const JUMP_HEIGHT: f32 = 1.0;
const JUMP_COOLDOWN: f32 = 0.05;
const DESIRED_SPEED: f32 = 10.0;
const SPEED: f32 = SCARED_JUMP_DISTANCE / (SCARED_JUMP_DISTANCE / DESIRED_SPEED - JUMP_COOLDOWN);
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let model = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animal-bunny.glb"));
@@ -51,13 +71,68 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
fn control(
time: Res<Time>,
dog: Single<&Transform, (With<Dog>, Without<Bunny>)>,
bunnies: Query<&mut Transform, With<Bunny>>,
bunnies: Query<(&mut Transform, &mut Bunny)>,
) {
for (mut bunny_transform, mut bunny) in bunnies {
match &bunny.jump_state {
JumpState::Sitting => control_sitting(&dog, &mut bunny_transform, &mut bunny),
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);
}
};
}
}
fn control_sitting(dog: &Transform, bunny_transform: &mut Transform, bunny: &mut Bunny) {
let direction = bunny_transform.translation - dog.translation;
if direction.length_squared() > DETECTION_DISTANCE * DETECTION_DISTANCE {
return;
}
let dog_direction = direction.normalize();
bunny.jump_state = JumpState::Jumping {
from: bunny_transform.translation,
to: bunny_transform.translation + dog_direction * SCARED_JUMP_DISTANCE,
};
bunny_transform.look_to(-direction, Vec3::Y);
}
fn animate_jumping(
time: &Time,
bunny_transform: &mut Transform,
bunny: &mut Bunny,
from: Vec3,
to: Vec3,
) {
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();
}
let current = bunny_transform.translation.with_y(to.y);
let direction = to - current;
let delta = SPEED * time.delta_secs();
let (direction, length) = direction.normalize_and_length();
if length <= delta {
bunny_transform.translation = to;
bunny.jump_state = JumpState::Cooldown {
ready: time.elapsed() + Duration::from_secs_f32(JUMP_COOLDOWN),
};
return;
}
let next = current + direction.normalize() * delta;
let total_distance = from.distance(to);
bunny_transform.translation = next
+ Vec3::Y
* JUMP_HEIGHT
* (std::f32::consts::PI * from.distance(next) / total_distance).sin();
}
fn cooldown(time: &Time, bunny: &mut Bunny, ready: Duration) {
if time.elapsed() >= ready {
bunny.jump_state = JumpState::Sitting;
}
}
src/main.rs +6 -2
diff --git a/src/main.rs b/src/main.rs
index e698c99..74a6330 100644
@@ -6,7 +6,7 @@ use bevy::{
app::{App, Startup},
camera::{Camera3d, OrthographicProjection, Projection, ScalingMode},
ecs::system::Commands,
light::DirectionalLight,
light::{AmbientLight, DirectionalLight},
math::Vec3,
transform::components::Transform,
};
@@ -32,7 +32,11 @@ fn setup(mut commands: Commands) {
},
..OrthographicProjection::default_3d()
}),
Transform::from_xyz(0.0, 10.0, 0.0).looking_to(Vec3::NEG_Y, Vec3::Z),
AmbientLight {
brightness: 1000.0,
..Default::default()
},
Transform::from_xyz(0.0, 10.0, -5.0).looking_at(Vec3::ZERO, Vec3::Z),
));
commands.spawn((