Further refactoring

This commit is contained in:
2022-11-08 22:36:19 +01:00
parent 757180b13c
commit e70ec51673
4 changed files with 204 additions and 552 deletions

View File

@@ -35,15 +35,8 @@ impl Plugin for SnakePlugin {
.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
apply_direction_system
.run_in_state(AppState::InGame)
.run_if(tick_triggered),
)
@@ -53,7 +46,7 @@ impl Plugin for SnakePlugin {
.run_if(tick_triggered),
)
.add_system(
about_to_collide
collision_system
.run_in_state(AppState::InGame)
.run_if(tick_triggered),
)
@@ -69,24 +62,13 @@ impl Plugin for SnakePlugin {
pub const Z_HEIGHT: f32 = 10.;
#[derive(Component)]
#[derive(Component, Debug)]
struct SnakeSegment;
#[derive(Component)]
#[derive(Component, Debug)]
pub struct SnakeHead;
#[derive(Component, Copy, Clone)]
enum Direction {
Up,
Down,
Left,
Right,
}
#[derive(Component)]
struct DirectionBuffer(Option<Direction>);
#[derive(Component)]
#[derive(Component, Debug)]
struct Snake {
about_to_collide: bool,
segments: Vec<Entity>,
@@ -101,6 +83,14 @@ impl Snake {
}
}
#[derive(Component, Copy, Clone, Debug)]
enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
fn from_keypress(keypress: Res<Input<KeyCode>>) -> Option<Self> {
if keypress.pressed(KeyCode::Up) {
@@ -117,6 +107,9 @@ impl Direction {
}
}
#[derive(Component, Default, Debug)]
struct DirectionBuffer(Option<Direction>);
fn create_snake_segment(commands: &mut Commands, grid_position: grid::Coordinate) -> Entity {
commands
.spawn_bundle(SpriteBundle {
@@ -139,13 +132,13 @@ fn setup_snake_system(mut commands: Commands) {
commands
.entity(snake_head)
.insert(Name::new("SnakeHead"))
.insert(SnakeHead)
.insert(DirectionBuffer(None));
.insert(SnakeHead);
commands
.spawn()
.insert(Snake::with_segments(&[snake_head]))
.insert(Name::new("Snake"))
.insert(DirectionBuffer::default())
.insert_bundle(SpatialBundle::default())
.add_child(snake_head);
}
@@ -153,11 +146,11 @@ fn setup_snake_system(mut commands: Commands) {
fn add_direction_system(
mut commands: Commands,
keypress: Res<Input<KeyCode>>,
mut query: Query<Entity, With<SnakeHead>>,
mut query: Query<Entity, With<Snake>>,
) {
if let Some(direction) = Direction::from_keypress(keypress) {
let snake_head = query.single_mut();
commands.entity(snake_head).insert(direction);
let snake = query.single_mut();
commands.entity(snake).insert(direction);
commands.insert_resource(NextState(AppState::InGame));
}
@@ -184,9 +177,7 @@ fn add_tail_system(
}
}
fn update_direction_system(
mut query: Query<(&mut Direction, &mut DirectionBuffer), With<SnakeHead>>,
) {
fn apply_direction_system(mut query: Query<(&mut Direction, &mut DirectionBuffer), With<Snake>>) {
for (mut direction, mut direction_buffer) in query.iter_mut() {
if let Some(new_direction) = direction_buffer.0 {
*direction = new_direction;
@@ -197,7 +188,7 @@ fn update_direction_system(
fn change_direction_system(
keypress: Res<Input<KeyCode>>,
mut query: Query<(&Direction, &mut DirectionBuffer), With<SnakeHead>>,
mut query: Query<(&Direction, &mut DirectionBuffer), With<Snake>>,
) {
if let Some(new_direction) = Direction::from_keypress(keypress) {
let (direction, mut direction_buffer) = query.single_mut();
@@ -205,7 +196,7 @@ fn change_direction_system(
if let (Direction::Up, Direction::Down)
| (Direction::Down, Direction::Up)
| (Direction::Left, Direction::Right)
| (Direction::Right, Direction::Left) = (direction, &new_direction)
| (Direction::Right, Direction::Left) = (direction, new_direction)
{
return;
}
@@ -222,46 +213,49 @@ fn grid_transform_system(
}
}
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>,
snake_query: Query<(&Snake, &Direction)>,
) {
for snake in snake_query.iter() {
if snake.about_to_collide {
for (
Snake {
about_to_collide,
segments,
..
},
direction,
) in snake_query.iter()
{
if *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();
// ... then the other elements
for (&segment, &previous_segment) in segments.iter().rev().tuple_windows() {
let previous_coordinate = *segment_query.get(previous_segment).unwrap();
let mut coordinate = segment_query.get_mut(segment).unwrap();
*coordinate = previous_coordinate;
}
// First move head ...
let head = *segments.iter().next().expect("Snake has always a head");
let mut head_coordinate = segment_query.get_mut(head).unwrap();
*head_coordinate = next_grid_coordinate(&head_coordinate, direction);
}
}
fn about_to_collide(
head_query: Query<(&grid::Coordinate, &Direction, &Parent), With<SnakeHead>>,
fn collision_system(
segment_query: Query<&grid::Coordinate, (With<SnakeSegment>, Without<SnakeHead>)>,
mut snake_query: Query<&mut Snake>,
head_query: Query<(&grid::Coordinate, &Parent), With<SnakeHead>>,
mut snake_query: Query<(&Direction, &mut Snake)>,
) {
for (&head_coordinate, &head_direction, parent) in head_query.iter() {
let next_head_coordinate = next_grid_coordinate(head_coordinate, head_direction);
for (head_coordinate, parent) in head_query.iter() {
let (direction, mut snake) = snake_query
.get_mut(parent.get())
.expect("Head must be child of Snake");
let next_head_coordinate = next_grid_coordinate(head_coordinate, direction);
let hit_border = !next_head_coordinate.in_bounds();
@@ -269,10 +263,6 @@ fn about_to_collide(
.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;
}
}
@@ -284,13 +274,13 @@ fn game_over_system(query: Query<&Snake>, mut commands: Commands) {
}
fn next_grid_coordinate(
current_coordinate: grid::Coordinate,
direction: Direction,
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),
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),
}
}

View File

@@ -1,367 +0,0 @@
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),
}
}