Spaces:
Running
Running
| use crate::blood::handle_blood_grain; | |
| use crate::bone::handle_bone_grain; | |
| use crate::grid::{Grid, GridPosition}; | |
| use crate::utils::constants::{GAME_RESOLUTION_X, GAME_RESOLUTION_Y, WINDOW_SIZE}; | |
| use crate::utils::remap::remap_cursor_position; | |
| use crate::utils::tick::TickCounter; | |
| use bevy::prelude::*; | |
| use bevy::sprite::Anchor; | |
| use rand::Rng; | |
| /* | |
| 🦀 Components. Learn more: https://bevy-cheatbook.github.io/programming/res.html | |
| */ | |
| pub struct Grain { | |
| pub grain_type: GrainType, | |
| pub velocity: Vec2, // Physics-based movement | |
| pub mass: f32, | |
| pub is_simulated: bool, | |
| } | |
| pub struct Lifetime(pub u32); | |
| pub enum GrainType { | |
| Bone, | |
| Blood, | |
| } | |
| pub enum Direction { | |
| Right, | |
| Left, | |
| None, | |
| } | |
| impl Default for Direction { | |
| fn default() -> Self { | |
| Direction::None | |
| } | |
| } | |
| pub struct GrainBundle { | |
| pub grain: Grain, | |
| pub lifetime: Lifetime, | |
| pub direction: Direction, | |
| pub grid_position: GridPosition, | |
| pub sprite_bundle: SpriteBundle, | |
| } | |
| impl GrainBundle { | |
| pub fn new( | |
| grain_type: GrainType, | |
| x: i32, | |
| y: i32, | |
| color: Color, | |
| size: Vec2, | |
| ) -> Self { | |
| GrainBundle { | |
| grain: Grain { | |
| grain_type, | |
| velocity: Vec2::ZERO, | |
| mass: 1.0, | |
| is_simulated: true, | |
| }, | |
| lifetime: Lifetime(0), | |
| direction: Direction::None, | |
| grid_position: GridPosition::new(x, y), | |
| sprite_bundle: SpriteBundle { | |
| sprite: Sprite { | |
| color, | |
| custom_size: Some(size), | |
| anchor: Anchor::Center, | |
| ..default() | |
| }, | |
| transform: Transform::from_translation(Vec3::new(x as f32, y as f32, 0.0)), | |
| ..default() | |
| }, | |
| } | |
| } | |
| } | |
| pub fn spawn_grain( | |
| mut commands: Commands, | |
| mouse_button_input: Res<Input<MouseButton>>, | |
| windows: Query<&Window>, | |
| camera: Query<(&Camera, &GlobalTransform)>, | |
| ) { | |
| if mouse_button_input.just_pressed(MouseButton::Left) { | |
| let window = windows.single(); | |
| let (camera, camera_transform) = camera.single(); | |
| if let Some(cursor_position) = window.cursor_position() { | |
| if let Some(world_position) = camera.viewport_to_world(camera_transform, cursor_position) { | |
| let grid_pos = remap_cursor_position(world_position.origin.truncate()); | |
| commands.spawn(GrainBundle::new( | |
| GrainType::Blood, | |
| grid_pos.x, | |
| grid_pos.y, | |
| Color::RED, | |
| Vec2::splat(1.0), | |
| )); | |
| } | |
| } | |
| } | |
| if mouse_button_input.just_pressed(MouseButton::Right) { | |
| let window = windows.single(); | |
| let (camera, camera_transform) = camera.single(); | |
| if let Some(cursor_position) = window.cursor_position() { | |
| if let Some(world_position) = camera.viewport_to_world(camera_transform, cursor_position) { | |
| let grid_pos = remap_cursor_position(world_position.origin.truncate()); | |
| commands.spawn(GrainBundle::new( | |
| GrainType::Bone, | |
| grid_pos.x, | |
| grid_pos.y, | |
| Color::WHITE, | |
| Vec2::splat(1.0), | |
| )); | |
| } | |
| } | |
| } | |
| } | |
| pub fn update_grains( | |
| mut query: Query<(&mut Transform, &mut GridPosition, &mut Grain, &mut Lifetime, &mut Direction)>, | |
| mut grid: ResMut<Grid>, | |
| time: Res<Time>, | |
| ) { | |
| // Process grains in reverse order for more realistic physics (bottom-up) | |
| let mut grains: Vec<_> = query.iter_mut().collect(); | |
| grains.sort_by(|a, b| b.1.current_y.cmp(&a.1.current_y)); | |
| for (mut transform, mut grid_position, mut grain, mut lifetime, mut direction) in grains { | |
| if !grain.is_simulated { | |
| continue; | |
| } | |
| // Update lifetime | |
| lifetime.0 += 1; | |
| // Apply gravity | |
| grain.velocity.y -= 9.8 * time.delta_seconds(); | |
| // Apply air resistance | |
| grain.velocity *= 0.995; | |
| // Update position based on velocity | |
| let mut new_x = grid_position.current_x + (grain.velocity.x * time.delta_seconds()).round() as i32; | |
| let mut new_y = grid_position.current_y + (grain.velocity.y * time.delta_seconds()).round() as i32; | |
| // Handle grain-specific behavior | |
| match grain.grain_type { | |
| GrainType::Blood => { | |
| handle_blood_grain( | |
| &mut transform, | |
| &mut grid_position, | |
| &mut grid, | |
| &mut direction, | |
| &mut grain, | |
| lifetime.0, | |
| &mut new_x, | |
| &mut new_y, | |
| ); | |
| } | |
| GrainType::Bone => { | |
| handle_bone_grain( | |
| &mut transform, | |
| &mut grid_position, | |
| &mut grid, | |
| &mut direction, | |
| &mut grain, | |
| lifetime.0, | |
| &mut new_x, | |
| &mut new_y, | |
| ); | |
| } | |
| } | |
| // Update transform position | |
| transform.translation.x = grid_position.current_x as f32; | |
| transform.translation.y = grid_position.current_y as f32; | |
| // Reset previous position tracking | |
| grid_position.reset_prev(); | |
| } | |
| } 🦀 Systems. Learn more: https://bevy-cheatbook.github.io/programming/systems.html | |
| */ | |
| pub fn add_grain( | |
| mut commands: Commands, | |
| query: Query<&Window>, | |
| asset_server: Res<AssetServer>, | |
| input: Res<Input<MouseButton>>, | |
| ) { | |
| if let Some(position) = query.single().cursor_position() { | |
| let remaped_cursor_pos = remap_cursor_position(position, WINDOW_SIZE, [GAME_RESOLUTION_X, GAME_RESOLUTION_Y]); | |
| let mut rng = rand::thread_rng(); | |
| if input.pressed(MouseButton::Left) { | |
| // Create a bone sprite texture | |
| let bone_textures: [&str; 3] = ["bone_1.png", "bone_2.png", "bone_3.png"]; | |
| let random_index = rng.gen_range(0..bone_textures.len()); | |
| let bone_sprite_bundle = SpriteBundle { | |
| sprite: Sprite { | |
| custom_size: Some(Vec2::new(1.0, 1.0)), | |
| anchor: Anchor::TopLeft, | |
| ..default() | |
| }, | |
| texture: asset_server.load(bone_textures[random_index]), | |
| ..default() | |
| }; | |
| // Add a data row (entity) with it's set of components | |
| commands | |
| .spawn(( | |
| Grain, | |
| bone_sprite_bundle, | |
| GrainType::Bone, | |
| GridPosition { | |
| current_x: remaped_cursor_pos.x.round() as i32, | |
| current_y: remaped_cursor_pos.y.round() as i32, | |
| prev_x: None, | |
| prev_y: None, | |
| }, | |
| )) | |
| .insert(Transform { | |
| translation: Vec3::new( | |
| remaped_cursor_pos.x.round() - (GAME_RESOLUTION_X as f32 / 2.0), | |
| -(remaped_cursor_pos.y.round() - (GAME_RESOLUTION_Y as f32 / 2.0)), | |
| 0.0, | |
| ), | |
| ..default() | |
| }); | |
| } | |
| if input.pressed(MouseButton::Right) { | |
| // Creat a blood sprite texture | |
| let blood_sprite_bundle = SpriteBundle { | |
| sprite: Sprite { | |
| custom_size: Some(Vec2::new(1.0, 1.0)), | |
| anchor: Anchor::TopLeft, | |
| ..default() | |
| }, | |
| texture: asset_server.load("blood_2.png"), | |
| ..default() | |
| }; | |
| // Pick a random direction to slide toward | |
| let random_number = rng.gen::<f32>(); | |
| let random_direction = if random_number < 0.5 { | |
| Direction::Left | |
| } else { | |
| Direction::Right | |
| }; | |
| // Add a data row (entity) with it's set of components | |
| commands | |
| .spawn(( | |
| Grain, | |
| Lifetime(0), | |
| blood_sprite_bundle, | |
| GrainType::Blood, | |
| random_direction, | |
| GridPosition { | |
| current_x: remaped_cursor_pos.x.round() as i32, | |
| current_y: remaped_cursor_pos.y.round() as i32, | |
| prev_x: None, | |
| prev_y: None, | |
| }, | |
| )) | |
| .insert(Transform { | |
| translation: Vec3::new( | |
| remaped_cursor_pos.x.round() - (GAME_RESOLUTION_X as f32 / 2.0), | |
| -(remaped_cursor_pos.y.round() - (GAME_RESOLUTION_Y as f32 / 2.0)), | |
| 0.0, | |
| ), | |
| ..default() | |
| }); | |
| } | |
| } | |
| } | |
| pub fn update_grain( | |
| grid_data: Res<Grid>, | |
| mut query: Query<( | |
| &mut Transform, | |
| &mut GridPosition, | |
| &GrainType, | |
| &mut Lifetime, | |
| Option<&mut Direction>, | |
| )>, | |
| mut tick_counter: ResMut<TickCounter>, | |
| ) { | |
| tick_counter.count += 1; | |
| if tick_counter.count >= tick_counter.tick_rate { | |
| tick_counter.count = 0; | |
| for (mut transform, mut grid_position, grain_type, mut lifetime, direction) in query.iter_mut() { | |
| lifetime.0 += 1; | |
| match grain_type { | |
| GrainType::Bone => handle_bone_grain(&grid_position, &grid_data), | |
| GrainType::Blood => { | |
| if let Some(mut dir) = direction { | |
| handle_blood_grain(&mut transform, &mut grid_position, &grid_data, &mut dir, lifetime.0); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |