动态创建与销毁精灵

本节我们来实现最后一个需求,即坦克发射子弹。

我们先明确下需求:

  1. 键盘按下 Space 键,坦克发射子弹
  2. 子弹飞行一定距离后需要被销毁

发射子弹

子弹我们可以定义一个 叫做 Bullet 的 Component,因为涉及到需要销毁子弹,所以我们需要记录子弹飞行了多少距离,或者子弹剩余多少距离可以继续飞,以此来判断是否需要将其销毁。因此我们的子弹定义如下:


#![allow(unused)]
fn main() {
struct Bullet {
	range: f32,
}
}

其中 range 表示子弹剩余的可飞行距离。

接下来就是实现按键 Space 创建子弹:


#![allow(unused)]
fn main() {
fn tank_fire_system(
	mut commands: Commands,
    mut query: Query<Tank>,
    keyboard_input: Res<Input<KeyCode>>,
) {
    for mut tank in query.iter_mut() {
        if keyboard_input.pressed(KeyCode::Space) {
			commands
                .spawn_bundle(SpriteBundle {
                    material: materials.add(ColorMaterial::from(Color::GOLD).into()),
                    sprite: Sprite::new(Vec2::new(10., 10.)),
                    transform: transform.clone(),
                    ..Default::default()
                })
                .insert(Bullet { range: 250. });
        }
    }
}
}

这里面值得注意的是:

  1. spawn_bundle 时将坦克的 transform 值直接拷贝了一份,因为我们默认子弹初始位置和方向是和坦克一致的。

让子弹飞一会


#![allow(unused)]
fn main() {
fn bullet_movement_system(
    mut commands: Commands,
    mut query: Query<(Entity, &mut Bullet, &mut Transform)>,
) {
    for (ent, mut bullet, mut transform) in query.iter_mut() {
        if bullet.range > 0.0 {
            let range = 1.;
            bullet.range -= range;
            r#move(&mut transform, range);
        } else {
            commands.entity(ent).despawn();
        }
    }
}
}

这里面值得注意的是:

  1. Query 里加了一个 Entity 类型,通过它可以获取该 Entity
  2. commands.entity(ent).despawn() 会将该 Entity 销毁

最终结果

use bevy::prelude::*;

struct Tank;
struct Bullet {
    range: f32,
}

fn setup_system(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());
    commands
        .spawn_bundle(SpriteBundle {
            material: materials.add(asset_server.load("images/tank.png").into()),
            sprite: Sprite::new(Vec2::new(50., 50.)),
            ..Default::default()
        })
        .insert(Tank);
}

fn tank_movement_system(
    mut query: Query<&mut Transform, With<Tank>>,
    keyboard_input: Res<Input<KeyCode>>,
) {
    for mut transform in query.iter_mut() {
        if keyboard_input.pressed(KeyCode::W) {
            r#move(&mut transform, 0.5);
        }
        if keyboard_input.pressed(KeyCode::S) {
            r#move(&mut transform, -0.5);
        }
        if keyboard_input.pressed(KeyCode::A) {
            transform.rotation *= Quat::from_rotation_z(std::f32::consts::PI / 180.);
        }
        if keyboard_input.pressed(KeyCode::D) {
            transform.rotation *= Quat::from_rotation_z(-std::f32::consts::PI / 180.);
        }
    }
}

fn tank_fire_system(
    mut commands: Commands,
    query: Query<&Transform, With<Tank>>,
    keyboard_input: Res<Input<KeyCode>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    for transform in query.iter() {
        if keyboard_input.pressed(KeyCode::Space) {
            commands
                .spawn_bundle(SpriteBundle {
                    material: materials.add(ColorMaterial::from(Color::GOLD).into()),
                    sprite: Sprite::new(Vec2::new(10., 10.)),
                    transform: transform.clone(),
                    ..Default::default()
                })
                .insert(Bullet { range: 250. });
        }
    }
}

fn bullet_movement_system(
    mut commands: Commands,
    mut query: Query<(Entity, &mut Bullet, &mut Transform)>,
) {
    for (ent, mut bullet, mut transform) in query.iter_mut() {
        if bullet.range > 0.0 {
            let range = 1.;
            bullet.range -= range;
            r#move(&mut transform, range);
        } else {
            commands.entity(ent).despawn();
        }
    }
}

pub fn r#move(transform: &mut Transform, px: f32) {
    let (axis, angle) = transform.rotation.to_axis_angle();
    let angle = angle * &axis.z.signum();
    transform.translation.y += angle.cos() * &px;
    transform.translation.x -= angle.sin() * &px;
}

fn main() {
    App::build()
        .insert_resource(WindowDescriptor {
            height: 500.0,
            width: 500.0,
            ..Default::default()
        })
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup_system.system())
        .add_system(tank_movement_system.system())
        .add_system(tank_fire_system.system())
        .add_system(bullet_movement_system.system())
        .run();
}

最终效果如下: