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;
impl Plugin for HelloPlugin {

View File

@ -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_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
.spawn(Sprite {
color: SNAKE_HEAD_COLOR,
custom_size: Some(Vec2::new(1.0, 1.0)),
..default()
})
.insert(SnakeHead {
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) {
@ -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>();
}
}