Bevy ECS 介绍
Bevy 作为一个数据驱动的游戏引擎, 其内部架构为 ECS(Entity-Component-System) 组件化设计模式。
为什么用ECS
- 面向数据: 功能由数据驱动 在ECS中,数据是核心。系统的功能和行为主要通过数据的组织和处理来实现,而不是通过复杂的对象层次结构。
- 清晰架构: 功能松耦合 / 防止深层次的继承嵌套 ECS通过将功能分解为独立的组件和系统,实现了功能之间的松耦合。这种方式避免了传统面向对象设计中常见的深层次继承结构,使得代码更加模块化和易于维护。
- 高性能:高度并行且缓存友好 ECS的设计使得数据可以高效地存储和访问,支持高度并行的处理。由于数据通常按类型连续存储,这有助于提高缓存命中率,从而提升性能。
ECS定义
- 实体(Entity):具有唯一 ID 的组件集合 实体是由多个组件组成的集合,并且每个实体都有一个唯一的标识符(ID)。实体本身没有具体的行为,其行为由所包含的组件和系统共同决定。
- 组件(Component):只是一个普通的 Rust 数据类型。通常作用于单一功能 组件是包含特定数据的简单结构体,每个组件负责一个特定的功能。例如,
Position
组件包含位置信息,Health
组件包含生命值信息。- 资源(Resource):共享的全局数据 资源是可以在整个系统中共享的数据,通常用于存储全局状态或配置信息。例如,资产存储可以用来管理游戏中的所有资源文件,事件系统可以用来处理游戏中的各种事件。
- 系统(System):在实体、组件和资源上运行逻辑 系统是负责执行具体逻辑的模块。系统会遍历具有特定组件的实体,并对这些实体进行操作。例如,移动系统可能会更新所有具有
Position
和Velocity
组件的实体的位置,伤害系统可能会处理所有具有Health
组件的实体的伤害计算。从概念上讲, 您可以将ECS和数据库和表格做类比。不同的数据类型(组件)就是像表的“列”,不同的组件组成了一行,
Entity
ID就像行号,它就是一个整数索引,可以让您查找特定的组件值。指向原始笔记的链接
[ Entity
] (ID)[ Transform
]Player
Enemy
[ Camera
]Health
… … 107 ✓ <translation>
<rotation>
<scale>
✓ ✓ 50.0
108 ✓ <translation>
<rotation>
<scale>
✓ ✓ 25.0
109 ✓ <translation>
<rotation>
<scale>
✓ <camera data>
110 ✓ <translation>
<rotation>
<scale>
✓ ✓ 10.0
111 ✓ <translation>
<rotation>
<scale>
✓ ✓ 25.0
…
Bevy Entity 结构
当我们打印/调试时, 经常看到Entity
的Display/Debug输出是这样的
enemy entity: 1v1
player entity: 3v1
为什么呢? 我们看一下Entity的源码
// 简化注释和代码
pub struct Entity {
index: u32,
generation: NonZeroU32,
}
可以看到Entity内部存储了index和generation两个值,但为什么要存储这两个值呢? 索引用一个usize不能解决吗? bevy在文档里提到了这里使用了分代索引设计,它是一种复合索引,由两部分组成:
- Index:一个唯一的标识符,用于快速查找对象。
- Generation:一个计数器,用于区分同一索引在不同时间点上引用的对象
问题来源
ECS架构会牵涉到大量的实体创建/销毁, 因此会实现回收id。如果只用了一个usize做索引,会带一个ABA的问题。
比如说:
创建A
, index为0。
创建B
, index为1。
A自己创建了B
的索引,标记index为1。
有人删除了B
,又创建了C
,index也为1。
此时A
用index=1去引用B
, 但实际上却会拿到C
解决方案
代际索引就是将Index
和Generation
合并起来组成了一个key。当我们删除Entity时,会将其Generation增加,这样原来的key和现在新的key不一致,从而解决ABA问题。
用文章里的图解释一下:
在数组中插入新元素会生成key
要获取值,我们只需要提供它们的key
我们通过传递 key 来删除 element,然后删除 key 索引处的值并增加生成
我们在第一个可用空间中插入顶点 C,返回一个新key,其生成与数组条目中的生成匹配,注意:返回的key具有 index: 0 而不是 1。
key的生成与条目的生成不匹配,这意味着该key现在指向无效数据,返回空结果
然而Key C 指向正确的数据C!
结论
Bevy ECS 使用了代际索引机制,解决了在对象频繁创建和销毁的情况下,高效管理和查找对象的问题。