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将在具有PersonName 组件的所有实体上运行。

您可以将上面的查询解释为:“循环访问也具有人员组件的实体的每个名称组件”

现在,我们只需在我们的应用程序中注册系统:

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!”的显示顺序可能与上面的顺序不同。这是因为默认情况下,系统尽可能并行运行。

posted @ 2022-05-29 12:02  秦舒云  阅读(242)  评论(0编辑  收藏  举报