Refactor organisation
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
use crate::{
|
||||
grid,
|
||||
snake::{SnakeHead, Z_HEIGHT},
|
||||
snake::{self, SnakeHead},
|
||||
tick::tick_triggered,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
use iyes_loopless::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
const Z_HEIGHT: f32 = snake::Z_HEIGHT / 2.;
|
||||
|
||||
pub struct FruitPlugin;
|
||||
|
||||
impl Plugin for FruitPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<EatenEvent>()
|
||||
.add_startup_system(spawn_fruit_system)
|
||||
.add_startup_system(spawn_fruit_system.after(snake::SystemLabel::StartUp))
|
||||
.add_system(eat_fruit_system.run_if(tick_triggered))
|
||||
.add_system(debug_eaten_event_system)
|
||||
.add_system(despawn_fruit_system)
|
||||
|
||||
@@ -42,7 +42,7 @@ impl From<Coordinate> for Vec2 {
|
||||
impl From<&Coordinate> for Vec2 {
|
||||
fn from(grid_coordinate: &Coordinate) -> Self {
|
||||
Self::new(
|
||||
f32::from(grid_coordinate.0 - SIZE / 2)* SEGMENT_SIZE,
|
||||
f32::from(grid_coordinate.0 - SIZE / 2) * SEGMENT_SIZE,
|
||||
f32::from(grid_coordinate.1 - SIZE / 2) * SEGMENT_SIZE,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{canvas::CanvasPlugin, fruit::FruitPlugin, snake::SnakePlugin, tick::TickPlugin};
|
||||
use bevy::{prelude::*, render::camera::ScalingMode};
|
||||
use bevy_editor_pls::prelude::*;
|
||||
use bevy_tweening::TweeningPlugin;
|
||||
use grid::{SEGMENT_SIZE, SIZE};
|
||||
use iyes_loopless::prelude::*;
|
||||
use bevy_tweening::TweeningPlugin;
|
||||
|
||||
mod canvas;
|
||||
mod fruit;
|
||||
|
||||
184
src/snake.rs
184
src/snake.rs
@@ -1,15 +1,21 @@
|
||||
mod appearance;
|
||||
mod bulge;
|
||||
mod movement;
|
||||
|
||||
use crate::{fruit, grid, tick::tick_triggered, AppState};
|
||||
use bevy::prelude::*;
|
||||
use bevy_tweening::{lens::TransformScaleLens, *};
|
||||
use bulge::{
|
||||
add_bulge_system, animate_bulge_system, propagate_bulge_system, BulgePropagationTimer,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use iyes_loopless::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)]
|
||||
struct MovementStage;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemLabel)]
|
||||
pub enum SystemLabel {
|
||||
StartUp,
|
||||
SegmentMovement,
|
||||
}
|
||||
|
||||
@@ -20,13 +26,12 @@ impl Plugin for SnakePlugin {
|
||||
let movement_stage = SystemStage::parallel();
|
||||
|
||||
app.insert_resource(BulgePropagationTimer::default())
|
||||
.add_startup_system(setup_snake_system)
|
||||
.add_startup_system(setup_snake_system.label(SystemLabel::StartUp))
|
||||
.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),
|
||||
)
|
||||
@@ -34,7 +39,6 @@ impl Plugin for SnakePlugin {
|
||||
MovementStage,
|
||||
head_movement_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if_not(about_to_lose)
|
||||
.run_if(tick_triggered)
|
||||
.after(SystemLabel::SegmentMovement),
|
||||
)
|
||||
@@ -46,8 +50,12 @@ impl Plugin for SnakePlugin {
|
||||
.add_system(
|
||||
game_over_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered)
|
||||
.run_if(about_to_lose),
|
||||
.run_if(tick_triggered),
|
||||
)
|
||||
.add_system(
|
||||
about_to_collide
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered),
|
||||
)
|
||||
.add_system(add_direction_system.run_in_state(AppState::Begin))
|
||||
.add_system(change_direction_system.run_in_state(AppState::InGame))
|
||||
@@ -62,7 +70,7 @@ impl Plugin for SnakePlugin {
|
||||
pub const Z_HEIGHT: f32 = 10.;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SnakeSegment;
|
||||
struct SnakeSegment;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SnakeHead;
|
||||
@@ -79,26 +87,17 @@ enum Direction {
|
||||
struct DirectionBuffer(Option<Direction>);
|
||||
|
||||
#[derive(Component)]
|
||||
struct Snake;
|
||||
|
||||
#[derive(Component)]
|
||||
struct SnakeSegments(Vec<Entity>);
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct BulgeMarker;
|
||||
|
||||
struct BulgePropagationTimer(Timer);
|
||||
|
||||
impl Default for BulgePropagationTimer {
|
||||
fn default() -> Self {
|
||||
Self(Timer::new(Duration::from_millis(30), true))
|
||||
}
|
||||
struct Snake {
|
||||
about_to_collide: bool,
|
||||
segments: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl SnakeSegments {
|
||||
fn with_head(snake_head: Entity) -> Self {
|
||||
Self(vec![snake_head])
|
||||
impl Snake {
|
||||
fn with_segments(segments: &[Entity]) -> Self {
|
||||
Self {
|
||||
about_to_collide: false,
|
||||
segments: segments.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +144,7 @@ fn setup_snake_system(mut commands: Commands) {
|
||||
|
||||
commands
|
||||
.spawn()
|
||||
.insert(Snake)
|
||||
.insert(SnakeSegments::with_head(snake_head))
|
||||
.insert(Snake::with_segments(&[snake_head]))
|
||||
.insert(Name::new("Snake"))
|
||||
.insert_bundle(SpatialBundle::default())
|
||||
.add_child(snake_head);
|
||||
@@ -168,83 +166,21 @@ fn add_direction_system(
|
||||
fn add_tail_system(
|
||||
mut commands: Commands,
|
||||
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
|
||||
mut snake_query: Query<(Entity, &mut SnakeSegments)>,
|
||||
mut snake_query: Query<(Entity, &mut Snake)>,
|
||||
) {
|
||||
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();
|
||||
|
||||
let segment_number = snake_segments.0.len();
|
||||
snake_segments.0.push(segment);
|
||||
let (snake_entity, mut snake) = snake_query.single_mut();
|
||||
|
||||
snake.segments.push(segment);
|
||||
|
||||
commands
|
||||
.entity(segment)
|
||||
.insert(Name::new(format!("Segment {}", segment_number)));
|
||||
commands.entity(snake).add_child(segment);
|
||||
}
|
||||
}
|
||||
.insert(Name::new(format!("Segment {}", snake.segments.len())));
|
||||
|
||||
fn add_bulge_system(
|
||||
mut commands: Commands,
|
||||
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
|
||||
query: Query<Entity, With<SnakeHead>>,
|
||||
) {
|
||||
for _ in eaten_event_reader.iter() {
|
||||
let snake_head_entity = query.single();
|
||||
commands.entity(snake_head_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bulge_system(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &Parent), (With<SnakeSegment>, With<BulgeMarker>)>,
|
||||
snake_segments_query: Query<&SnakeSegments>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<BulgePropagationTimer>,
|
||||
) {
|
||||
timer.0.tick(time.delta());
|
||||
|
||||
if !timer.0.finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (entity, parent) in query.iter() {
|
||||
commands.entity(entity).remove::<BulgeMarker>();
|
||||
let mut segment_iter = snake_segments_query.get(parent.get()).unwrap().0.iter();
|
||||
segment_iter.find(|&&segment_entity| segment_entity == entity);
|
||||
|
||||
if let Some(&segment_entity) = segment_iter.next() {
|
||||
commands.entity(segment_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn animate_bulge_system(mut commands: Commands, query: Query<Entity, Added<BulgeMarker>>) {
|
||||
for entity in query.iter() {
|
||||
let tween_to = Tween::new(
|
||||
EaseFunction::CubicInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(100),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.0),
|
||||
end: Vec3::splat(1.1),
|
||||
},
|
||||
);
|
||||
|
||||
let tween_back = Tween::new(
|
||||
EaseFunction::CubicInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(150),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.1),
|
||||
end: Vec3::splat(1.0),
|
||||
},
|
||||
);
|
||||
|
||||
let tween = tween_to.then(tween_back);
|
||||
|
||||
commands.entity(entity).insert(Animator::new(tween));
|
||||
commands.entity(snake_entity).add_child(segment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,19 +222,31 @@ fn grid_transform_system(
|
||||
}
|
||||
}
|
||||
|
||||
fn head_movement_system(mut query: Query<(&mut grid::Coordinate, &Direction), With<SnakeHead>>) {
|
||||
let (mut grid_coordinate, &direction) = query.single_mut();
|
||||
fn head_movement_system(
|
||||
mut head_query: Query<(&mut grid::Coordinate, &Direction, &Parent), With<SnakeHead>>,
|
||||
snake_query: Query<&Snake>,
|
||||
) {
|
||||
for (mut grid_coordinate, &direction, parent) in head_query.iter_mut() {
|
||||
let snake = snake_query.get(parent.get()).unwrap();
|
||||
if snake.about_to_collide {
|
||||
continue;
|
||||
}
|
||||
|
||||
*grid_coordinate = next_grid_coordinate(*grid_coordinate, direction);
|
||||
*grid_coordinate = next_grid_coordinate(*grid_coordinate, direction);
|
||||
}
|
||||
}
|
||||
|
||||
fn segments_movement_system(
|
||||
snake_segments_query: Query<&SnakeSegments>,
|
||||
mut segment_query: Query<&mut grid::Coordinate, With<SnakeSegment>>,
|
||||
snake_query: Query<&Snake>,
|
||||
) {
|
||||
for snake_segments in snake_segments_query.iter() {
|
||||
for snake in snake_query.iter() {
|
||||
if snake.about_to_collide {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (&segment_entity, &previous_segment_entity) in
|
||||
snake_segments.0.iter().rev().tuple_windows()
|
||||
snake.segments.iter().rev().tuple_windows()
|
||||
{
|
||||
let previous_coordinate = *segment_query.get(previous_segment_entity).unwrap();
|
||||
let mut coordinate = segment_query.get_mut(segment_entity).unwrap();
|
||||
@@ -307,24 +255,32 @@ fn segments_movement_system(
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_lose(
|
||||
head_query: Query<(&grid::Coordinate, &Direction), With<SnakeHead>>,
|
||||
fn about_to_collide(
|
||||
head_query: Query<(&grid::Coordinate, &Direction, &Parent), With<SnakeHead>>,
|
||||
segment_query: Query<&grid::Coordinate, (With<SnakeSegment>, Without<SnakeHead>)>,
|
||||
) -> bool {
|
||||
let (&head_coordinate, &head_direction) = head_query.single();
|
||||
let next_head_coordinate = next_grid_coordinate(head_coordinate, head_direction);
|
||||
mut snake_query: Query<&mut Snake>,
|
||||
) {
|
||||
for (&head_coordinate, &head_direction, parent) in head_query.iter() {
|
||||
let next_head_coordinate = next_grid_coordinate(head_coordinate, head_direction);
|
||||
|
||||
let hit_border = !next_head_coordinate.in_bounds();
|
||||
let hit_border = !next_head_coordinate.in_bounds();
|
||||
|
||||
let hit_snake = !segment_query
|
||||
.iter()
|
||||
.all(|&coordinate| coordinate != next_head_coordinate);
|
||||
let hit_snake = !segment_query
|
||||
.iter()
|
||||
.all(|&coordinate| coordinate != next_head_coordinate);
|
||||
|
||||
hit_border || hit_snake
|
||||
let mut snake = snake_query
|
||||
.get_mut(parent.get())
|
||||
.expect("Head must be child of Snake.");
|
||||
|
||||
snake.about_to_collide = hit_border || hit_snake;
|
||||
}
|
||||
}
|
||||
|
||||
fn game_over_system(mut commands: Commands) {
|
||||
commands.insert_resource(NextState(AppState::End));
|
||||
fn game_over_system(query: Query<&Snake>, mut commands: Commands) {
|
||||
if query.get_single().unwrap().about_to_collide {
|
||||
commands.insert_resource(NextState(AppState::End));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_grid_coordinate(
|
||||
|
||||
0
src/snake/appearance.rs
Normal file
0
src/snake/appearance.rs
Normal file
81
src/snake/bulge.rs
Normal file
81
src/snake/bulge.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use super::{Snake, SnakeHead, SnakeSegment};
|
||||
use crate::{fruit, tick::TICK_PERIOD};
|
||||
use bevy::prelude::*;
|
||||
use bevy_tweening::{lens::TransformScaleLens, *};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(storage = "SparseSet")]
|
||||
pub(super) struct BulgeMarker;
|
||||
|
||||
pub(super) struct BulgePropagationTimer(Timer);
|
||||
|
||||
impl Default for BulgePropagationTimer {
|
||||
fn default() -> Self {
|
||||
Self(Timer::new(Duration::from_millis(TICK_PERIOD / 4), true))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_bulge_system(
|
||||
mut commands: Commands,
|
||||
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
|
||||
query: Query<Entity, With<SnakeHead>>,
|
||||
) {
|
||||
for _ in eaten_event_reader.iter() {
|
||||
let snake_head_entity = query.single();
|
||||
commands.entity(snake_head_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn propagate_bulge_system(
|
||||
mut commands: Commands,
|
||||
bulge_segment_query: Query<(Entity, &Parent), (With<SnakeSegment>, With<BulgeMarker>)>,
|
||||
snake_query: Query<&Snake>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<BulgePropagationTimer>,
|
||||
) {
|
||||
timer.0.tick(time.delta());
|
||||
|
||||
if !timer.0.finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (entity, parent) in bulge_segment_query.iter() {
|
||||
commands.entity(entity).remove::<BulgeMarker>();
|
||||
|
||||
let mut segment_iter = snake_query.get(parent.get()).unwrap().segments.iter();
|
||||
segment_iter.find(|&&segment_entity| segment_entity == entity);
|
||||
|
||||
if let Some(&segment_entity) = segment_iter.next() {
|
||||
commands.entity(segment_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn animate_bulge_system(mut commands: Commands, query: Query<Entity, Added<BulgeMarker>>) {
|
||||
for entity in query.iter() {
|
||||
let tween_to = Tween::new(
|
||||
EaseFunction::QuadraticInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(100),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.0),
|
||||
end: Vec3::splat(1.1),
|
||||
},
|
||||
);
|
||||
|
||||
let tween_back = Tween::new(
|
||||
EaseFunction::QuadraticInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(150),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.1),
|
||||
end: Vec3::splat(1.0),
|
||||
},
|
||||
);
|
||||
|
||||
let tween = tween_to.then(tween_back);
|
||||
|
||||
commands.entity(entity).insert(Animator::new(tween));
|
||||
}
|
||||
}
|
||||
0
src/snake/movement.rs
Normal file
0
src/snake/movement.rs
Normal file
367
src/snake_old.rs
Normal file
367
src/snake_old.rs
Normal file
@@ -0,0 +1,367 @@
|
||||
use crate::{fruit, grid, tick::tick_triggered, tick::TICK_PERIOD, AppState};
|
||||
use bevy::prelude::*;
|
||||
use bevy_tweening::{lens::TransformScaleLens, *};
|
||||
use itertools::Itertools;
|
||||
use iyes_loopless::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)]
|
||||
struct MovementStage;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemLabel)]
|
||||
pub enum SystemLabel {
|
||||
StartUp,
|
||||
SegmentMovement,
|
||||
}
|
||||
|
||||
pub struct SnakePlugin;
|
||||
|
||||
impl Plugin for SnakePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let movement_stage = SystemStage::parallel();
|
||||
|
||||
app.insert_resource(BulgePropagationTimer::default())
|
||||
.add_startup_system(setup_snake_system.label(SystemLabel::StartUp))
|
||||
.add_stage_after(CoreStage::Update, MovementStage, movement_stage)
|
||||
.add_system_to_stage(
|
||||
MovementStage,
|
||||
segments_movement_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered)
|
||||
.label(SystemLabel::SegmentMovement),
|
||||
)
|
||||
.add_system_to_stage(
|
||||
MovementStage,
|
||||
head_movement_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered)
|
||||
.after(SystemLabel::SegmentMovement),
|
||||
)
|
||||
.add_system(
|
||||
update_direction_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered),
|
||||
)
|
||||
.add_system(
|
||||
game_over_system
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered),
|
||||
)
|
||||
.add_system(
|
||||
about_to_collide
|
||||
.run_in_state(AppState::InGame)
|
||||
.run_if(tick_triggered),
|
||||
)
|
||||
.add_system(add_direction_system.run_in_state(AppState::Begin))
|
||||
.add_system(change_direction_system.run_in_state(AppState::InGame))
|
||||
.add_system(grid_transform_system.run_in_state(AppState::InGame))
|
||||
.add_system(add_tail_system)
|
||||
.add_system(add_bulge_system)
|
||||
.add_system(propagate_bulge_system)
|
||||
.add_system(animate_bulge_system);
|
||||
}
|
||||
}
|
||||
|
||||
pub const Z_HEIGHT: f32 = 10.;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SnakeSegment;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SnakeHead;
|
||||
|
||||
#[derive(Component, Copy, Clone)]
|
||||
enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct DirectionBuffer(Option<Direction>);
|
||||
|
||||
#[derive(Component)]
|
||||
struct Snake {
|
||||
about_to_collide: bool,
|
||||
segments: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl Snake {
|
||||
fn with_segments(segments: &[Entity]) -> Self {
|
||||
Self {
|
||||
about_to_collide: false,
|
||||
segments: segments.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct BulgeMarker;
|
||||
|
||||
struct BulgePropagationTimer(Timer);
|
||||
|
||||
impl Default for BulgePropagationTimer {
|
||||
fn default() -> Self {
|
||||
Self(Timer::new(Duration::from_millis(TICK_PERIOD / 4), true))
|
||||
}
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
fn from_keypress(keypress: Res<Input<KeyCode>>) -> Option<Self> {
|
||||
if keypress.pressed(KeyCode::Up) {
|
||||
Some(Self::Up)
|
||||
} else if keypress.pressed(KeyCode::Down) {
|
||||
Some(Self::Down)
|
||||
} else if keypress.pressed(KeyCode::Left) {
|
||||
Some(Self::Left)
|
||||
} else if keypress.pressed(KeyCode::Right) {
|
||||
Some(Self::Right)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_snake_segment(commands: &mut Commands, grid_position: grid::Coordinate) -> Entity {
|
||||
commands
|
||||
.spawn_bundle(SpriteBundle {
|
||||
sprite: Sprite {
|
||||
color: Color::RED,
|
||||
custom_size: Some(Vec2::splat(grid::SEGMENT_SIZE) * 0.9),
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform::from_translation(Vec2::from(grid_position).extend(Z_HEIGHT)),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(SnakeSegment)
|
||||
.insert(grid_position)
|
||||
.id()
|
||||
}
|
||||
|
||||
fn setup_snake_system(mut commands: Commands) {
|
||||
let snake_head = create_snake_segment(&mut commands, grid::Coordinate::splat(grid::SIZE / 2));
|
||||
|
||||
commands
|
||||
.entity(snake_head)
|
||||
.insert(Name::new("SnakeHead"))
|
||||
.insert(SnakeHead)
|
||||
.insert(DirectionBuffer(None));
|
||||
|
||||
commands
|
||||
.spawn()
|
||||
.insert(Snake::with_segments(&[snake_head]))
|
||||
.insert(Name::new("Snake"))
|
||||
.insert_bundle(SpatialBundle::default())
|
||||
.add_child(snake_head);
|
||||
}
|
||||
|
||||
fn add_direction_system(
|
||||
mut commands: Commands,
|
||||
keypress: Res<Input<KeyCode>>,
|
||||
mut query: Query<Entity, With<SnakeHead>>,
|
||||
) {
|
||||
if let Some(direction) = Direction::from_keypress(keypress) {
|
||||
let snake_head = query.single_mut();
|
||||
commands.entity(snake_head).insert(direction);
|
||||
|
||||
commands.insert_resource(NextState(AppState::InGame));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tail_system(
|
||||
mut commands: Commands,
|
||||
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
|
||||
mut snake_query: Query<(Entity, &mut Snake)>,
|
||||
) {
|
||||
for _ in eaten_event_reader.iter() {
|
||||
let segment =
|
||||
create_snake_segment(&mut commands, grid::Coordinate::splat(grid::Index::MIN / 2));
|
||||
|
||||
let (snake_entity, mut snake) = snake_query.single_mut();
|
||||
|
||||
snake.segments.push(segment);
|
||||
|
||||
commands
|
||||
.entity(segment)
|
||||
.insert(Name::new(format!("Segment {}", snake.segments.len())));
|
||||
|
||||
commands.entity(snake_entity).add_child(segment);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_bulge_system(
|
||||
mut commands: Commands,
|
||||
mut eaten_event_reader: EventReader<fruit::EatenEvent>,
|
||||
query: Query<Entity, With<SnakeHead>>,
|
||||
) {
|
||||
for _ in eaten_event_reader.iter() {
|
||||
let snake_head_entity = query.single();
|
||||
commands.entity(snake_head_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bulge_system(
|
||||
mut commands: Commands,
|
||||
bulge_segment_query: Query<(Entity, &Parent), (With<SnakeSegment>, With<BulgeMarker>)>,
|
||||
snake_query: Query<&Snake>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<BulgePropagationTimer>,
|
||||
) {
|
||||
timer.0.tick(time.delta());
|
||||
|
||||
if !timer.0.finished() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (entity, parent) in bulge_segment_query.iter() {
|
||||
commands.entity(entity).remove::<BulgeMarker>();
|
||||
|
||||
let mut segment_iter = snake_query.get(parent.get()).unwrap().segments.iter();
|
||||
segment_iter.find(|&&segment_entity| segment_entity == entity);
|
||||
|
||||
if let Some(&segment_entity) = segment_iter.next() {
|
||||
commands.entity(segment_entity).insert(BulgeMarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn animate_bulge_system(mut commands: Commands, query: Query<Entity, Added<BulgeMarker>>) {
|
||||
for entity in query.iter() {
|
||||
let tween_to = Tween::new(
|
||||
EaseFunction::QuadraticInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(100),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.0),
|
||||
end: Vec3::splat(1.1),
|
||||
},
|
||||
);
|
||||
|
||||
let tween_back = Tween::new(
|
||||
EaseFunction::QuadraticInOut,
|
||||
TweeningType::Once,
|
||||
Duration::from_millis(150),
|
||||
TransformScaleLens {
|
||||
start: Vec3::splat(1.1),
|
||||
end: Vec3::splat(1.0),
|
||||
},
|
||||
);
|
||||
|
||||
let tween = tween_to.then(tween_back);
|
||||
|
||||
commands.entity(entity).insert(Animator::new(tween));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_direction_system(
|
||||
mut query: Query<(&mut Direction, &mut DirectionBuffer), With<SnakeHead>>,
|
||||
) {
|
||||
for (mut direction, mut direction_buffer) in query.iter_mut() {
|
||||
if let Some(new_direction) = direction_buffer.0 {
|
||||
*direction = new_direction;
|
||||
direction_buffer.0 = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_direction_system(
|
||||
keypress: Res<Input<KeyCode>>,
|
||||
mut query: Query<(&Direction, &mut DirectionBuffer), With<SnakeHead>>,
|
||||
) {
|
||||
if let Some(new_direction) = Direction::from_keypress(keypress) {
|
||||
let (direction, mut direction_buffer) = query.single_mut();
|
||||
|
||||
if let (Direction::Up, Direction::Down)
|
||||
| (Direction::Down, Direction::Up)
|
||||
| (Direction::Left, Direction::Right)
|
||||
| (Direction::Right, Direction::Left) = (direction, &new_direction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
direction_buffer.0 = Some(new_direction);
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_transform_system(
|
||||
mut query: Query<(&mut Transform, &grid::Coordinate), With<SnakeSegment>>,
|
||||
) {
|
||||
for (mut transform, grid_coordinate) in query.iter_mut() {
|
||||
*transform = Transform::from_translation(Vec2::from(grid_coordinate).extend(Z_HEIGHT));
|
||||
}
|
||||
}
|
||||
|
||||
fn head_movement_system(
|
||||
mut head_query: Query<(&mut grid::Coordinate, &Direction, &Parent), With<SnakeHead>>,
|
||||
snake_query: Query<&Snake>,
|
||||
) {
|
||||
for (mut grid_coordinate, &direction, parent) in head_query.iter_mut() {
|
||||
let snake = snake_query.get(parent.get()).unwrap();
|
||||
if snake.about_to_collide {
|
||||
continue;
|
||||
}
|
||||
|
||||
*grid_coordinate = next_grid_coordinate(*grid_coordinate, direction);
|
||||
}
|
||||
}
|
||||
|
||||
fn segments_movement_system(
|
||||
mut segment_query: Query<&mut grid::Coordinate, With<SnakeSegment>>,
|
||||
snake_query: Query<&Snake>,
|
||||
) {
|
||||
for snake in snake_query.iter() {
|
||||
if snake.about_to_collide {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (&segment_entity, &previous_segment_entity) in
|
||||
snake.segments.iter().rev().tuple_windows()
|
||||
{
|
||||
let previous_coordinate = *segment_query.get(previous_segment_entity).unwrap();
|
||||
let mut coordinate = segment_query.get_mut(segment_entity).unwrap();
|
||||
*coordinate = previous_coordinate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn about_to_collide(
|
||||
head_query: Query<(&grid::Coordinate, &Direction, &Parent), With<SnakeHead>>,
|
||||
segment_query: Query<&grid::Coordinate, (With<SnakeSegment>, Without<SnakeHead>)>,
|
||||
mut snake_query: Query<&mut Snake>,
|
||||
) {
|
||||
for (&head_coordinate, &head_direction, parent) in head_query.iter() {
|
||||
let next_head_coordinate = next_grid_coordinate(head_coordinate, head_direction);
|
||||
|
||||
let hit_border = !next_head_coordinate.in_bounds();
|
||||
|
||||
let hit_snake = !segment_query
|
||||
.iter()
|
||||
.all(|&coordinate| coordinate != next_head_coordinate);
|
||||
|
||||
let mut snake = snake_query
|
||||
.get_mut(parent.get())
|
||||
.expect("Head must be child of Snake.");
|
||||
|
||||
snake.about_to_collide = hit_border || hit_snake;
|
||||
}
|
||||
}
|
||||
|
||||
fn game_over_system(query: Query<&Snake>, mut commands: Commands) {
|
||||
if query.get_single().unwrap().about_to_collide {
|
||||
commands.insert_resource(NextState(AppState::End));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_grid_coordinate(
|
||||
current_coordinate: grid::Coordinate,
|
||||
direction: Direction,
|
||||
) -> grid::Coordinate {
|
||||
match direction {
|
||||
Direction::Up => current_coordinate + grid::Coordinate(0, 1),
|
||||
Direction::Down => current_coordinate + grid::Coordinate(0, -1),
|
||||
Direction::Left => current_coordinate + grid::Coordinate(-1, 0),
|
||||
Direction::Right => current_coordinate + grid::Coordinate(1, 0),
|
||||
}
|
||||
}
|
||||
18
src/tick.rs
18
src/tick.rs
@@ -3,7 +3,7 @@ use bevy::prelude::*;
|
||||
use iyes_loopless::prelude::*;
|
||||
use std::time::Duration;
|
||||
|
||||
const TICK_PERIOD: u64 = 125;
|
||||
pub const TICK_PERIOD: u64 = 125;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, StageLabel)]
|
||||
struct FixedTimeStage;
|
||||
@@ -12,15 +12,13 @@ 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::<TickEvent>().add_stage_before(
|
||||
CoreStage::Update,
|
||||
FixedTimeStage,
|
||||
FixedTimestepStage::new(Duration::from_millis(TICK_PERIOD))
|
||||
.with_stage(fixed_time_system_stage),
|
||||
);
|
||||
app.add_event::<TickEvent>()
|
||||
.add_fixed_timestep(Duration::from_millis(TICK_PERIOD), "tick_time_step")
|
||||
.add_fixed_timestep_system(
|
||||
"tick_time_step",
|
||||
0,
|
||||
tick_event_system.run_in_state(AppState::InGame),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user