Please enable Javascript to view the contents

[Bevy 源码解析] ECS 基础

 ·  ☕ 3 分钟

前言

    ECS 系统数据的保存方式分为两种:
一种是像数据库一样,数据表结构作为 Entity 每个字段作为 Component,这种结构迭代数据比较快(要不人家数据库也不会这样设计了)。但是如果想修改 Entity 结构就很困难。就像不能随便动数据库表结构一样。

还有一种是将相同的 Component 数据保存在一个数组中。然后拿 Entity 的 Id 作为索引。这种方式迭代就比较低效,因为迭代每次要访问的地方都是随机的。但是如果想动 Entity 的结构就比较容易。因为都是按列存的,对已经存在的数据没影响。

作为游戏引擎来讲,我们把 Entity 定义好很少会在运行时动态修改它,所以一般都会采用第一种方案。

在 Bevy 的 ECS V2 中提供了两种选项,默认是我们之前聊的第一种。可以手动修改成第二种。

1
2
3
world.register_component(
    ComponentDescriptor:🆕:<MyComponent>(StorageType::SparseSet)
).unwrap();

Component/Entity/System

可以理解成数据库的字段就是 Component,一个数据库条目就是一个 Entity。只不过 Component 是一种功能,而并不是单纯的数据类型。

Entity 是一些 Component 的集合,提供统一的 EntityId

System 具体执行逻辑的地方

Resource

全局共享的数据,例如游戏状态等等

Stage

Stage 的概念是和 System 的运行息息相关的。它直接影响 System 的执行顺序。

库中预定义的 Stage 都在 bevy_app/src.lib.rs 的 CoreStage 中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum CoreStage {
    /// Runs once at the beginning of the app.
    Startup,
    /// Name of app stage that runs before all other app stages
    First,
    /// Name of app stage that runs before EVENT
    PreEvent,
    /// Name of app stage that updates events. Runs before UPDATE
    Event,
    /// Name of app stage responsible for performing setup before an update. Runs before UPDATE.
    PreUpdate,
    /// Name of app stage responsible for doing most app logic. Systems should be registered here by default.
    Update,
    /// Name of app stage responsible for processing the results of UPDATE. Runs after UPDATE.
    PostUpdate,
    /// Name of app stage that runs after all other app stages
    Last,
}
/// The names of the default App startup stages
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum StartupStage {
    /// Name of app stage that runs once before the startup stage
    PreStartup,
    /// Name of app stage that runs once when an app starts up
    Startup,
    /// Name of app stage that runs once after the startup stage
    PostStartup,
}

除了预定义的这些 Stage 我们还可以基于此定义自己的 Stage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        .add_stage_before(
            CoreStage::Update,
            MyStage::BeforeRound,
            SystemStage::parallel(),
        )
        .add_stage_after(
            CoreStage::Update,
            MyStage::AfterRound,
            SystemStage::parallel(),
        )

自由度应该算是拉满了吧。

我们默认使用的 add_system 其实就是默认把system 插在 CoreStage::Update 处。

add_startup_system 其实就是把 system 默认插入在 StartupStage::Startup 处。

add_statge 系列函数可以指定 stage 的执行模式 SystemStage::parallel

如果就是单纯的想区分两个 System 的顺序,不想在全局再添加过多的 stage 可以再 system() 后调用 label 和 after before 等函数进行指定。

Resource 的是添加

insert_resource 可以将我们构造好的对象直接添加进引擎

init_resource::<T>() 会在 FromWorld 中调用类型的 Default trait 的 default() 方法。其实默认行为就是直接利用 Default 初始化默认值。

System 中对数据的修改:

如果想 World 实例中做例如生成新玩家的操作需要用到 Commands,它是一个缓存队列,类似 DirectX 的命令队列,它只保证该操作在未来会执行,但不是立即执行。

如果想对 World 实例做立即生效的修改需要用 thread_local 的 system 直接拿到 world:&mut World。

System 本地变量

System 可以有用 Local<State> 包裹的本地变量,这样,对于相同类型的System 的不同实例也可以有自己独立的状态。
还可以通过 config 手动配置:

1
2
3
4
fn foo(value: Local<usize>) {    
}

app.add_system(foo.system().config(|c| c.0 = Some(10)));
分享

saberuster
作者
saberuster
一个喜欢编程和折腾的追风少年