Bevy #2: Space Shooter - The Player
Tags: rust, gamedev, tutorial
Published: 2020-09-26 21:52:11

NOTE: Since this has been written, Bevy has received many awesome, but sadly, breaking updates. The code in this guide is no longer maintained.

{% vimeo 462220496 %}

Welcome back! If you didn't see the previous article, you can find that here: {% link ethanyidong/the-bevy-engine-1-4k4k %}

You'll need to download the assets [here](https://www.kenney.nl/assets/space-shooter-redux) to follow along. (Kenney assets are great for any aspiring devs with questionable art skills, make sure to check out the rest of their stuff)
Again, setup your project the same way as the last time: `cargo new <your-cool-name>` and

### *`Cargo.toml`*
```toml
[dependencies]
bevy = "0.2"
```
Now, make a folder structure with assets/textures in your projects, and move in some images from the asset pack into this folder. You can choose whatever, but for this tutorial  I'm using `playerShip1_blue.png`, `laserRed01.png`, `laserBlue01.png`, and `enemyRed1.png`![Screen Shot 2020-09-26 at 12.52.39 PM](https://dev-to-uploads.s3.amazonaws.com/i/tr5l6w2rbeu78up5wdw6.png)
 

# Movement code

This is going to be a lot of repeat from last time, so here's the player movement code in full.

### *`main.rs`*
```rs
use bevy::prelude::*;

struct Player {
    speed: f32, /* #1 */
}

fn main() {
    App::build()
        .add_resource(WindowDescriptor { /* #2 */
            title: "Space Shooter".to_string(),
            width: 1024,
            height: 1024,
            vsync: true,
            resizable: false,
            ..Default::default()
        })
        .add_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0)))
        .add_default_plugins()
        .add_startup_system(setup.system())
        .add_system(player_control.system())
        .run();
}

fn setup(
    mut commands: Commands, 
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>
) {
    let player_texture_handle = asset_server.load("assets/textures/playerShip1_blue.png").unwrap();/* #3 */
    commands
        .spawn(Camera2dComponents::default())
        .spawn(SpriteComponents {
            material: materials.add(player_texture_handle.into()),
            transform: Transform::from_translation(Vec3::new(0.0, -256.0, 0.0)),
            ..Default::default()
        })
        .with(Player { 
            speed: 400.0
        });
}

fn player_control(
    time: Res<Time>, /* #4 */
    keyboard_input: Res<Input<KeyCode>>,
    mut query: Query<(&Player, &mut Transform)>,
) {
    let mut movement = 0.0;
    if keyboard_input.pressed(KeyCode::A) {
        movement -= 1.0;
    }
    if keyboard_input.pressed(KeyCode::D) {
        movement += 1.0;
    }

    for (player, mut transform) in &mut query.iter() {
        transform.translate(Vec3::new(movement * player.speed * time.delta_seconds, 0.0, 0.0)) /* #4 */
    }
}
```
Most of this should be self-explanatory, except for a few key differences I want to highlight.

1. Our player struct now has a `speed` property that controls its speed. We use this property within the `player_control` system to move the player accordingly. We could just move this to a constant instead, but this is cleaner and takes advantage of the powers of ECS.
2. We've added in a `WindowDescriptor`. You can find more details [here](https://docs.rs/bevy/0.2.1/bevy/prelude/struct.WindowDescriptor.html), but it basically tells the Window plugin how to construct the window. The `ClearColor` is changed as well, to get a black background. 
3. Here because we want to draw something other than a rectangle (boring), we have to load a handle to the texture with `AssetServer`, a resource added in the default plugin.
4. Finally, we added another resource handle to our movement system because we need to use `Time.delta_seconds` to move our ship regardless of framerate.

# Shooting

We could make the player entity directly spawn the laser bolts into the world, but the player isn't the only thing that needs to shoot. Later when we go to implement enemy shooting, we would need to copy that code over, and we'd have to edit both every time we wanted to make a change.

Luckily, ECS lets us modularize very easily by creating a new Component.

