Bevy Component

253 阅读3分钟

movikit-software-module-1344x756.webp

ECS中,我们可以想象Component就是数据库表中的项,而Entity,则是一行记录。

Component使我们能够将数据附加到Entity中。

实际上,一个单独的Entity并没有什么用,它只是代表了一行记录的Id,也就是游戏世界中一些创建的东西的Id

那么,哪些是Component呢?

  • 游戏窗口,也就是Window,就是一个Component
  • 相机Camera也是一个Component

而如果我们需要修改一个Entity的值,我们就需要通过System来查询到这条记录,然后修改。

定义组件

通过派生宏的方式,来定义组件,这里我们定义几个组件:

//枚举可以是Component
#[derive(Component)]
enum Ship {
    Boat,
    Clipper,
}

//元组可以是Component
#[derive(Component)]
struct Health(f32);

//结构体可以是Component
#[derive(Component)]
struct Position {
    x: f32,
    y: f32,
}

//一个什么数据都没有的结构体,也可以是Component
#[derive(Component)]
struct Player;

#[derive(Component)]
struct Enemy;

关于最后两个ComponentPlayerEnemy,是Bevy中一种典型的用法,即标记。他会标记我们添加的Entity方便后续我们查询到该Entity,并访问相关的Component

使用Component

我们先看看,如果在Bevy中,使用这些Component

//定义一个生成这些Component的System
fn setup_entities(mut commands: Commands) {
   
    commands.spawn((Health(100.0), Player, Position { x: 100f32, y: 200f32 }));
    
    commands.spawn((Health(72.0), Enemy, Position { x: 20f32, y: 60f32 }));
}
//一定不要忘了添加到App
.add_systems(Startup, setup_entities)

在创建这些Component时,有两个点需要注意:

  • 每个Entity生成的组件,类型不能重复(commands.spawn((Health(100.0), Health(40.0)) //不可以这样)。
  • 如果想在后续再出入一条同类型,那么之前类型的数据就会覆盖。

行与列

现在,我们用行与列的概念想象一下上述代码:

Entity IdHealthPlayerPositionEnemy
1100.0(100, 200)
272(20,60)

Bevy中,Component被存储为许多单独的连续数组。

每个数组都有一种类型的组件。Entity实际上是每个不同数组中其所有组件的索引。

例如,我们有一个HealthPosition组件的数组:

Health: [Health(100.0), Health(72.0)]
Position: [Position { x: 100f32, y: 200f32 }, Position { x: 20f32, y: 60f32 }]

在内存中,它会存储成:

Entity 0[HealthArray[0], PositionArray[0]]
Entity 1[HealthArray[1], PositionArray[1]]

如果Entity的索引是0,那么我们就能很快找到数据:Health(100.0)Position { x: 100f32, y: 200f32 }

这就是为什么我们只能拥有每种类型的一个组件的原因。我们只能在插槽中找到实体索引相匹配的一个组件。

这样做就是为了提高性能——尽量从CPU的缓存中获取数据。

必须组件

我们稍微对Player做一下修改:

#[derive(Component)]
#[require(Health)] //声明了一个必须组件
struct Player;

当我们创建一个组件的时候,如果我们需要有另外一个或者多个组件必须一起创建,我们可以使用#[require(xxx,xxx)]来声明必须组件。

如果在创建的时候已经有了该必须组件,那么Bevy会自动使用这个组件,如果没有,则会自动创建一个必须组件。所以,每一个必须组件必须实现Default trait

#[derive(Component)]
struct Health(f32);

impl Default for Health  {
    fn default() -> Self {
        Self(100.0)
    }
}

当我们添加到Bevy中时:

commands.spawn((Player, Position { x: 100f32, y: 200f32 }));

Bevy会自动创建一个Health(100.0)的组件,生成一条记录。

总结

组件就像游戏世界的乐高积木,每个零件都让实体更鲜活!试着用不同组件组合出飞船、英雄或魔法道具,Bevy的灵活架构会让创意轻松落地~

欢迎在评论区留言。