working tail
This commit is contained in:
@ -32,10 +32,6 @@ fn change_names(mut query: Query<&mut Name, With<Person>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn hello_world() {
|
||||
println!("hello world!");
|
||||
}
|
||||
|
||||
pub struct HelloPlugin;
|
||||
|
||||
impl Plugin for HelloPlugin {
|
||||
|
@ -1,9 +1,14 @@
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
use std::time::Duration;
|
||||
|
||||
const SNAKE_HEAD_COLOR: Color = Color::srgb(0.7, 1.0, 0.7);
|
||||
const ARENA_WIDTH: u32 = 20;
|
||||
const ARENA_HEIGHT: u32 = 15;
|
||||
use bevy::{prelude::*, time::common_conditions::on_timer, window::PrimaryWindow};
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
use rand::prelude::random;
|
||||
|
||||
const SNAKE_HEAD_COLOR: Color = Color::srgb(0.5, 0.7, 0.5);
|
||||
const SNAKE_SEGMENT_COLOR: Color = Color::srgb(0.3, 0.5, 0.3);
|
||||
const FOOD_COLOR: Color = Color::srgb(0.7, 0.5, 0.7);
|
||||
const ARENA_WIDTH: u32 = 15;
|
||||
const ARENA_HEIGHT: u32 = 10;
|
||||
const WINDOW_WIDTH: f32 = 1280.0;
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||
@ -31,6 +36,18 @@ struct SnakeHead {
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct SnakeSegment;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct SnakeSegments(Vec<Entity>);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct LastTailPosition(Option<Position>);
|
||||
|
||||
#[derive(Component)]
|
||||
struct Food;
|
||||
|
||||
#[derive(Component, Clone, Copy, Eq, PartialEq)]
|
||||
struct Position {
|
||||
x: i32,
|
||||
y: i32,
|
||||
@ -51,10 +68,7 @@ impl Size {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MovementTimer(Timer);
|
||||
|
||||
fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<(&mut SnakeHead)>) {
|
||||
fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
|
||||
if let Some(mut head) = heads.iter_mut().next() {
|
||||
let dir: Direction = if input.pressed(KeyCode::ArrowUp) {
|
||||
Direction::Up
|
||||
@ -75,24 +89,33 @@ fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<(&mut
|
||||
}
|
||||
|
||||
fn snake_movement(
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<MovementTimer>,
|
||||
mut heads: Query<(&SnakeHead, &mut Position)>,
|
||||
segments: Res<SnakeSegments>,
|
||||
heads: Query<(Entity, &SnakeHead)>,
|
||||
mut positions: Query<&mut Position>,
|
||||
mut last_tail_position: ResMut<LastTailPosition>,
|
||||
) {
|
||||
if let Some((head, mut pos)) = heads.iter_mut().next() {
|
||||
if timer.0.tick(time.delta()).just_finished() {
|
||||
if head.direction == Direction::Up {
|
||||
pos.y += 1;
|
||||
} else if head.direction == Direction::Down {
|
||||
pos.y -= 1;
|
||||
} else if head.direction == Direction::Left {
|
||||
pos.x -= 1;
|
||||
} else if head.direction == Direction::Right {
|
||||
pos.x += 1;
|
||||
}
|
||||
}
|
||||
// Filter positions to just the segments
|
||||
let segment_positions = segments
|
||||
.0
|
||||
.iter()
|
||||
.map(|entity| *positions.get_mut(*entity).unwrap())
|
||||
.collect::<Vec<Position>>();
|
||||
let (head_entity, head) = heads.single();
|
||||
let mut head_pos = positions.get_mut(head_entity).unwrap();
|
||||
|
||||
match &head.direction {
|
||||
Direction::Up => head_pos.y += 1,
|
||||
Direction::Down => head_pos.y -= 1,
|
||||
Direction::Left => head_pos.x -= 1,
|
||||
Direction::Right => head_pos.x += 1,
|
||||
}
|
||||
segment_positions
|
||||
.iter()
|
||||
.zip(segments.0.iter().skip(1))
|
||||
.for_each(|(pos, segment)| {
|
||||
*positions.get_mut(*segment).unwrap() = *pos;
|
||||
});
|
||||
last_tail_position.0 = Some(*segment_positions.last().unwrap());
|
||||
}
|
||||
|
||||
fn scale_translation(
|
||||
@ -131,18 +154,81 @@ fn position_translation(
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_player(mut commands: Commands) {
|
||||
fn spawn_segment(commands: &mut Commands, pos: Position) -> Entity {
|
||||
commands
|
||||
.spawn(Sprite {
|
||||
color: SNAKE_HEAD_COLOR,
|
||||
custom_size: Some(Vec2::new(1.0, 1.0)),
|
||||
color: SNAKE_SEGMENT_COLOR,
|
||||
..Default::default()
|
||||
})
|
||||
.insert(SnakeSegment)
|
||||
.insert(Name::new(String::from("SnakeSegment")))
|
||||
.insert(pos)
|
||||
.insert(Size::square(0.77))
|
||||
.id()
|
||||
}
|
||||
|
||||
fn spawn_food(mut commands: Commands) {
|
||||
commands
|
||||
.spawn(Sprite {
|
||||
color: FOOD_COLOR,
|
||||
..default()
|
||||
})
|
||||
.insert(SnakeHead {
|
||||
direction: Direction::Right,
|
||||
.insert(Position {
|
||||
x: (random::<f32>() * ARENA_WIDTH as f32) as i32,
|
||||
y: (random::<f32>() * ARENA_HEIGHT as f32) as i32,
|
||||
})
|
||||
.insert(Position { x: 0, y: 0 })
|
||||
.insert(Size::square(1.0));
|
||||
.insert(Name::new(String::from("Food")))
|
||||
.insert(Size::square(0.8))
|
||||
.insert(Food);
|
||||
}
|
||||
|
||||
fn spawn_snake(mut commands: Commands, mut segments: ResMut<SnakeSegments>) {
|
||||
*segments = SnakeSegments(vec![
|
||||
commands
|
||||
.spawn(Sprite {
|
||||
color: SNAKE_HEAD_COLOR,
|
||||
..default()
|
||||
})
|
||||
.insert(SnakeHead {
|
||||
direction: Direction::Right,
|
||||
})
|
||||
.insert(Size::square(1.0))
|
||||
.insert(Name::new(String::from("Snake Head")))
|
||||
.insert(Position { x: 5, y: 0 })
|
||||
.id(),
|
||||
spawn_segment(&mut commands, Position { x: 4, y: 0 }),
|
||||
])
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
struct GrowthEvent;
|
||||
|
||||
fn snake_eating(
|
||||
mut commands: Commands,
|
||||
mut growth_writer: EventWriter<GrowthEvent>,
|
||||
food_positions: Query<(Entity, &Position), With<Food>>,
|
||||
head_positions: Query<&Position, With<SnakeHead>>,
|
||||
) {
|
||||
let head_pos = head_positions.single();
|
||||
for (ent, food_pos) in food_positions.iter() {
|
||||
if food_pos == head_pos {
|
||||
commands.entity(ent).despawn();
|
||||
growth_writer.send(GrowthEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn snake_growth(
|
||||
mut commands: Commands,
|
||||
last_tail_position: Res<LastTailPosition>,
|
||||
mut segments: ResMut<SnakeSegments>,
|
||||
mut growth_reader: EventReader<GrowthEvent>,
|
||||
) {
|
||||
if growth_reader.read().next().is_some() {
|
||||
segments
|
||||
.0
|
||||
.push(spawn_segment(&mut commands, last_tail_position.0.unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_camera(mut commands: Commands) {
|
||||
@ -152,15 +238,28 @@ fn setup_camera(mut commands: Commands) {
|
||||
pub struct SnakePlugin;
|
||||
impl Plugin for SnakePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, (setup_camera, setup_player));
|
||||
app.add_systems(Update, snake_movement_input);
|
||||
app.add_systems(FixedUpdate, snake_movement);
|
||||
app.add_systems(PostUpdate, (scale_translation, position_translation));
|
||||
app.add_systems(Startup, (setup_camera, spawn_snake))
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
snake_movement_input,
|
||||
(spawn_food).run_if(on_timer(Duration::from_secs(3))),
|
||||
),
|
||||
)
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
(snake_movement, snake_eating, snake_growth)
|
||||
.chain()
|
||||
.run_if(on_timer(Duration::from_millis(500))),
|
||||
)
|
||||
.add_systems(PostUpdate, (scale_translation, position_translation));
|
||||
|
||||
app.insert_resource(MovementTimer(Timer::from_seconds(
|
||||
1.0,
|
||||
TimerMode::Repeating,
|
||||
)));
|
||||
app.insert_resource(SnakeSegments::default())
|
||||
.insert_resource(LastTailPosition::default());
|
||||
|
||||
// Events must be added to the app before they can be used
|
||||
// using the 'add_event' method
|
||||
app.add_event::<GrowthEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user