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

相关推荐
Yeauty5 小时前
Rust 中的高效视频处理:利用硬件加速应对高分辨率视频
开发语言·rust·ffmpeg·音视频·音频·视频
zhu12893035566 小时前
基于Rust与WebAssembly实现高性能前端计算
前端·rust·wasm
关山月6 小时前
Rust 如何处理闭包:Fn、FnMut 和 FnOnce
rust
Vitalia13 小时前
从零开始学Rust:枚举(enum)与模式匹配核心机制
开发语言·后端·rust
一只小松许️16 小时前
Rust闭包详解
开发语言·rust
SoFlu软件机器人19 小时前
Go/Rust 疯狂蚕食 Java 市场?老牌语言的 AI 化自救之路
java·golang·rust
小白学大数据21 小时前
异步读取HTTP响应体的Rust实现
网络协议·http·rust
Source.Liu1 天前
【学Rust写CAD】24 扫描渐变(sweep_gradient.rs)
后端·rust
一只小松许️1 天前
Rust迭代器详解
rust
机构师1 天前
<tauri><rust><GUI>基于rust和tauri,实现一个svg转png的工具
javascript·后端·rust