working tail

This commit is contained in:
Christian Nieves
2024-12-09 16:28:12 -06:00
parent f02ba89a93
commit a97a142401
2 changed files with 139 additions and 44 deletions

View File

@ -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 {

View File

@ -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>();
} }
} }