A core component of Snake is the eating and spawning of apples.
In lib.rs
create a public submodule called food
.
pub mod board;
pub mod colors;
pub mod controls;
pub mod food;
pub mod snake;
In food.rs
create a struct that we’ll use for tagging Position
s as Food
.
use bevy::prelude::*;
#[derive(Component)]
pub struct Food;
In board.rs
we can copy/paste the code we used for our custom snake segment spawn command and change the name and color to make it an Apple spawner!
Also note we’ve brought Food
into scope and are insert
ing it after we insert the position.
pub struct SpawnApple {
pub position: Position,
}
impl Command for SpawnApple {
fn write(self, world: &mut World) {
let board = world
.query::<&Board>()
.iter(&world)
.next()
.unwrap();
let x = board
.cell_position_to_physical(self.position.x);
let y = board
.cell_position_to_physical(self.position.y);
world
.spawn()
.insert_bundle(SpriteBundle {
sprite: Sprite {
color: COLORS.food,
custom_size: Some(Vec2::new(
TILE_SIZE, TILE_SIZE,
)),
..Sprite::default()
},
transform: Transform::from_xyz(x, y, 2.0),
..Default::default()
})
.insert(self.position);
}
}
In colors.rs
I’ve picked a red color to represent my apples.
use bevy::prelude::Color;
pub struct Colors {
pub board: Color,
pub tile_placeholder: Color,
pub tile_placeholder_dark: Color,
pub snake: Color,
pub food: Color,
}
pub const COLORS: Colors = Colors {
board: Color::rgb(0.42, 0.63, 0.07),
tile_placeholder: Color::rgb(0.62, 0.83, 0.27),
tile_placeholder_dark: Color::rgb(0.57, 0.78, 0.22),
snake: Color::WHITE,
food: Color::RED,
};
and in board.rs
we’ll need to use our SpawnApple
right below our SpawnSnakeSegment
usage.
for segment in snake.segments.iter() {
commands.add({
SpawnSnakeSegment { position: *segment }
});
}
commands.add(SpawnApple {
position: Position { x: 15, y: 15 },
})
I’ve picked (15, 15)
as the first apple spawn which feels good since the snake spawns in the lower left of the board.
Eating the Apples
With an apple on the board, our snake needs to be able to actually eat them. When it eats an apple, it should grow into the apple space and continue moving.
In tick
in lib.rs
add a query for Position
s with Food
components. [With](https://docs.rs/bevy/0.7.0/bevy/ecs/query/struct.With.html)
is a query filter. The first tuple in the Query
type arguments is the items we want to query for, while the second tuple is for filtering down our query to only the items we want.
pub fn tick(
mut commands: Commands,
mut snake: ResMut<Snake>,
positions: Query<(Entity, &Position)>,
input: Res<controls::Direction>,
query_food: Query<(Entity, &Position), With<Food>>,
) {
...
}
Lower down in tick
we’ll replace the old_tail
code with code that detects if the snake is eating food or not.
First we determine if the next_position
, which is the position of the new snake head, overlaps with a food position.
Then we match on the result of that and if we are on an apple, then we despawn the apple. If we aren’t on an apple, then we pop off the old tail and despawn it.
Take the old_tail
code we have below commands.add
and put it in the None
match here to handle the despawn logic.
commands.add({
SpawnSnakeSegment {
position: next_position,
}
});
// remove old snake segment, unless snake just
// ate food
let is_food = query_food
.iter()
.find(|(_, pos)| &&next_position == pos);
match is_food {
Some((entity, _)) => {
commands.entity(entity).despawn_recursive();
}
None => {
let old_tail =
snake.segments.pop_back().unwrap();
if let Some((entity, _)) = positions
.iter()
.find(|(_, pos)| pos == &&old_tail)
{
commands.entity(entity).despawn_recursive();
}
}
}
And now on cargo run
we’ve got a growing snake when it eats an apple!