The snake is moving across the screen, but it’s not in any way controlled by a user yet.
We could respond to user input keycodes in the tick system. Instead of next_position += 1 we listen to the user’s pressed keys and add or subtract to the relevant axis.
pub fn tick(
mut commands: Commands,
mut snake: ResMut<Snake>,
positions: Query<(Entity, &Position)>,
input: Res<Input<KeyCode>>,
) {
let mut next_position = snake.segments[0].clone();
// next_position.x += 1;
if input.pressed(KeyCode::Up) {
next_position.y += 1;
} else if input.pressed(KeyCode::Down) {
next_position.y -= 1;
} else if input.pressed(KeyCode::Left) {
next_position.x -= 1;
} else if input.pressed(KeyCode::Right) {
next_position.x += 1;
}
snake.segments.push_front(next_position);
commands.add({
SpawnSnakeSegment {
position: next_position,
}
});
let old_tail = snake.segments.pop_back().unwrap();
if let Some((entity, _)) =
positions.iter().find(|(_, pos)| pos == &&old_tail)
{
commands.entity(entity).despawn_recursive();
}
}
This works, but it produces some odd behavior. The snake only moves when the user is pressing down a direction. We need to keep the snake moving in the same direction as the last pressed key when the user isn’t pressing any keys.
To do that we need to store the last pressed key somewhere. A resource would make sense but there’s one more glitch in the plan: the tick function only runs on the interval we set. That means you have to be actively pressing the key when the system runs to make the snake move.
So to solve this we want to constantly be listening to user input, and update a resource with the last key the user pressed, then read the direction from that resource any time the tick system runs.
In lib.rs add a new submodule called controls and then create a new file called controls.rs.
We’ll define a new public enum named Direction to represent which direction the snake should be heading.
The use keyword isn’t just for what we commonly call imports, it can shorted any module path, even module paths in the same file. We use it here to bring each of the Direction variants into the file’s top level scope.
We’re going to init_resource on Direction so we need to implement Default, which will be Right.
use bevy::prelude::*;
pub enum Direction {
Up,
Down,
Left,
Right,
}
use Direction::*;
impl Default for Direction {
fn default() -> Self {
Right
}
}
fn user_input(
input: Res<Input<KeyCode>>,
mut last_pressed: ResMut<Direction>,
) {
if input.pressed(KeyCode::Up) {
*last_pressed = Up;
} else if input.pressed(KeyCode::Down) {
*last_pressed = Down;
} else if input.pressed(KeyCode::Left) {
*last_pressed = Left;
} else if input.pressed(KeyCode::Right) {
*last_pressed = Right;
}
}
And finally the user_input system takes the user’s input and a mutable reference to the Direction and sets the Direction.
We’re using Direction instead of KeyCode here because we’ll have to handle the current direction in lib.rs and using Direction communicates that there are indeed only four options, instead of the plethora of different keycodes you could act on.
In tick, we’ll get the controls::Direction resource and act on it like we did before using keycodes. We match on the value of the input by dereferencing in the match.
pub fn tick(
mut commands: Commands,
mut snake: ResMut<Snake>,
positions: Query<(Entity, &Position)>,
input: Res<controls::Direction>,
) {
let mut next_position = snake.segments[0].clone();
match *input {
controls::Direction::Up => {
next_position.y += 1;
}
controls::Direction::Down => {
next_position.y -= 1;
}
controls::Direction::Right => {
next_position.x += 1;
}
controls::Direction::Left => {
next_position.x -= 1;
}
};
...
}
In this way we’ve let the user press a key at any time to set which direction to go next, adding more leniency and accurately representing the user’s intent more closely.
We still need to add these resources and systems to the game. To do this, we’ll use Bevy Plugins.
At the top of controls.rs we can create a new ControlsPlugin and implement the Bevy Plugin trait for it.
pub struct ControlsPlugin;
impl Plugin for ControlsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Direction>()
.add_system(user_input);
}
}
Plugins are how, well, everything in Bevy is organized. You’ll notice that if we don’t add the DefaultPlugins bevy doesn’t really do anything in the first place.
We have access to the same power for organizing our own code.
A Bevy Plugin allows us to take a mutable reference to an App and add resources and systems just like we would normally. So we do that for Direction and user_input.
Back in main.rs we then need to bring ControlsPlugin into scope and add the plugin after the DefaultPlugins.
App::new()
.insert_resource(WindowDescriptor {
title: "Snake!".to_string(),
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_plugin(ControlsPlugin)
...
Our snake can move!