Bevy中的所有应用程序逻辑都使用实体(Entity)组件(Component)系统(System)范式, 通常简称为ECS.
ECS是一种软件模式, 包括将程序分解为实体(Entity), 组件(Component)和系统(System).
实体(Entity) 是指向一群 组件(Component) 的唯一 事物(things), 然后使用 系统(System) 处理其过程.
例如, 一个实体可能有位置(Position)
和速度(Velocity)
组件, 而另一个实体可能有位置(Position)
和UI
组件. 系统是在一组特定组件上运行的逻辑. 你可能有一个运行在所有带有位置(Position)
和速度(Velocity)
组件的实体上的移动
系统.
ECS模式鼓励清晰, (解耦)分离的设计, 强制使你将数据和逻辑分解为核心组件. 通过优化内存访问模式并简化并行性, 有助于使你的代码更快.
Bevy ECS是Bevy对ECS模式的实现. 不像其他需要复杂的生存期, 特征, 构建器模式或者宏的Rust ECS的实现, Bevy ECS对所有这些概念使用常规的Rust数据类型:
struct Position { x: f32, y: f32 }
fn print_position_system(query: Query<&Transform>) {
for transform in query.iter() {
println!("position: {:?}", transform.translation);
}
}
struct Entity(u64);
现在让我们在实践中看看它是如何工作的!
拷贝下面的函数到你的main.rs
文件中:
fn hello_world() {
println!("hello world!");
}
这将是我们第一个系统. 剩下的步骤就是把它加到我们的App中!
fn main() {
App::build()
.add_system(hello_world.system())
.run();
}
注意hello_world.system()
函数的调用. 这是一种特征扩展方法
, 可将hello_world
函数转变成System
类型.
调用add_system()
方法把系统加到App的Schedule
, 这个我们稍后再介绍.
现在我们再一次执行cargo run
. 你将在命令行中看见hello world!
的输出.
能向整个世界打招呼当然很好, 但是如果我们只想问候特定的人呢? 在ESC中, 通常会将人建模成实体, 并使用一些组件来定义它们. 让我们从一个Person
的组件开始.
将这个结构体添加到main.rs
:
struct Person;
但是, 如果我们希望人有一个名字? 在传统的设计中, 我们可能只是添加一个name: String
的字段到Person
中. 但其他实体也会拥有名字! 举个例子, 狗可能也应该有一个名字. 将数据类型分解成小块, 以鼓励代码重用通常更有意义. 因此, 让名字(Name)
成为独立的组件.
struct Name(String);
我们可以通过startup system
将People
加到我们的世界(World
).
startup system
就像普通的系统(System)一样, 但它们只运行一次, 而且是在其他系统之前被调用.
当我们的应用启动时, 让我们通过Commands
生产大量的实体到我们的世界(World
)中.
fn add_people(commands: &mut Commands) {
commands
.spawn((Person, Name("Elaina Proctor".to_string())))
.spawn((Person, Name("Renzo Hume".to_string())))
.spawn((Person, Name("Zayna Nieves".to_string())));
}
像这样注册启动系统(startup system):
fn main() {
App::build()
.add_startup_system(add_people.system())
.add_system(hello_world.system())
.run();
}
我们现在可以运行这个应用, add_people
系统将会最先执行, 然后是hello_world
. 但是我们新的people
仍然无事可做! 让我们创建一个系统, 让我们的新公民向我们的世界(World
)打招呼:
fn greet_people(query: Query<&Name, With<Person>>) {
for name in query.iter() {
println!("hello {}!", name.0);
}
}
我们传递给system function
的参数定了系统运行的数据. 在本例中, greet_people
将在所有带有Person
和Name
组件的实体上运行.
你可以将上面的查询解释为: "遍历每一个拥有Person
组件的Name
组件"
在我们的App中注册它:
fn main() {
App::build()
.add_startup_system(add_people.system())
.add_system(hello_world.system())
.add_system(greet_people.system())
.run();
}
现在我们运行我们的App, 将会得到以下结果:
hello world!
hello Elaina Proctor!
hello Renzo Hume!
hello Zayna Nieves!
棒棒哒!
备注: hello world!
可能出现的顺序和上面显示的不同. 这是因为系统在没有共享依赖关系的情况下, 默认是并行的.