diff --git a/Cargo.lock b/Cargo.lock index 5c2e94b..0a2ea9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,7 @@ dependencies = [ "bevy-inspector-egui", "itertools", "iyes_loopless", + "rand", ] [[package]] @@ -2716,6 +2717,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "pretty-type-name" version = "1.0.0" @@ -2763,6 +2770,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index cf90835..3a6bb8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" iyes_loopless = "0.7.0" itertools = "0.10.3" bevy-inspector-egui = "0.12.1" +rand = "0.8.5" # bevy_editor_pls = { git = "https://github.com/jakobhellermann/bevy_editor_pls" } [dependencies.bevy] diff --git a/src/fruit.rs b/src/fruit.rs new file mode 100644 index 0000000..36ce882 --- /dev/null +++ b/src/fruit.rs @@ -0,0 +1,85 @@ +use crate::{ + grid, + snake::{self, SnakeHead, Z_HEIGHT}, + tick::tick_triggered, +}; +use bevy::prelude::*; +use iyes_loopless::prelude::*; +use rand::prelude::*; + +pub struct FruitPlugin; + +impl Plugin for FruitPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_startup_system(spawn_fruit_system) + .add_system( + eat_fruit_system + .run_if(tick_triggered) + .before(snake::SystemLabel::SegmentMovement), + ) + .add_system(debug_eaten_event_system) + .add_system(despawn_fruit_system) + .add_system(spawn_fruit_system.run_on_event::()); + } +} + +pub struct EatenEvent(Option); + +#[derive(Component)] +pub struct Fruit; + +fn spawn_fruit_system(mut commands: Commands) { + // TODO: not spawn in snake + let mut rng = rand::thread_rng(); + let coordinate_range = 0..grid::SIZE; + let fruit_coordinate = grid::Coordinate( + rng.gen_range(coordinate_range.clone()), + rng.gen_range(coordinate_range), + ); + + commands + .spawn_bundle(SpriteBundle { + sprite: Sprite { + color: Color::GREEN, + custom_size: Some(Vec2::splat(grid::SEGMENT_SIZE) * 0.6), + ..Default::default() + }, + transform: Transform::from_translation(Vec2::from(fruit_coordinate).extend(Z_HEIGHT)), + ..Default::default() + }) + .insert(fruit_coordinate) + .insert(Fruit) + .insert(Name::new("Fruit")); +} + +fn eat_fruit_system( + snake_head_query: Query<&grid::Coordinate, With>, + fruit_query: Query<(Entity, &grid::Coordinate), With>, + mut eaten_event_writer: EventWriter, +) { + let &snake_head_coordinate = snake_head_query.single(); + + for (fruit, &fruit_coordinate) in fruit_query.iter() { + if snake_head_coordinate == fruit_coordinate { + eaten_event_writer.send(EatenEvent(Some(fruit))); + } + } +} + +fn despawn_fruit_system(mut commands: Commands, mut event_reader: EventReader) { + for &EatenEvent(fruit) in event_reader.iter() { + if let Some(fruit) = fruit { + commands.entity(fruit).despawn(); + } + } +} + +fn debug_eaten_event_system( + mut event_writer: EventWriter, + keypress: Res>, +) { + if keypress.just_pressed(KeyCode::Space) { + event_writer.send(EatenEvent(None)); + } +} diff --git a/src/main.rs b/src/main.rs index 1ca51ee..9c5425c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ use bevy::{prelude::*, render::camera::ScalingMode}; // use bevy_editor_pls::prelude::*; -use crate::{canvas::CanvasPlugin, snake::SnakePlugin}; +use crate::{canvas::CanvasPlugin, fruit::FruitPlugin, snake::SnakePlugin, tick::TickPlugin}; use bevy_inspector_egui::WorldInspectorPlugin; use grid::{SEGMENT_SIZE, SIZE}; use iyes_loopless::prelude::*; mod canvas; +mod fruit; mod grid; mod snake; +mod tick; const ASPECT_RATIO: f32 = 16. / 9.; const WINDOW_WIDTH: f32 = 720. * ASPECT_RATIO; @@ -23,7 +25,7 @@ enum AppState { fn main() { App::new() - .insert_resource(ClearColor(Color::rgb_u8(12, 12, 12))) + .insert_resource(ClearColor(Color::rgb_u8(64, 64, 64))) .insert_resource(WindowDescriptor { title: "Bevy-Snake".into(), resizable: true, @@ -35,7 +37,9 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugin(WorldInspectorPlugin::new()) // .add_plugin(EditorPlugin) + .add_plugin(TickPlugin) .add_plugin(SnakePlugin) + .add_plugin(FruitPlugin) .add_plugin(CanvasPlugin) .add_startup_system(setup_system) .add_system(camera_move_system) diff --git a/src/snake.rs b/src/snake.rs index 745fe45..56a1b96 100644 --- a/src/snake.rs +++ b/src/snake.rs @@ -1,43 +1,47 @@ -use crate::{grid, AppState}; +use crate::{fruit, grid, tick::tick_triggered, AppState}; use bevy::prelude::*; use itertools::Itertools; use iyes_loopless::prelude::*; -use std::time::Duration; -#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)] -struct FixedTimeStage; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(StageLabel)] +struct MovementStage; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(SystemLabel)] +pub enum SystemLabel { + SegmentMovement, +} pub struct SnakePlugin; impl Plugin for SnakePlugin { fn build(&self, app: &mut App) { - let mut fixed_time_systems0 = SystemStage::parallel(); - fixed_time_systems0.add_system( - segments_movement_system - .run_in_state(AppState::InGame) - .run_if_not(about_to_lose), - ); + let movement_stage = SystemStage::parallel(); - let mut fixed_time_systems1 = SystemStage::parallel(); - fixed_time_systems1 - .add_system( + app.add_startup_system(setup_snake_system) + .add_stage_after(CoreStage::Update, MovementStage, movement_stage) + .add_system_to_stage( + MovementStage, + segments_movement_system + .run_in_state(AppState::InGame) + .run_if_not(about_to_lose) + .run_if(tick_triggered) + .label(SystemLabel::SegmentMovement), + ) + .add_system_to_stage( + MovementStage, head_movement_system .run_in_state(AppState::InGame) - .run_if_not(about_to_lose), + .run_if_not(about_to_lose) + .run_if(tick_triggered) + .after(SystemLabel::SegmentMovement), ) .add_system( game_over_system .run_in_state(AppState::InGame) - .run_if(about_to_lose), - ); - - app.add_startup_system(setup_snake_system) - .add_stage_before( - CoreStage::Update, - FixedTimeStage, - FixedTimestepStage::new(Duration::from_millis(125)) - .with_stage(fixed_time_systems0) - .with_stage(fixed_time_systems1), + .run_if(about_to_lose) + .run_if(tick_triggered), ) .add_system(add_direction_system.run_in_state(AppState::Begin)) .add_system(change_direction_system.run_in_state(AppState::InGame)) @@ -46,13 +50,13 @@ impl Plugin for SnakePlugin { } } -const Z_HEIGHT: f32 = 10.; +pub const Z_HEIGHT: f32 = 10.; #[derive(Component)] struct SnakeSegment; #[derive(Component)] -struct SnakeHead; +pub struct SnakeHead; #[derive(Component, Copy, Clone)] enum Direction { @@ -138,10 +142,10 @@ fn add_direction_system( fn add_tail_system( mut commands: Commands, - keypress: Res>, + mut eaten_event_reader: EventReader, mut snake_query: Query<(Entity, &mut SnakeSegments)>, ) { - if keypress.just_pressed(KeyCode::Space) { + for _ in eaten_event_reader.iter() { let segment = create_snake_segment(&mut commands, grid::Coordinate::splat(grid::Index::MIN / 2)); let (snake, mut snake_segments) = snake_query.single_mut(); diff --git a/src/tick.rs b/src/tick.rs new file mode 100644 index 0000000..df0e11e --- /dev/null +++ b/src/tick.rs @@ -0,0 +1,44 @@ +use crate::AppState; +use bevy::prelude::*; +use iyes_loopless::prelude::*; +use std::time::Duration; + +const TICK_PERIOD: u64 = 125; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(StageLabel)] +struct FixedTimeStage; + +pub struct TickPlugin; + +impl Plugin for TickPlugin { + fn build(&self, app: &mut App) { + let mut fixed_time_system_stage = SystemStage::parallel(); + fixed_time_system_stage.add_system(tick_event_system.run_in_state(AppState::InGame)); + + app.add_event::() + .add_stage_before( + CoreStage::Update, + FixedTimeStage, + FixedTimestepStage::new(Duration::from_millis(TICK_PERIOD)) + .with_stage(fixed_time_system_stage), + ); + // .add_system(print_tick_system); + } +} + +pub struct TickEvent; + +fn tick_event_system(mut event_writer: EventWriter) { + event_writer.send(TickEvent); +} + +pub fn tick_triggered(mut event_reader: EventReader) -> bool { + event_reader.iter().count() != 0 +} + +// fn print_tick_system(mut event_reader: EventReader) { +// for _ in event_reader.iter() { +// println!("Tick"); +// } +// }