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!