rust-sandbox / rust /src /grain.rs
DSDUDEd's picture
Update rust/src/grain.rs
212614e verified
Raw
History Blame Contribute Delete
10.3 kB
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
*/
#[derive(Component)]
pub struct Grain {
pub grain_type: GrainType,
pub velocity: Vec2, // Physics-based movement
pub mass: f32,
pub is_simulated: bool,
}
#[derive(Component)]
pub struct Lifetime(pub u32);
#[derive(Component, Clone, Copy, Debug, PartialEq)]
pub enum GrainType {
Bone,
Blood,
}
#[derive(Component, PartialEq)]
pub enum Direction {
Right,
Left,
None,
}
impl Default for Direction {
fn default() -> Self {
Direction::None
}
}
#[derive(Bundle)]
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);
}
}
}
}
}
}