### *`main.rs`*
```rs
struct Weapon {
    fired: bool,
    offset: Vec3,
    cooldown: Timer,
    material_id: usize,
}

fn main() {
    /**/
        .add_system_to_stage(stage::POST_UPDATE, weapons.system())
}

fn setup(
    /**/
) {
    /**/
        .with(Player { 
            speed: 400.0
        })
        .with(Weapon {
            fired: false,
            offset: Vec3::new(0.0, 30.0, 0.0),
            cooldown: Timer::from_seconds(0.4, false),
            material_id: 0,
        });
}

fn player_control(
    time: Res<Time>,
    keyboard_input: Res<Input<KeyCode>>,
    mut query: Query<(&Player, &mut Transform, Option<&mut Weapon>)>,
) {
    /**/

    for (player, mut transform, weapon) in &mut query.iter() {
        /**/
        if let Some(mut w) = weapon {
            w.fired = weapon_fired || w.fired;
        }
    }
}

fn weapons (
    time: Res<Time>,
    materials: Res<MaterialHandles>,
    mut query: Query<(&mut Weapon, &Transform)>,
) {
    for (mut weapon, transform) in &mut query.iter() {
        weapon.cooldown.tick(time.delta_seconds);
        if weapon.cooldown.finished && weapon.fired {
            println!("I'm firin' mah lazer");
            weapon.fired = false;
            weapon.cooldown.reset();
        }
    }
}
```
There are three new things introduced here: `Timer`, `Option<Component>` and `stage`s.
`Timer` is a helper struct provided by bevy that doesn't do much but keep track of elapsed time and whether or not it is more than the specified length. This is extremely useful for weapon cooldowns.
Optional components let us set a component as optional within the query. This lets us support things in the future (if we decide to make a player without weapons) 
`stage`s let us control when bevy executes each system. By default, all systems are in `stage::UPDATE`, so by placing our weapon system in `stage::POST_UPDATE`, we can fire our weapon the same "frame" as the button was pressed.

UPDATE: Do not follow the stage code above, adding the weapons system to the post_update stage causes some weird stalling behavior the first time it is fired. Just use add_system as normal.

# Lasers, lasers, lasers!

Obviously, we want to do more than just print out a message when we fire our laser, so let's add in our lasers. But first, let's talk about `Assets`. `Assets<T>` is basically a `HashMap<Handle<T>, T>`. When we run `materials.add()`, we are returned a new handle every time. If we do this for the lasers, every time we want to shoot we'd have to load in the texture again. To avoid this, we have to store our Handles as a resource.

### *`main.rs`*
```rs
struct MaterialHandles(Vec<Handle<ColorMaterial>>);

fn setup(
    /**/
) -> {
    let laser_texture_handle = asset_server.load("assets/textures/laserBlue01.png").unwrap();
    commands
        .insert_resource(MaterialHandles(vec![materials.add(laser_texture_handle.into())]))
}

```
Now let's add the code to insert our lasers into the World. We need to access `Commands`, a thread-safe queue of operations on the ECS World in our `weapons` system, just like we did in our startup system.

### *`main.rs`*
```rs
struct Laser {
    speed: f32,
}

fn weapons (
    mut commands: Commands, /* this must be the fist argument for bevy to recognize this as a system */
    time: Res<Time>,
    materials: Res<MaterialHandles>,
    mut query: Query<(&mut Weapon, &Transform)>,
) {
    for (mut weapon, transform) in &mut query.iter() {
        weapon.cooldown.tick(time.delta_seconds);
        if weapon.cooldown.finished && weapon.fired {
            commands
                .spawn(SpriteComponents {
                    material: materials.0[weapon.material_id],
                    transform: Transform::from_translation(weapon.offset + transform.translation()),
                    ..Default::default()
                })
                .with(Laser {
                    speed: 1000.0
                });
            weapon.fired = false;
            weapon.cooldown.reset();
        }
    }
}
```

Finally, we can add a movement system for lasers.

### *`main.rs`*
```rs
fn main() {
    /**/
        .add_system(player_control.system())
        .add_system(laser_move.system())
        .add_system_to_stage(stage::POST_UPDATE, weapons.system())
}

fn laser_move(
    time: Res<Time>,
    mut query: Query<(&Laser, &mut Transform)>
) {
    for (laser, mut transform) in &mut query.iter() {
        transform.translate(Vec3::new(0.0, laser.speed * time.delta_seconds, 0.0) )
    }
}
```

Aaaand we're done! Thanks for following along! You can find the code for this tutorial here: 

{% github EthanYidong/learn-bevy %}

In the next article (WIP), we'll add in our enemies.