Bevy ECS

与其它大部分游戏引擎不同,Bevy 采用了一种名为ECS 的编程范式,ECS 将数据和行为分离。Bevy将存储我们的所有数据,并为我们管理所有独立的功能单元。代码会在适当的时候运行。

那么,ECS代表什么意思?

缩写 全称
E Entity,实体
C Component,组件
S System,系统

如果我们有面向对象语言的基础,那么可以这么理解ECS:对象实例,类,函数。

好的,我们从两个视角(面向对象,面向ECS)来看看游戏的运行逻辑。

面向对象视角

如果我们从面向对象视角看游戏的运行逻辑,我们可能会这样实现。

定义对象:

rust 复制代码
struct Sprite {
    texture: String,
    position: Vec2,
}

定义对象的行为:

rust 复制代码
impl Sprite {

    fn new(texture: String, position: Vec2) -> Self {
        Self { texture, position }
    }

    fn set_position(&mut self, position: Vec2) {
        self.position = position;
    }
}

运行对象相关的逻辑(创建示例,设置行为):

rust 复制代码
fn main() {
    let mut sprite = Sprite::new("boy".to_string(), Vec2::ZERO);
    sprite.set_position(Vec2::new(400.0, 200.0));
}

这完全是面向对象的做法,首先定义对象的字段,然后定义对象支持的行为,然后在合适的时机运行对象的逻辑。

Bevy ,用了完全不一样的做法,即ECS

ECS视角

定义对象:

rust 复制代码
pub struct Sprite {
    pub image: Handle<Image>,
    //other code
}
rust 复制代码
pub struct Transform {
    pub translation: Vec3,
    // other code
}

创建对象:

rust 复制代码
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn((
        Sprite::from_image(asset_server.load("bevy_bird_dark.png")),
        Transform::from_xyz(200.0, 200.0, 0.0),
    ));
}

定义对象行为:

rust 复制代码
fn moving_sprite(mut sprite_query: Option<Single<(&mut Transform)>>) {
    if let Some(mut sprite) = sprite_query {
        sprite.0.translation.x = 0.0;
        sprite.0.translation.y = 0.0;
    }
}

嗯~~~,这只是代码的一部分,从目前的代码看,和面向对象的视角有很多不一样。现在,让我们深入一下,看看Bevy如何运行一个简单的游戏逻辑。

ECS的游戏逻辑

从简单开始

rust 复制代码
use bevy::prelude::*;

fn main() {
   
    App::new()
        .add_plugins(DefaultPlugins)
        .run();
}

上一篇文章中,我们已经介绍了这段代码的功能,那就是只显示一个窗口。

创建精灵

rust 复制代码
#[derive(Component)]
struct SpriteMark;

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2d);
    commands.spawn((
        Sprite::from_image(asset_server.load("bevy_bird_dark.png")),
        Transform::from_xyz(200.0, 200.0, 0.0),
        SpriteMark,
    ));
}
    

#[derive(Component)]是一个声明,这个声明表示我们创建的struct是一个Component ,例如上面的代码,我们创建了一个SpriteMarkComponent ,这个Component 有什么用?大家看代码实现会发现,这个SpriteMark没有任何数据。一般情况下,这种Component 我们称为标记组件,标记组件的唯一作用,就是方便System 去查找我们的Entity,这个以后再讲。

fn setup(mut commands: Commands, asset_server: Res<AssetServer>)该函数就一个作用,创建Entity ,我们看下实际的创建逻辑。commands.spawn(Camera2d);生成一个2D相机,游戏世界中,唯一不可缺少的组件就是相机,因为有了相机,我们才能看见游戏世界。

rust 复制代码
commands.spawn((
        Sprite::from_image(asset_server.load("bevy_bird_dark.png")),
        Transform::from_xyz(200.0, 200.0, 0.0),
        SpriteMark,
));

上述代码,便是生成我们的精灵实体。第二行代码生成的是精灵纹理,可以看到加载了一个文件作为精灵的纹理,第三行代码定义了精灵的位置,第四行,创建了我们精灵的标记。

