The Bevy Book - 2.3 ECS
ECS
Bevy 中的所有应用逻辑都使用Entity Component System 范型,该范例通常缩写为 ECS。ECS 是一种软件模式,涉及将程序分解为实体、组件和系统。实体是唯一的“事物”,它们被分配了组件组,然后使用系统进行处理。
例如,一个实体可能具有 Position和Velocity组件,而另一个实体可能具有 Position和UI组件。系统在一组特定的组件类型的集上运行逻辑。您可能有一个movement系统,运行所有带有Position和Velocity组件的实体。
ECS 模式通过强制您将应用数据和逻辑分解为其核心组件来鼓励干净、解耦的设计。它还有助于通过优化内存访问模式和简化并行性来加快代码速度。
Bevy ECS
Bevy ECS 是 Bevy 对 ECS 模式的实现。与其他通常需要复杂生命周期、特征、构建器模式或宏的 Rust ECS 实现不同,Bevy ECS 使用通用Rust 数据类型来实现所有这些概念:
- 组件 Components:实现
Component的Rust结构体
#[derive(Component)] struct Position { x: f32, y: f32 }
- 系统 Systems:标准Rust函数
fn print_position_system(query: Query<&Transform>) { for transform in query.iter() { println!("position: {:?}", transform.translation); } }
- 实体 Entities:包含integer的简单类型
struct Entity(u64);
现在让我们看看这在实践中是如何工作的!
您的第一个系统
将以下函数粘贴到文件中:main.rs
fn hello_world() {
println!("hello world!");
}
这将是我们的第一个系统。剩下的唯一步骤是将其添加到我们的应用程序中!
fn main() {
App::new()
.add_system(hello_world)
.run();
}
add_system()
函数将系统添加到应用的Schedule
中,但我们稍后会对此进行更多介绍。
现在使用 cargo run 再次运行您的app。您应该会在终端中看到hello world!打印。
您的第一个组件
问候整个世界是伟大的,但是如果我们想问候特定的人怎么办?在 ECS 中,您通常将人员建模为实体,其中包含一组定义他们的组件。让我们从一个组件Person
开始。
将此结构体添加到 :main.rs
#[derive(Component)]
struct Person;
但是,如果我们希望我们的人有一个名字呢?在更传统的设计中,我们可能只是在Person
上钉上name: String
。但其他实体也可能有名称!例如,狗可能也应该有一个名字。通常,将数据类型分解为多块以鼓励代码重用是有意义的。因此,让自己的组件上加上 Name
:
#[derive(Component)]
struct Name(String);
然后,我们可以使用"startup system"添加People
到我们的World
。startup system就像普通系统一样,但它在所有其他系统之前只运行一次,就在我们的应用程序启动时。让我们使用Commands
将一些实体生成到我们的World
:
fn add_people(mut commands: Commands) {
commands.spawn().insert(Person).insert(Name("Elaina Proctor".to_string()));
commands.spawn().insert(Person).insert(Name("Renzo Hume".to_string()));
commands.spawn().insert(Person).insert(Name("Zayna Nieves".to_string()));
}
现在像这样注册startup system:
fn main() {
App::new()
.add_startup_system(add_people)
.add_system(hello_world)
.run();
}
我们现在可以运行此应用程序,add_people
系统将首先运行,然后是 hello_world
.但是我们的新员工还没有任何事情可做!让我们建立一个系统,正确地迎接我们World
的新公民:
fn greet_people(query: Query<&Name, With<Person>>) {
for name in query.iter() {
println!("hello {}!", name.0);
}
}
我们传递给 "system function" 的参数定义了系统运行的数据。在这种情况下,greet_people
将在具有Person
和Name
组件的所有实体上运行。
您可以将上面的查询解释为:“循环访问也具有人员组件的实体的每个名称组件”
现在,我们只需在我们的应用程序中注册系统:
fn main() {
App::new()
.add_startup_system(add_people)
.add_system(hello_world)
.add_system(greet_people)
.run();
}
运行我们的应用将产生以下输出:
hello world!
hello Elaina Proctor!
hello Renzo Hume!
hello Zayna Nieves!
奇妙!
快速说明:“hello world!”的显示顺序可能与上面的顺序不同。这是因为默认情况下,系统尽可能并行运行。