assets/models/fence.glb
+0
-0
diff --git a/assets/models/fence.glb b/assets/models/fence.glb
new file mode 100644
index 0000000..e6faad4
Binary files /dev/null and b/assets/models/fence.glb differ
docs/screenshot.png
+0
-0
diff --git a/docs/screenshot.png b/docs/screenshot.png
index 28d0b41..e8352e9 100644
Binary files a/docs/screenshot.png and b/docs/screenshot.png differ
src/bunny.rs
+30
-11
diff --git a/src/bunny.rs b/src/bunny.rs
index 457a5b1..990b51d 100644
@@ -2,20 +2,21 @@ use std::time::Duration;
use bevy::{
app::{App, Startup, Update},
asset::AssetServer,
asset::{AssetServer, Assets},
ecs::{
component::Component,
query::{With, Without},
system::{Commands, Query, Res, Single},
system::{Commands, Query, Res, ResMut, Single},
},
gltf::GltfAssetLabel,
math::Vec3,
math::{Vec3, primitives::Cuboid},
mesh::{Mesh, Mesh3d, MeshBuilder, Meshable},
scene::SceneRoot,
time::Time,
transform::components::Transform,
};
use crate::dog::Dog;
use crate::{dog::Dog, obstacles::Obstacles};
pub trait BunnySystems {
fn add_bunny_systems(&mut self) -> &mut Self;
@@ -52,18 +53,21 @@ 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>) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut meshes: ResMut<Assets<Mesh>>) {
let model = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animal-bunny.glb"));
let mesh = meshes.add(Cuboid::from_length(2.0).mesh().build());
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(5.0, 0.0, 0.0),
Transform::from_xyz(4.0, 0.0, 0.0),
Mesh3d(mesh.clone()),
Bunny::default(),
));
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(-5.0, 0.0, 0.0),
Transform::from_xyz(-4.0, 0.0, 0.0),
Mesh3d(mesh.clone()),
Bunny::default(),
));
}
@@ -72,10 +76,13 @@ fn control(
time: Res<Time>,
dog: Single<&Transform, (With<Dog>, Without<Bunny>)>,
bunnies: Query<(&mut Transform, &mut Bunny)>,
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),
JumpState::Sitting => {
control_sitting(&dog, &mut bunny_transform, &mut bunny, &mut obstacles)
}
JumpState::Jumping { from, to } => {
let from = *from;
let to = *to;
@@ -89,18 +96,30 @@ fn control(
}
}
fn control_sitting(dog: &Transform, bunny_transform: &mut Transform, bunny: &mut Bunny) {
fn control_sitting(
dog: &Transform,
bunny_transform: &mut Transform,
bunny: &mut Bunny,
obstacles: &mut Obstacles,
) {
let direction = bunny_transform.translation - dog.translation;
if direction.length_squared() > DETECTION_DISTANCE * DETECTION_DISTANCE {
return;
}
let dog_direction = direction.normalize();
let Some(to) = obstacles.avoid(
bunny_transform.translation,
bunny_transform.translation + dog_direction * SCARED_JUMP_DISTANCE,
) else {
return;
};
bunny.jump_state = JumpState::Jumping {
from: bunny_transform.translation,
to: bunny_transform.translation + dog_direction * SCARED_JUMP_DISTANCE,
to,
};
bunny_transform.look_to(-direction, Vec3::Y);
bunny_transform.look_to(-(to - bunny_transform.translation), Vec3::Y);
}
fn animate_jumping(
src/dog.rs
+11
-4
diff --git a/src/dog.rs b/src/dog.rs
index aadfba6..78f4468 100644
@@ -1,14 +1,18 @@
use bevy::{
app::{App, Startup, Update},
asset::AssetServer,
asset::{AssetServer, Assets},
camera::Camera,
ecs::{
component::Component,
query::With,
system::{Commands, Res, Single},
system::{Commands, Res, ResMut, Single},
},
gltf::GltfAssetLabel,
math::{Dir3, Vec3, primitives::InfinitePlane3d},
math::{
Dir3, Vec3,
primitives::{Cuboid, InfinitePlane3d},
},
mesh::{Mesh, Mesh3d, MeshBuilder, Meshable},
scene::SceneRoot,
time::Time,
transform::components::{GlobalTransform, Transform},
@@ -31,10 +35,13 @@ pub struct Dog;
const SPEED: f32 = 10.0;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut meshes: ResMut<Assets<Mesh>>) {
let mesh = meshes.add(Cuboid::from_length(2.0).mesh().build());
commands.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animal-dog.glb"))),
Transform::from_xyz(0.0, 0.0, 0.0),
Mesh3d(mesh),
Dog::default(),
));
}
src/main.rs
+69
-4
diff --git a/src/main.rs b/src/main.rs
index 74a6330..4efd076 100644
@@ -1,13 +1,18 @@
mod bunny;
mod dog;
mod obstacles;
use bevy::{
DefaultPlugins,
app::{App, Startup},
asset::{AssetServer, Assets},
camera::{Camera3d, OrthographicProjection, Projection, ScalingMode},
ecs::system::Commands,
ecs::system::{Commands, Res, ResMut},
gltf::GltfAssetLabel,
light::{AmbientLight, DirectionalLight},
math::Vec3,
math::{Quat, Vec3, primitives::Cuboid},
mesh::{Mesh, Mesh3d, MeshBuilder, Meshable},
scene::SceneRoot,
transform::components::Transform,
};
@@ -17,7 +22,7 @@ use crate::dog::DogSystems;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Startup, (setup, setup_fences))
.add_dog_systems()
.add_bunny_systems()
.run();
@@ -28,7 +33,7 @@ fn setup(mut commands: Commands) {
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: 10.0,
viewport_height: 20.0,
},
..OrthographicProjection::default_3d()
}),
@@ -47,3 +52,63 @@ fn setup(mut commands: Commands) {
Transform::from_xyz(0.0, 0.0, 0.0).looking_to(Vec3::NEG_Y, Vec3::Z),
));
}
fn setup_fences(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
) {
let model = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/fence.glb"));
let mesh = meshes.add(
Cuboid::from_length(1.0)
.mesh()
.build()
.translated_by(Vec3::new(0.5, 0.0, 0.0)),
);
const SIDE: usize = 20;
for offset in 0..SIDE {
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(
offset as f32 - (SIDE as f32 / 2.0 - 0.5),
0.0,
SIDE as f32 / 2.0 + 0.5,
)
.with_rotation(Quat::from_rotation_y(std::f32::consts::FRAC_PI_2)),
Mesh3d(mesh.clone()),
));
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(
offset as f32 - (SIDE as f32 / 2.0 - 0.5),
0.0,
-(SIDE as f32 / 2.0 - 0.5),
)
.with_rotation(Quat::from_rotation_y(std::f32::consts::FRAC_PI_2)),
Mesh3d(mesh.clone()),
));
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(
SIDE as f32 / 2.0 - 0.5,
0.0,
offset as f32 - (SIDE as f32 / 2.0 - 0.5),
),
Mesh3d(mesh.clone()),
));
commands.spawn((
SceneRoot(model.clone()),
Transform::from_xyz(
-(SIDE as f32 / 2.0 + 0.5),
0.0,
offset as f32 - (SIDE as f32 / 2.0 - 0.5),
),
Mesh3d(mesh.clone()),
));
}
}
src/obstacles.rs
+44
-0
diff --git a/src/obstacles.rs b/src/obstacles.rs
new file mode 100644
index 0000000..8ef6a8a
@@ -0,0 +1,44 @@
use bevy::{
ecs::system::SystemParam,
math::{FloatPow, Ray3d, Vec3},
picking::mesh_picking::ray_cast::{MeshRayCast, MeshRayCastSettings},
};
#[derive(SystemParam)]
pub struct Obstacles<'w, 's> {
ray_caster: MeshRayCast<'w, 's>,
}
const TURNS_TO_TEST: usize = 4;
const ROTATION: f32 = std::f32::consts::PI / TURNS_TO_TEST as f32;
impl<'w, 's> Obstacles<'w, 's> {
pub fn avoid(&mut self, from: Vec3, to: Vec3) -> Option<Vec3> {
let dir = to - from;
let ray_settings = MeshRayCastSettings::default().with_early_exit_test(&|_| true);
for turn in 0..=TURNS_TO_TEST {
{
let dir = dir.rotate_y(ROTATION * turn as f32);
let hits = self
.ray_caster
.cast_ray(Ray3d::new(from, dir.try_into().unwrap()), &ray_settings);
if hits.is_empty() || hits[0].1.distance.squared() > dir.length_squared() {
return Some(from + dir);
}
}
if turn != 0 && turn != TURNS_TO_TEST {
let dir = dir.rotate_y(-ROTATION * turn as f32);
let hits = self
.ray_caster
.cast_ray(Ray3d::new(from, dir.try_into().unwrap()), &ray_settings);
if hits.is_empty() || hits[0].1.distance.squared() > dir.length_squared() {
return Some(from + dir);
}
}
}
None
}
}