添加到系统

rust 复制代码
App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(Startup, setup)
    .run();
}

之前我们定义的setup函数,需要添加到Bevy 的运行逻辑中。此处,我们使用了add_systems(Startup, setup),把setup添加到了游戏的运行逻辑中。Starup会在游戏启动的时候运行一次,也就是说,setup函数,会在我们游戏运行的时候,初始化阶段,执行一次。

Startup,在Bevy 中,叫做调度。而setup函数,当我们使用add_systems时候,它就变成了ECS 中的System,也就是系统。

Bevy 中最常见的一个错误,就是定义了一个函数或者运行逻辑,而没有添加到System中。

此时,我们的代码已经可以运行了,我们看看效果。

哦,对了,忘记告诉各位要在项目的某个目录下,放入你喜欢的资源。

再复杂一点

如果仅仅在初始位置放置一个精灵,这并不吸引人。那么我们稍微添加一个逻辑,当点击键盘M键的时候,移动一下精灵。

rust 复制代码
fn moving_sprite(mut sprite_query: Option<Single<(&mut Transform, &SpriteMark)>>) {
    if let Some(mut sprite) = sprite_query {
        sprite.0.translation.x = 0.0;
        sprite.0.translation.y = 0.0;
    }
}

上述代码唯一值得研究的地方就是函数参数, mut sprite_query: Option<Single<(&mut Transform, &SpriteMark)>>,我们定义了一个单项查询Option<Single<>>,在Bevy 中,获取Entity 的方式就是通过查询,你可以理解为查找满足某个条件的Entity 。这个时候,我们终于看到了SpriteMark作用,如果我们在编写查询条件的时候,有点点困难,那么我们就可以定义个Component ,专门作为Entity 的标记。这个参数翻译过来就是:帮我查询一个,既带有Transform也带有SpriteMarkEntity 。因为SpriteMark的作用,所以我们查询到的Entity,就是我们之前的创建的精灵。

好的,接下来函数实现,就是移动精灵的位置。

最后,别忘了,添加到Bevy的系统:

rust 复制代码
App::new()
    //other code
    .add_systems(Update, moving_sprite.run_if(input_pressed(KeyCode::KeyM)))
    //other code

.add_systems(Update, moving_sprite.run_if(input_pressed(KeyCode::KeyM))),这次,我们换了一个不一样的调度------Update,该调度和游戏的帧率保持一致,也就是在游戏运行期间,会一直运行,游戏如果是一秒钟60帧,那么该调度就调度System一秒钟60次。moving_sprite.run_if(input_pressed(KeyCode::KeyM))moving_sprite的实际执行,会在按下键盘M键的时候执行。

OK,现在,我们的游戏没有那么无聊了,在游戏运行之后,点击M键,会移动一下了。

总结

好的,我们已经了解基本的Bevy 运行方式,即ECS 。可能目前还不是那么深入,不过没关系,后续随着文章的更新,对ECS会越来越深入的。

相关推荐
m0_748240544 小时前
数据库操作与数据管理——Rust 与 SQLite 的集成
数据库·rust·sqlite
~kiss~5 小时前
Rust~二刷异步逻辑
开发语言·后端·rust
SomeB1oody5 小时前
【Rust中级教程】2.7. API设计原则之灵活性(flexible) Pt.3:借用 vs. 拥有、`Cow`类型、可失败和阻塞的析构函数及解决办法
开发语言·后端·性能优化·rust
web_155342746566 小时前
性能巅峰对决:Rust vs C++ —— 速度、安全与权衡的艺术
c++·算法·rust
weixin_502539856 小时前
rust学习笔记5-所有权机制
笔记·学习·rust
学习两年半的Javaer6 小时前
Rust语言基础知识详解【一】
开发语言·rust
全栈开发圈6 小时前
新书速览|Rust汽车电子开发实践
开发语言·rust·汽车
m0_748238276 小时前
项目升级Sass版本或升级Element Plus版本遇到的问题
前端·rust·sass
~kiss~6 小时前
Rust学习~tokio简介
开发语言·学习·rust