创建一个精灵

说了这么多,没有能创建一个可以操作的精灵更能直观感受 Bevy 的开发流程了。
接下来我们就尝试开发一个可以通过键盘控制的坦克。

我们先描述下需求:

  1. 在屏幕上绘制一个坦克
  2. 通过键盘控制坦克移动
  3. 坦克可以发射子弹

本节关注于第一个需求,即绘制一个坦克。

上一节,我们探讨了 ECS 模型,并了解了一些可以在 System 函数中声明的参数类型,包括 Commands, Query 等,接下来要再介绍几种:

Res 和 ResMut

Res 即 Resource,中文翻译为资源,在 bevy 中可以用来存储全局单例数据实体。我们可以在声明 App 时插入一个 Resource,然后在 System 中通过 Res 获取该实例(ResMut 获取该实例可变引用):

use bevy::prelude::*;

struct Goals(usize);

fn add_goals_system(mut goals: ResMut<Goals>) {
    goals.0 += 1;
}

fn read_goals_system(goals: Res<Goals>) {
    println!("{}", goals.0);
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .insert_resource(Goals(0))
        .add_system(add_goals_system.system())
        .add_system(read_goals_system.system())
        .run();
}

值得注意的是 bevy 内置了很多 Resource ,其中就包括 WindowDescriptor,可以通过它配置默认窗口。 比如我们想控制窗口是 500 * 500 的大小,就可以这么处理:

fn main() {
	 App::build()
        .insert_resource(WindowDescriptor {
            width: 500.,
            height: 500.,
            ..Default::default()
        })
        .add_plugins(DefaultPlugins)
        .run();
}

注意,WindowDescriptor 需要在 DefaultPlugins 之前,因为 DefaultPlugins 中有一个 WindowPlugin 会读取已经声明的 WindowDescriptor,如果没有则自行创建一个。因此如果我们自定义的 WindowDescriptor 放到后面的话就会错过被 WindowPlugin 读取的机会。

Assets 和 AssetServer

Assets 和 AssetServer 都是 bevy 内置的 Resource,用来处理一些特定的资源,包括图片,视频,音频,字体等。

先来看看 Assets 的使用。

use bevy::prelude::*;

fn setup_system(mut commands: Commands) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());
}

fn add_assets_system(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
    commands.spawn_bundle(SpriteBundle {
        material: materials.add(ColorMaterial::from(Color::GOLD)),
        sprite: Sprite::new(Vec2::new(10., 10.)),
        ..Default::default()
    });
}

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup_system.system())
        .add_startup_system(add_assets_system.system())
        .run();
}

上面的例子可以在窗口中心绘制一个 10 * 10 大小的金色方块。

其中有几点需要注意:

  1. setup_system 中我们添加了一个正交投影相机,否则无法显示
  2. add_assets_system 中的 SpriteBundle 是 bevy 内置的一个生成精灵的一组 Component 集合,包含了贴图(material),大小(sprite)以及变换(transform)等等。
  3. 生成的精灵使用的贴图是用 Assets<ColorMaterial> 来生成的。

再来看看 AssetServer,AssetServer 屏蔽了资源加载过程,对于同一个资源无论是加载中还是加载完成都会返回一个相同的 Handle,开发人员只需使用该 Handle 即可,无需关心是否加载完成。

use bevy::prelude::*;

fn setup_system(mut commands: Commands) {
    commands.spawn_bundle(OrthographicCameraBundle::new_2d());
}

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

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup_system.system())
        .add_startup_system(add_assets_system.system())
        .run();
}

该例子可以在屏幕中央绘制一个 50 * 50 的坦克。

其中有几点需要注意:

  1. asset_server.load("images/tank.png") 会加载 <project_folder>/assets/images/tank.png
  2. asset_server.load 返回类型是 Handle<Texture> 继续调用 into 可以转换成 ColorMaterial 类型
  3. 用到的图片资源:tank.png

最终结果

结合本节所讲,可以轻松完成一个坦克的绘制:

use bevy::prelude::*;

struct Tank;

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 main() {
    App::build()
        .insert_resource(WindowDescriptor {
            height: 500.0,
            width: 500.0,
            ..Default::default()
        })
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup_system.system())
        .run();
}

tank