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;
|
pub struct HelloPlugin;
|
||||||
|
|
||||||
impl Plugin for HelloPlugin {
|
impl Plugin for HelloPlugin {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
use bevy::{prelude::*, window::PrimaryWindow};
|
use std::time::Duration;
|
||||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
|
||||||
|
|
||||||
const SNAKE_HEAD_COLOR: Color = Color::srgb(0.7, 1.0, 0.7);
|
use bevy::{prelude::*, time::common_conditions::on_timer, window::PrimaryWindow};
|
||||||
const ARENA_WIDTH: u32 = 20;
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
const ARENA_HEIGHT: u32 = 15;
|
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;
|
const WINDOW_WIDTH: f32 = 1280.0;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Copy, Clone)]
|
#[derive(Eq, PartialEq, Copy, Clone)]
|
||||||
@ -31,6 +36,18 @@ struct SnakeHead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[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 {
|
struct Position {
|
||||||
x: i32,
|
x: i32,
|
||||||
y: i32,
|
y: i32,
|
||||||
@ -51,10 +68,7 @@ impl Size {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
|
||||||
struct MovementTimer(Timer);
|
|
||||||
|
|
||||||
fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<(&mut SnakeHead)>) {
|
|
||||||
if let Some(mut head) = heads.iter_mut().next() {
|
if let Some(mut head) = heads.iter_mut().next() {
|
||||||
let dir: Direction = if input.pressed(KeyCode::ArrowUp) {
|
let dir: Direction = if input.pressed(KeyCode::ArrowUp) {
|
||||||
Direction::Up
|
Direction::Up
|
||||||
@ -75,24 +89,33 @@ fn snake_movement_input(input: Res<ButtonInput<KeyCode>>, mut heads: Query<(&mut
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn snake_movement(
|
fn snake_movement(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
segments: Res<SnakeSegments>,
|
||||||
time: Res<Time>,
|
heads: Query<(Entity, &SnakeHead)>,
|
||||||
mut timer: ResMut<MovementTimer>,
|
mut positions: Query<&mut Position>,
|
||||||
mut heads: Query<(&SnakeHead, &mut Position)>,
|
mut last_tail_position: ResMut<LastTailPosition>,
|
||||||
) {
|
) {
|
||||||
if let Some((head, mut pos)) = heads.iter_mut().next() {
|
// Filter positions to just the segments
|
||||||
if timer.0.tick(time.delta()).just_finished() {
|
let segment_positions = segments
|
||||||
if head.direction == Direction::Up {
|
.0
|
||||||
pos.y += 1;
|
.iter()
|
||||||
} else if head.direction == Direction::Down {
|
.map(|entity| *positions.get_mut(*entity).unwrap())
|
||||||
pos.y -= 1;
|
.collect::<Vec<Position>>();
|
||||||
} else if head.direction == Direction::Left {
|
let (head_entity, head) = heads.single();
|
||||||
pos.x -= 1;
|
let mut head_pos = positions.get_mut(head_entity).unwrap();
|
||||||
} else if head.direction == Direction::Right {
|
|
||||||
pos.x += 1;
|
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(
|
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_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(Position {
|
||||||
|
x: (random::<f32>() * ARENA_WIDTH as f32) as i32,
|
||||||
|
y: (random::<f32>() * ARENA_HEIGHT as f32) as i32,
|
||||||
|
})
|
||||||
|
.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
|
commands
|
||||||
.spawn(Sprite {
|
.spawn(Sprite {
|
||||||
color: SNAKE_HEAD_COLOR,
|
color: SNAKE_HEAD_COLOR,
|
||||||
custom_size: Some(Vec2::new(1.0, 1.0)),
|
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.insert(SnakeHead {
|
.insert(SnakeHead {
|
||||||
direction: Direction::Right,
|
direction: Direction::Right,
|
||||||
})
|
})
|
||||||
.insert(Position { x: 0, y: 0 })
|
.insert(Size::square(1.0))
|
||||||
.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) {
|
fn setup_camera(mut commands: Commands) {
|
||||||
@ -152,15 +238,28 @@ fn setup_camera(mut commands: Commands) {
|
|||||||
pub struct SnakePlugin;
|
pub struct SnakePlugin;
|
||||||
impl Plugin for SnakePlugin {
|
impl Plugin for SnakePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, (setup_camera, setup_player));
|
app.add_systems(Startup, (setup_camera, spawn_snake))
|
||||||
app.add_systems(Update, snake_movement_input);
|
.add_systems(
|
||||||
app.add_systems(FixedUpdate, snake_movement);
|
Update,
|
||||||
app.add_systems(PostUpdate, (scale_translation, position_translation));
|
(
|
||||||
|
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(
|
app.insert_resource(SnakeSegments::default())
|
||||||
1.0,
|
.insert_resource(LastTailPosition::default());
|
||||||
TimerMode::Repeating,
|
|
||||||
)));
|
// 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