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会越来越深入的。

相关推荐
叶 落1 小时前
[Rust 基础课程]猜数字游戏-获取用户输入并打印
rust·rust基础
UWA3 小时前
UWA DAY 2025 游戏开发者大会|全议程
游戏·unity·性能优化·游戏开发·uwa·unreal engine
RustFS3 小时前
RustFS 如何修改默认密码?
rust
景天科技苑5 小时前
【Rust线程池】如何构建Rust线程池、Rayon线程池用法详细解析
开发语言·后端·rust·线程池·rayon·rust线程池·rayon线程池
龚子亦20 小时前
【Unity开发】数据存储——XML
xml·unity·游戏引擎·数据存储·游戏开发
该用户已不存在1 天前
Zig想要取代Go和Rust,它有资格吗
前端·后端·rust
侑虎科技1 天前
Unity中利用遗传算法训练MLP
性能优化·游戏开发
用户1774125612441 天前
不懂装懂的AI,折了程序员的阳寿
rust
量子位2 天前
vivo自研蓝河操作系统内核开源!Rust开发新机遇来了
rust·ai编程
祈澈菇凉2 天前
rust嵌入式开发零基础入门教程(六)
stm32·单片机·rust