Bevy 引擎还处于一个非常早期的阶段,本文的内容很容易过时。若有商业开发需求 Godot 会更为合适。代码基于bevy v0.11.2
默认已通过 rustup 安装 rust 环境
在 Hello, ECS!(三)中的例子1模拟了一个这样的事情:
- 在某个概率条件下生成一个 Entity
- 输出所有 Entity 当前的存活时间
- 销毁所有 存活时间 > 2 的 Entity
在上面的例子里,比起Hello, ECS!(二)多出了不少概念,本期来解析一下
Resource
在 Hello, ECS!(二)我们知道了 ECS 数据是存放于 Component 中的,但正如《守望先锋》游戏架构和网络同步的设计中描述
单例模式的使用非常普遍,我们整个游戏里的40%组件都是单例的。
《守望先锋》中采用了一个可以由 EntityAdmin(也就是 World)直接访问的单例匿名实体,由该实体负责单例数据的相关访问。
而这个匿名实体所持有的 Component 在 Bevy 里便称为 Resource。
与普通的 Component 不同,一般 System 需要用到 Component 数据是,注册到 System 的会是所有符合条件的 Component,但由于 Resource 的独立性,所以 System 使用 Resource 时将能直接访问到具体的数据而无需再经数据遍历(同时 System 对 Resource 也是仅通过类型进行指定)。
SystemSet
在 Hello, ECS!(二)我们知道了 ECS 中 System 的调度是由 Schedule 实现的。在 Hello, ECS!(三)的例子中,Bevy 提供了更详细的流程调度控制手段 - SystemSet。
SystemSet 用于标记各个执行阶段(Stage)。SystemSet 可以是结构体可以是枚举,可以灵活地选择合适的类型标记 Schedule 中的各个阶段。
Commands
在介绍 Commands 前有必要再更详细地介绍一下 Bevy 中的 Schedule。
所有的 System 都会被包含在某个 Stage 中。每帧,Bevy 会按顺序处理各个 Stage。在各个 Stage,Bevy 的调度算法(scheduling algorithm)能使用多核处理器的性能,并发地运行各个 System 达到更好的性能。
但并发处理下,数据的读写成为了不可回避的问题。
众所周知,在多线程的操作下读写操作需要满足一下几个规则。
- 同一时刻,允许多个对同一数据的读取
- 同一时刻,不允许多个对同一数据的写入
- 同一时刻,不允许读取与写入同时进行
尽管 Rust 有无谓并发
,但 Rust 中的数据读写仍然需要满足上述规则(或者说 Rust 通过mut
关键字实现了上述规则)。
在 ECS 下,System 与 Component 的关系是通过 World 与 Entity 索引建立的。考虑一下几个场景:
- 对 World 添加/删除 Entity
- 对特定 Entity 添加/删除 Component
这些对数据的修改都会导致数据的变动,而这些变动若在 System 并发执行时,将会有可能违反上述读写规则。
为了解决这个问题,Bevy 提供了 Commands 代替直接对 World 进行操作。
所以通过 Command 进行的操作不会立即生效,而是会被加入到队列中,等待执行时机。
未完
https://github.com/bevyengine/bevy/blob/main/crates/bevy_ecs/examples/change_detection.rs ↩︎