split code into smaller files for more modular project structure

This commit is contained in:
2026-01-02 22:02:23 +00:00
parent 7cdbe2e2f6
commit 73750629a3
6 changed files with 348 additions and 333 deletions

45
src/components.rs Normal file
View File

@@ -0,0 +1,45 @@
use bevy::prelude::*;
#[derive(PartialEq)]
pub enum Item {
None,
Burger,
}
#[derive(Component)]
pub struct Customer {
pub order: Item,
pub served: bool,
pub range: f32,
}
#[derive(Component)]
pub struct Fryer {
pub range: f32,
pub speed: f32,
}
#[derive(Component)]
pub struct Unit {
pub move_speed: f32,
pub height_offset: f32,
}
#[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;
#[derive(Component)]
pub struct MoveTarget {
pub position: Vec3,
}

View File

@@ -1,4 +1,9 @@
use bevy::{light::NotShadowCaster, prelude::*};
use bevy::prelude::*;
mod components;
mod systems;
use systems::{gameplay::*, movement::*, setup::*};
fn main() {
App::new()
@@ -17,335 +22,3 @@ fn main() {
)
.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);
}
}
}

55
src/systems/gameplay.rs Normal file
View 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);
}
}
}

3
src/systems/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod gameplay;
pub mod movement;
pub mod setup;

125
src/systems/movement.rs Normal file
View 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_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
};
}

114
src/systems/setup.rs Normal file
View File

@@ -0,0 +1,114 @@
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)
)),
));
// 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),
},
));
}