Compare commits
3 Commits
7cdbe2e2f6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ddefb0b7f | |||
| 2783bb6b2d | |||
| 73750629a3 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -320,7 +320,6 @@ version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3ee8652fe0577fd8a99054e147740850140d530be8e044a9be4e23a3e8a24"
|
||||
dependencies = [
|
||||
"bevy_dylib",
|
||||
"bevy_internal",
|
||||
]
|
||||
|
||||
@@ -605,15 +604,6 @@ dependencies = [
|
||||
"sysinfo",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_dylib"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50a92aea6e896b6939ea51db6ced3a7e2dfd591016286e3eed9cb60d9e4f149"
|
||||
dependencies = [
|
||||
"bevy_internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_ecs"
|
||||
version = "0.17.3"
|
||||
|
||||
@@ -4,11 +4,10 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.17.3", features = ["default", "dynamic_linking"] }
|
||||
bevy = { version = "0.17.3", features = ["default", "bevy_gltf"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
strip = "none"
|
||||
opt-level = "z"
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
66
src/components.rs
Normal file
66
src/components.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
// Common
|
||||
#[derive(Component)]
|
||||
pub struct Unit {
|
||||
pub move_speed: f32,
|
||||
pub height_offset: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct MoveTarget {
|
||||
pub position: Vec3,
|
||||
}
|
||||
|
||||
// Player
|
||||
#[derive(PartialEq)]
|
||||
pub enum Item {
|
||||
None,
|
||||
Burger,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Player {
|
||||
pub holding: Item,
|
||||
pub progress: f32,
|
||||
}
|
||||
#[derive(Component)]
|
||||
pub struct ProgressBar {
|
||||
pub width: f32,
|
||||
pub player_height: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PlayerBurgerIndicator;
|
||||
|
||||
// Customer
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Customer {
|
||||
pub order: Item,
|
||||
pub served: bool,
|
||||
pub range: f32,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct CustomerSpawner {
|
||||
pub timer: Timer,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct CounterQueue {
|
||||
pub positions: Vec<Vec3>,
|
||||
pub occupied: Vec<bool>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Fryer {
|
||||
pub range: f32,
|
||||
pub speed: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct QueueSlot(pub usize);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CustomerBurgerIndicator;
|
||||
344
src/main.rs
344
src/main.rs
@@ -1,4 +1,9 @@
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy::prelude::*;
|
||||
|
||||
mod components;
|
||||
mod systems;
|
||||
|
||||
use systems::{customer::*, gameplay::*, movement::*, setup::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
@@ -12,340 +17,11 @@ fn main() {
|
||||
fryer_cook,
|
||||
customer_serve,
|
||||
update_progress_bar,
|
||||
update_burger_indicators,
|
||||
update_player_indicator,
|
||||
spawn_customers,
|
||||
despawn_customers,
|
||||
update_customer_indicators,
|
||||
),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Item {
|
||||
None,
|
||||
Burger,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Customer {
|
||||
order: Item,
|
||||
served: bool,
|
||||
range: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Fryer {
|
||||
range: f32,
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Unit {
|
||||
move_speed: f32,
|
||||
height_offset: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player {
|
||||
holding: Item,
|
||||
progress: f32,
|
||||
}
|
||||
#[derive(Component)]
|
||||
struct ProgressBar {
|
||||
width: f32,
|
||||
player_height: f32,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct PlayerBurgerIndicator;
|
||||
|
||||
#[derive(Component)]
|
||||
struct MoveTarget {
|
||||
position: Vec3,
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Camera
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.0, 25.0, -25.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
|
||||
// Light
|
||||
commands.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 200.0, // Subtle fill light so shadows aren't pitch black
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 1000.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
-0.8, // Pitch down (looking down at scene)
|
||||
0.5, // Yaw (angle from side)
|
||||
0.0, // Roll (no rotation)
|
||||
)),
|
||||
));
|
||||
|
||||
// Plane
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(50.0)))),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
|
||||
));
|
||||
|
||||
// Fryer
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(2.0, 1.75, 2.0))),
|
||||
Transform::from_xyz(-10.0, 0.875, 0.0),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.1, 0.1, 0.1))),
|
||||
Fryer {
|
||||
range: 3.0,
|
||||
speed: 50.0,
|
||||
},
|
||||
));
|
||||
|
||||
// Front Counter
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(2.0, 1.5, 10.0))),
|
||||
Transform::from_xyz(10.0, 0.75, 0.0),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.7, 0.7, 0.7))),
|
||||
));
|
||||
|
||||
// Player
|
||||
commands.spawn((
|
||||
SceneRoot(asset_server.load("models/employee.glb#Scene0")),
|
||||
Transform::from_xyz(0.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
Unit {
|
||||
move_speed: 10.0,
|
||||
height_offset: 1.5,
|
||||
},
|
||||
Player {
|
||||
holding: Item::None,
|
||||
progress: 0.0,
|
||||
},
|
||||
MoveTarget {
|
||||
position: Vec3::new(0.0, 0.0, 0.0),
|
||||
},
|
||||
// Burger indicator
|
||||
children![(
|
||||
SceneRoot(asset_server.load("models/burger.glb#Scene0")),
|
||||
Transform::from_xyz(0.0, 7.1, 0.0).with_scale(Vec3::splat(0.9)),
|
||||
Visibility::Hidden,
|
||||
PlayerBurgerIndicator,
|
||||
)],
|
||||
));
|
||||
|
||||
// ProgressBar
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(4.0, 0.5, 0.1))),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.0, 1.0, 0.0))),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
ProgressBar {
|
||||
width: 1.0,
|
||||
player_height: 4.0,
|
||||
},
|
||||
NotShadowCaster,
|
||||
));
|
||||
|
||||
// Customer
|
||||
commands.spawn((
|
||||
SceneRoot(asset_server.load("models/customer.glb#Scene0")),
|
||||
Transform::from_xyz(20.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
Unit {
|
||||
move_speed: 5.0,
|
||||
height_offset: 1.5,
|
||||
},
|
||||
Customer {
|
||||
order: Item::Burger,
|
||||
served: false,
|
||||
range: 4.0,
|
||||
},
|
||||
MoveTarget {
|
||||
position: Vec3::new(12.0, 0.0, 0.0),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn handle_click(
|
||||
mouse_button: Res<ButtonInput<MouseButton>>,
|
||||
windows: Query<&Window>,
|
||||
camera_query: Query<(&Camera, &GlobalTransform)>,
|
||||
mut target_query: Query<&mut MoveTarget, With<Player>>,
|
||||
) {
|
||||
// Guard clause
|
||||
if !mouse_button.just_pressed(MouseButton::Right) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(window) = windows.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(cursor_position) = window.cursor_position() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok((camera, camera_transform)) = camera_query.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// check for sky clicks
|
||||
if ray.direction.y > 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the distance that the intercept with the ground occurs
|
||||
let t = -ray.origin.y / ray.direction.y;
|
||||
|
||||
// plug T into our formula
|
||||
let intersection = ray.origin + ray.direction * t;
|
||||
|
||||
for mut target in target_query.iter_mut() {
|
||||
target.position = intersection;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_to_target(mut query: Query<(&mut Transform, &MoveTarget, &Unit)>, time: Res<Time>) {
|
||||
for (mut transform, target, unit) in query.iter_mut() {
|
||||
let adjusted_target = target.position + Vec3::Y * unit.height_offset;
|
||||
let direction = adjusted_target - transform.translation;
|
||||
let distance = direction.length();
|
||||
|
||||
// stop divide by 0 errors
|
||||
if distance < 0.001 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let move_amount = unit.move_speed * time.delta_secs();
|
||||
|
||||
// Snap
|
||||
if distance < move_amount {
|
||||
transform.translation = adjusted_target;
|
||||
} else {
|
||||
transform.translation += direction.normalize() * move_amount;
|
||||
transform.look_at(adjusted_target, Vec3::Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fryer_cook(
|
||||
mut player: Query<(&mut Player, &Transform)>,
|
||||
fryer: Query<(&Fryer, &Transform)>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok((mut player_data, player_transform)) = player.single_mut() else {
|
||||
return;
|
||||
};
|
||||
if player_data.holding == Item::Burger {
|
||||
return;
|
||||
}
|
||||
for (fryer, fryer_transform) in fryer.iter() {
|
||||
let distance = player_transform.translation - fryer_transform.translation;
|
||||
if distance.length() < fryer.range {
|
||||
player_data.progress += fryer.speed * time.delta_secs();
|
||||
|
||||
if player_data.progress > 100.0 {
|
||||
player_data.holding = Item::Burger;
|
||||
println!("YOU HAVE BURGER!!");
|
||||
}
|
||||
} else {
|
||||
player_data.progress = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_progress_bar(
|
||||
player: Query<(&Player, &Transform), Without<ProgressBar>>,
|
||||
mut progress_bar: Query<(&mut Transform, &mut Visibility, &ProgressBar)>,
|
||||
camera: Query<&Transform, (With<Camera3d>, Without<ProgressBar>, Without<Player>)>,
|
||||
) {
|
||||
let Ok((player, player_transform)) = player.single() else {
|
||||
return;
|
||||
};
|
||||
let Ok((mut bar_transform, mut visibility, bar_data)) = progress_bar.single_mut() else {
|
||||
return;
|
||||
};
|
||||
let Ok(camera_transform) = camera.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let progress_percent = (player.progress / 100.0).clamp(0.0, 1.0);
|
||||
|
||||
// Update above player
|
||||
bar_transform.translation = player_transform.translation + Vec3::Y * bar_data.player_height;
|
||||
|
||||
// Billboard effect
|
||||
let to_camera = (camera_transform.translation - bar_transform.translation).normalize();
|
||||
let angle = to_camera.z.atan2(to_camera.x);
|
||||
bar_transform.rotation = Quat::from_rotation_y(angle + std::f32::consts::FRAC_PI_2);
|
||||
|
||||
bar_transform.scale.x = bar_data.width * progress_percent;
|
||||
|
||||
if progress_percent == 0.0 || progress_percent == 1.0 {
|
||||
*visibility = Visibility::Hidden;
|
||||
} else {
|
||||
*visibility = Visibility::Visible;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_burger_indicators(
|
||||
player: Query<&Player>,
|
||||
mut burger: Query<(&mut Visibility, &mut Transform), With<PlayerBurgerIndicator>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok(player_data) = player.single() else {
|
||||
return;
|
||||
};
|
||||
let Ok((mut burger_vis, mut burger_transform)) = burger.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Rotate burger
|
||||
burger_transform.rotate_y(time.delta_secs());
|
||||
|
||||
// Show/hide based on inventory
|
||||
*burger_vis = if player_data.holding == Item::Burger {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
|
||||
fn customer_serve(
|
||||
mut customer: Query<(&mut Customer, &mut MoveTarget, &Transform)>,
|
||||
mut player: Query<(&mut Player, &Transform)>,
|
||||
) {
|
||||
let Ok((mut player_data, player_transform)) = player.single_mut() else {
|
||||
return;
|
||||
};
|
||||
if player_data.holding == Item::None {
|
||||
return;
|
||||
}
|
||||
for (mut customer_data, mut customer_target, customer_transform) in customer.iter_mut() {
|
||||
if customer_data.served {
|
||||
continue;
|
||||
}
|
||||
if player_data.holding != customer_data.order {
|
||||
continue;
|
||||
}
|
||||
|
||||
let distance = player_transform.translation - customer_transform.translation;
|
||||
if distance.length() < customer_data.range {
|
||||
player_data.holding = Item::None;
|
||||
customer_data.served = true;
|
||||
customer_target.position = Vec3::new(100.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
87
src/systems/customer.rs
Normal file
87
src/systems/customer.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::components::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn spawn_customers(
|
||||
mut commands: Commands,
|
||||
mut spawner: ResMut<CustomerSpawner>,
|
||||
mut queue: ResMut<CounterQueue>,
|
||||
time: Res<Time>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
spawner.timer.tick(time.delta());
|
||||
|
||||
// Guard clause
|
||||
if !spawner.timer.just_finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
// check positions
|
||||
let available_slot = queue.occupied.iter().position(|&occupied| !occupied);
|
||||
|
||||
let Some(slot_index) = available_slot else {
|
||||
println!("FULL");
|
||||
return;
|
||||
};
|
||||
|
||||
// Take first location
|
||||
let target_position = queue.positions[slot_index];
|
||||
queue.occupied[slot_index] = true;
|
||||
|
||||
commands.spawn((
|
||||
SceneRoot(asset_server.load("models/customer.glb#Scene0")),
|
||||
Transform::from_xyz(20.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
Unit {
|
||||
move_speed: 5.0,
|
||||
height_offset: 1.5,
|
||||
},
|
||||
Customer {
|
||||
order: Item::Burger,
|
||||
served: false,
|
||||
range: 4.0,
|
||||
},
|
||||
MoveTarget {
|
||||
position: target_position,
|
||||
},
|
||||
QueueSlot(slot_index),
|
||||
children![(
|
||||
SceneRoot(asset_server.load("models/burger.glb#Scene0")),
|
||||
Transform::from_xyz(0.0, 7.1, 0.0).with_scale(Vec3::splat(0.9)),
|
||||
Visibility::Hidden,
|
||||
CustomerBurgerIndicator,
|
||||
)],
|
||||
));
|
||||
}
|
||||
|
||||
pub fn despawn_customers(
|
||||
mut commands: Commands,
|
||||
mut queue: ResMut<CounterQueue>,
|
||||
customers: Query<(Entity, &Transform, &Customer, &QueueSlot)>,
|
||||
) {
|
||||
for (entity, transform, customer, slot) in customers.iter() {
|
||||
if customer.served && transform.translation.x > 25.0 {
|
||||
// Customer reached border
|
||||
queue.occupied[slot.0] = false;
|
||||
|
||||
commands.entity(entity).despawn();
|
||||
|
||||
println!("CUSTOMER DELETED!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_customer_indicators(
|
||||
customers: Query<(&Customer, &Children)>,
|
||||
mut burgers: Query<&mut Visibility, With<CustomerBurgerIndicator>>,
|
||||
) {
|
||||
for (customer, children) in customers.iter() {
|
||||
for child in children.iter() {
|
||||
if let Ok(mut visibility) = burgers.get_mut(child) {
|
||||
*visibility = if customer.served {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/systems/gameplay.rs
Normal file
55
src/systems/gameplay.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::components::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn fryer_cook(
|
||||
mut player: Query<(&mut Player, &Transform)>,
|
||||
fryer: Query<(&Fryer, &Transform)>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok((mut player_data, player_transform)) = player.single_mut() else {
|
||||
return;
|
||||
};
|
||||
if player_data.holding == Item::Burger {
|
||||
return;
|
||||
}
|
||||
for (fryer, fryer_transform) in fryer.iter() {
|
||||
let distance = player_transform.translation - fryer_transform.translation;
|
||||
if distance.length() < fryer.range {
|
||||
player_data.progress += fryer.speed * time.delta_secs();
|
||||
|
||||
if player_data.progress > 100.0 {
|
||||
player_data.holding = Item::Burger;
|
||||
println!("YOU HAVE BURGER!!");
|
||||
}
|
||||
} else {
|
||||
player_data.progress = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn customer_serve(
|
||||
mut customer: Query<(&mut Customer, &mut MoveTarget, &Transform)>,
|
||||
mut player: Query<(&mut Player, &Transform)>,
|
||||
) {
|
||||
let Ok((mut player_data, player_transform)) = player.single_mut() else {
|
||||
return;
|
||||
};
|
||||
if player_data.holding == Item::None {
|
||||
return;
|
||||
}
|
||||
for (mut customer_data, mut customer_target, customer_transform) in customer.iter_mut() {
|
||||
if customer_data.served {
|
||||
continue;
|
||||
}
|
||||
if player_data.holding != customer_data.order {
|
||||
continue;
|
||||
}
|
||||
|
||||
let distance = player_transform.translation - customer_transform.translation;
|
||||
if distance.length() < customer_data.range {
|
||||
player_data.holding = Item::None;
|
||||
customer_data.served = true;
|
||||
customer_target.position = Vec3::new(100.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/systems/mod.rs
Normal file
4
src/systems/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod customer;
|
||||
pub mod gameplay;
|
||||
pub mod movement;
|
||||
pub mod setup;
|
||||
125
src/systems/movement.rs
Normal file
125
src/systems/movement.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::components::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub fn handle_click(
|
||||
mouse_button: Res<ButtonInput<MouseButton>>,
|
||||
windows: Query<&Window>,
|
||||
camera_query: Query<(&Camera, &GlobalTransform)>,
|
||||
mut target_query: Query<&mut MoveTarget, With<Player>>,
|
||||
) {
|
||||
// Guard clause
|
||||
if !mouse_button.just_pressed(MouseButton::Right) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Ok(window) = windows.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(cursor_position) = window.cursor_position() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok((camera, camera_transform)) = camera_query.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// check for sky clicks
|
||||
if ray.direction.y > 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the distance that the intercept with the ground occurs
|
||||
let t = -ray.origin.y / ray.direction.y;
|
||||
|
||||
// plug T into our formula
|
||||
let intersection = ray.origin + ray.direction * t;
|
||||
|
||||
for mut target in target_query.iter_mut() {
|
||||
target.position = intersection;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_to_target(mut query: Query<(&mut Transform, &MoveTarget, &Unit)>, time: Res<Time>) {
|
||||
for (mut transform, target, unit) in query.iter_mut() {
|
||||
let adjusted_target = target.position + Vec3::Y * unit.height_offset;
|
||||
let direction = adjusted_target - transform.translation;
|
||||
let distance = direction.length();
|
||||
|
||||
// stop divide by 0 errors
|
||||
if distance < 0.001 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let move_amount = unit.move_speed * time.delta_secs();
|
||||
|
||||
// Snap
|
||||
if distance < move_amount {
|
||||
transform.translation = adjusted_target;
|
||||
} else {
|
||||
transform.translation += direction.normalize() * move_amount;
|
||||
transform.look_at(adjusted_target, Vec3::Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_progress_bar(
|
||||
player: Query<(&Player, &Transform), Without<ProgressBar>>,
|
||||
mut progress_bar: Query<(&mut Transform, &mut Visibility, &ProgressBar)>,
|
||||
camera: Query<&Transform, (With<Camera3d>, Without<ProgressBar>, Without<Player>)>,
|
||||
) {
|
||||
let Ok((player, player_transform)) = player.single() else {
|
||||
return;
|
||||
};
|
||||
let Ok((mut bar_transform, mut visibility, bar_data)) = progress_bar.single_mut() else {
|
||||
return;
|
||||
};
|
||||
let Ok(camera_transform) = camera.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let progress_percent = (player.progress / 100.0).clamp(0.0, 1.0);
|
||||
|
||||
// Update above player
|
||||
bar_transform.translation = player_transform.translation + Vec3::Y * bar_data.player_height;
|
||||
|
||||
// Billboard effect
|
||||
let to_camera = (camera_transform.translation - bar_transform.translation).normalize();
|
||||
let angle = to_camera.z.atan2(to_camera.x);
|
||||
bar_transform.rotation = Quat::from_rotation_y(angle + std::f32::consts::FRAC_PI_2);
|
||||
|
||||
bar_transform.scale.x = bar_data.width * progress_percent;
|
||||
|
||||
if progress_percent == 0.0 || progress_percent == 1.0 {
|
||||
*visibility = Visibility::Hidden;
|
||||
} else {
|
||||
*visibility = Visibility::Visible;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_player_indicator(
|
||||
player: Query<&Player>,
|
||||
mut burger: Query<(&mut Visibility, &mut Transform), With<PlayerBurgerIndicator>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let Ok(player_data) = player.single() else {
|
||||
return;
|
||||
};
|
||||
let Ok((mut burger_vis, mut burger_transform)) = burger.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Rotate burger
|
||||
burger_transform.rotate_y(time.delta_secs());
|
||||
|
||||
// Show/hide based on inventory
|
||||
*burger_vis = if player_data.holding == Item::Burger {
|
||||
Visibility::Visible
|
||||
} else {
|
||||
Visibility::Hidden
|
||||
};
|
||||
}
|
||||
110
src/systems/setup.rs
Normal file
110
src/systems/setup.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::components::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
|
||||
pub fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Camera
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.0, 25.0, -25.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
|
||||
// Light
|
||||
commands.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 200.0, // Subtle fill light so shadows aren't pitch black
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 1000.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
-0.8, // Pitch down (looking down at scene)
|
||||
0.5, // Yaw (angle from side)
|
||||
0.0, // Roll (no rotation)
|
||||
)),
|
||||
));
|
||||
|
||||
// Floor plane
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(50.0)))),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
|
||||
));
|
||||
|
||||
// Fryer
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(2.0, 1.75, 2.0))),
|
||||
Transform::from_xyz(-10.0, 0.875, 0.0),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.1, 0.1, 0.1))),
|
||||
Fryer {
|
||||
range: 3.0,
|
||||
speed: 50.0,
|
||||
},
|
||||
));
|
||||
|
||||
// Front Counter
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(2.0, 1.5, 10.0))),
|
||||
Transform::from_xyz(10.0, 0.75, 0.0),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.7, 0.7, 0.7))),
|
||||
));
|
||||
|
||||
// Player
|
||||
commands.spawn((
|
||||
SceneRoot(asset_server.load("models/employee.glb#Scene0")),
|
||||
Transform::from_xyz(0.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
Unit {
|
||||
move_speed: 10.0,
|
||||
height_offset: 1.5,
|
||||
},
|
||||
Player {
|
||||
holding: Item::None,
|
||||
progress: 0.0,
|
||||
},
|
||||
MoveTarget {
|
||||
position: Vec3::new(0.0, 0.0, 0.0),
|
||||
},
|
||||
// Burger indicator
|
||||
children![(
|
||||
SceneRoot(asset_server.load("models/burger.glb#Scene0")),
|
||||
Transform::from_xyz(0.0, 7.1, 0.0).with_scale(Vec3::splat(0.9)),
|
||||
Visibility::Hidden,
|
||||
PlayerBurgerIndicator,
|
||||
)],
|
||||
));
|
||||
|
||||
// ProgressBar
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(4.0, 0.5, 0.1))),
|
||||
MeshMaterial3d(materials.add(Color::srgb(0.0, 1.0, 0.0))),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
ProgressBar {
|
||||
width: 1.0,
|
||||
player_height: 4.0,
|
||||
},
|
||||
NotShadowCaster,
|
||||
));
|
||||
|
||||
// Customer spawner
|
||||
commands.insert_resource(CounterQueue {
|
||||
positions: vec![
|
||||
Vec3::new(12.0, 0.0, -4.0),
|
||||
Vec3::new(12.0, 0.0, 0.0),
|
||||
Vec3::new(12.0, 0.0, 4.0),
|
||||
],
|
||||
occupied: vec![false, false, false],
|
||||
});
|
||||
|
||||
commands.insert_resource(CustomerSpawner {
|
||||
timer: Timer::from_seconds(7.0, TimerMode::Repeating),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user