发表于: 11/29/2023
有了上一篇的铺垫,在这一篇里我们将聚焦于复杂一些的逻辑。
本文将基于 Bevy 演示代码。
首先来思考一个“简单”的问题:实体的创建。
fn spawn_xxx(mut commands: Command){}
commands.spawn((
XXXComponent,
// ...
));
}
如上是一个简单创建实体的过程,spawn
方法可以接受一个组件组成的元组,其中的组件将被附加到创建的实体上。
很简单地,我们可以把所有用到的组件一股脑写上去。比如:
(Sprite, Name, Life, Movable, Player, Character, Transform {x: 0.0, y: 0.0, z: 0.0})
:::info 该示例仅用作展示一些可能的组件的类型,实际初始化的结构体包含更多的字段。 :::
但是这种简单的做法并不十分美好,这个spawn_xxx
的系统仅能生成一种实体,而且无从定制一些初始化参数:生成位置、不同阵营实体的区分、特定的强化等等。而且在省略的 Sprite 中,很显然我们还需要加载{Asset^资产},来让角色有面目以示人。
我们需要更多参数!
世界出现了参差,这意味这我们一定把一些隐藏的东西给忽略了。
容易发现,我们的需求实际是两部分:创建实体、实体的初始化。这在平常的范式中通常是实例化一个预制体,于是我们得到了这个实体的引用,在此之上进行我们需要的修改,就是初始化过程了。但在这里我们做不到这一点:spawn_xxx
的运行独立于其他系统,如果我们不想在不同的逻辑里插入一大堆重复的 spawn((...))
的话。
那么你一定想问了:你不是说系统可以作用到特定的实体的组件上吗,我们设计一个 “Initialize” 组件,只需要创建一个包含必要初始化信息的实体,让这个 spawn_xxx
成为 initialize_xxx
补充缺少的组件不就好了?
Bravo,这就是我们要做的:
fn spawner(mut commands: Command){
commands.spawn((Initialize {
position: ...
}, Team(1), ...))
}
fn initializer( // Entity 本质为一个自增id,Initialize是组件,查询时获取其引用,Character则是(组件)筛选条件,并不读取其数据
things_to_initialize: Query<(Entity, &Initialize), With<Character>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut commands: Command
) {
things_to_initialize.for_each(|(entity, initialize_infomation)|{
commands.entity(entity).insert((
MaterialMesh2dBundle {
mesh: meshes.add(shape::Circle::new(32.).into()).into(),
material: materials.add(ColorMaterial::from(Color::ALICE_BLUE)),
transform: Transform::from_translation(initialize_infomation.position),
..default()
},
...
)).remove::<Initialize>()
})
}
通过查询组件(Initialize, Character)
的组合,我们得到了需要初始化的实体,以一套通用的逻辑通过 insert
补上我们需要的组件,然后删除 Initialize
就完成了初始化,实体不再符合查询条件。
而当我们想要以一个通用的逻辑来生成一类不同的实体时,这样做更是必须的:
比如 “武器” 可以装载不同 “弹药” ,发射时就需要生成不同的子弹。
我们就需要一个 Bullet
组件来承载一些公共参数:速度、散布、伤害等等,每种弹药一个组件作为区分:霰弹(弹头数量)、冲锋枪弹、狙击步枪弹等等。
当武器开火时,发送一个事件到 spawner
,事件包含弹药等必要信息。
fn spawner(mut event: EventReader<BulletSpawnEvent>, ...) {
// ... 读取事件
commands.spawn((event.bullet, Initialize, event.bullet_type));
}
fn shotgun_initializer(shotgun_bullets: Query<(Entity, &Bullet, &BulletType, &Initialize)>, ...) { ... }
// ... 其他子弹初始化
:::info 由于 Bevy 目前 ^0.12.0 尚未有足够方便好用的预制体一类的功能,这或许也是目前最“舒服”的重复创建过程了。 :::