前言#
用rust来写游戏也是一种不错的选择,如果你还没有rust基础,你可以看下我之前的文章
如果觉得不行,可以看官方原文档:Installation - The Rust Programming Language (rust-lang.org)
另外Bevy的学习文档:Bevy - Getting Started (bevyengine.org)
Bevy

Bevy_introduction
一个基于rust的清爽的数据驱动型游戏引擎。
不过bevy现在还太过年轻,无法用于生产环境。
如果你有这方面的需求,可以用Godot engine[2]
Getting Start!
例子
我们直接去GitHub上拉个例子下来
git clone https://github.com/bevyengine/bevy
git checkout latest注意别忘了把版本升级到最最新的,不然会和rustup冲突
然后cd到bevy目录里直接
cargo run --example breakout 
将Bevy作为依赖#
一般引擎/库是我们的代码包含它的代码,而框架则相反。
我们可以直接从crates.io里下载下来
cargo add bevy然后在Cargo.toml中引入
[dependencies]
bevy = "0.9" # make sure this is the latest version 然后我们就可以使用啦~
Setup
在开始做游戏之前,我们还需要先搞好配置:
- 安装
rust开发环境(这个相信大家都已经安装好了,这里就不多说了) - 操作系统依赖,我这里是
Windows系统,得安装VS2019 build tools[7],不过我之前已经安装过了,所以这里就不多说了。 而linux系统的请跟着这个来:https://github.com/bevyengine/bevy/blob/main/docs/linux_dependencies.md,MacOS则需要安装XCode命令行工具xcode-select --install或者下载app:https://apps.apple.com/en/app/xcode/id497799835 IDE: 想用啥用啥,不过推荐搭配rust-analyzer[8]
那么以上都搞好了我们就可以直接上手开发了
创建一个Bevy项目#
cargo new my_bevy_game
cd my_bevy_game
cargo add bevy安装好之后我们声明到Cargo.toml中
[package]
name = "my_bevy_game"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = "0.9"注意这里bevy的版本得确保是最新版本的,不然有可能有冲突。
如果你的这个项目是在一个workspace里的,那么你还需要加上
[workspace]
resolver = "2" # Important! wgpu/Bevy needs this!优化编译性能
对于一个大型3d模型来说,如果要去debug它可能一次就需要编译几分钟,这样对于开发来说是十分不便的,但是这个现象也是挺常见的。
不过还是有方法可以降低编译的时间的
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3 默认情况下dev mode 的opt-level是0,对debug来说是最适合的,但是会对runtime有一定性能影响,比如低FPS等,所以设置为1比较合适。
而对于其它非自己的代码比如依赖就可以最高等级优化,因为他们基本不会再改动。
注意这些都是dev也就是开发阶段的,不影响prod。
如果你还想提升编译的速度,你可以使用Bevy的动态链接特性,它可以显著的降低编译时间。
Bevy/dynamic
我们可以手动输入来使用
cargo run --features bevy/dynamic当然,这样每次run都得加上,
最好的方式自然就是放到Cargo.toml中。
[dependencies]
bevy = { version = "0.9.0", features = ["dynamic"] } 不过注意在发布前把这个features移除,不然你就会把库libbevy_dylib也跟着带上线。
LLD Linker
编译阶段花费很多的时间在link阶段,所以我们还可以替换自带的编译linker来降低编译时间。
LLD linker就是用来替换原来的编译器linker的:
Ubuntu:sudo apt-get install lldArch:sudo pacman -S lldMacOS:现在暂未支持,可以使用zld替代:brew install michaeleisel/zld/zldWindows:需要搭配cargo-binutils
cargo install -f cargo-binutils
rustup component add llvm-tools-previewmold linker
这里还有一个可以替换的linker,它比LLD快5x也就是五倍多,但是它不稳定,还有一些警告和issue。
同样也是区分操作系统的:
- Ubuntu:
sudo apt-get install mold - Arch:
sudo pacman -S mold - MacOS: 不支持。也没有替代方案
- Windows: 也是不支持,也没有替代方案,可以看这个相信说明:https://github.com/rui314/mold/issues/190
然后还需要配置Cargo.toml
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"] 另外建议不要搭配bevy/dynamic来使用,这样反而会降低编译速度。
Nightly Rust Compiler
你还可以切换到Nightly Rust Compiler来体验最新的编译性能,不过代价自然就是不稳定。
在项目根目录创建一个rust-toolchain.toml文件
[toolchain]
channel = "nightly"具体用法可以看这里:Overrides - The rustup book
Generic Sharing
最后我们还可以使用泛型分享,这样编译器就不需要去重复生成泛型,这样也能省下编译的时间。当然,目前也是nightly的。
为了可以快速编译,你可以安装nightly rust compiler和LLD。不过你还需要将这段配置信息复制到package/.cargo/config.toml中:https://github.com/bevyengine/bevy/blob/main/.cargo/config_fast_builds
# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
# NOTE: For maximum performance, build using a nightly compiler
# If you are using rust stable, remove the "-Zshare-generics=y" below.
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]
# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
# `brew install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld", "-Zshare-generics=y"]
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/opt/homebrew/bin/zld", "-Zshare-generics=y"]
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
rustflags = ["-Zshare-generics=n"]
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
#[profile.dev]
#debug = 1在我们的项目中则是my_bevy_game/.cargo/config.toml。
如果你配置过程中遇到某些问题,可以在这里找找有没有相同的问题:Bevy - Troubleshooting
如果没找到可以提问:https://discord.com/invite/gMUk5Ph
那么现在我们就可以直接cargo run了,不过第一次会很慢,因为需要check所有依赖。
创建App#
use bevy::prelude::*;
fn main() {
App::new().run();
}然后直接cargo run,应该是无事发生,因为我们并没有通知Bevy要做什么。
ECS(Entity Component System)#
ECS是一种软件模式。基于这种软件模式,你的代码会被分割成Entities也就是实体、Components组件、Systems系统。
实体是指向一群组件的唯一事物,然后使用系统处理其过程。
举个例子,一个实体,它有Position和Velocity两个组件。然后另外还有一个实体,它有Position和UI两个组件。系统是在一组特定组件上运行的逻辑,你可能有一个运行在所有带有Position和Velocity组件的实体上的movement系统。
ECS模式鼓励清晰、解耦的设计,这样可以迫使我们把数据和逻辑分解为核心组件。这样也能提升我们代码访问内存的性能并且让并发简单许多。
Bevy ECS
Bevy就是基于这种模式来的。
不过和其它Rust Ecs实现的不同,它不需要复杂的生命周期、traits、构建模式或者宏等。贯穿这些概念,Bevy ECS都是使用普通的Rust数据类型。
Components:使用Struct搭配Component这个trait。
#[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:一个简单的类型包含一个唯一整数
struct Entity(u64); 然后我们来看下它们是怎么组合工作的。
我们的第一个System#
我们来改下main.rs中的代码
use bevy::app::App;
fn hello_world() {
println!("hello world!");
}
fn main() {
App::new()
.add_system(hello_world)
.run();
} 这个hello_world就是我们的第一个system。
add_system[15] 把我们的system加入到app调度里(Schedule[16])。
现在我们再次运行cargo run,应该会输出hello world!。

我们的第一个Component#
#[derive(Component)]
struct Person;
#[derive(Component)]
struct Name(String);我们定义了一个Person组件 ,同时创建了一个Name的组件,这个Name用来表示这个Person 的name。
常规开发中,这个name我们是放到Person里面作为字段的,因为有关联。
不过可能有其它的实体比如Dog也需要这个Name,所以直接抽出来当作一个component岂不美哉。
现在我们的有了一个实体:People。这个实体有两个组件:人和名。
我们现在可以搞一个系统把人给加到我们的世界里了(World[18])
这里使用Commands[19]来spawn也就是产出People。一次可以多来几个。
fn add_people(mut commands: Commands) {
commands.spawn((Person, Name("Elaina Proctor".to_string())));
commands.spawn((Person, Name("Renzo Hume".to_string())));
commands.spawn((Person, Name("Zayna Nieves".to_string())));
}然后将我们的add_people这个system加入到app调度中。
fn main() {
App::new()
.add_startup_system(add_people)
.add_system(hello_world)
.run();
} add_startup_system,这个方法注册到app调度里的system只会执行一次,他会在所有其它system之前执行,一般在app启动时。
不过现在我们的people还没做啥。我们再来一个system:greet_people。给大哥大嫂拜年好。
fn greet_people(query: Query<&Name, With<Person>>) {
for name in query.iter() {
println!("hello {}!", name.0);
}
}
// ...
fn main() {
App::new()
.add_startup_system(add_people)
.add_system(hello_world)
.add_system(greet_people)
.run();
}ok,现在我们可以给几个人say hello了。

插件
Bevy的其中一个核心原理就是模块化(modularity)。所有Bevy引擎中的特性(features)都是基于plugins来实现的,包括内部的特性renderer也就是渲染器。不仅如此,我们的游戏本身也是被当做plugins的。
而这就意味着灵活性,比如我们不需要UI,我们可以不去注册UiPlugin[21],或者我们想搞个无头(headless)服务器,我们可不去注册RenderPlugin[22]。
或者觉得官方提供的不好,我们自己搞一个,这都是可以的。
不过绝大多数情况下我们都不需要去特殊定制的,我们直接用官方提供的即可。
Bevy默认的插件
我们来加个默认的plugin,就叫DefaultPlugin[24]。。。。这个插件中包含了很多一般开发用得到的东西,比如2d/3d的渲染器,UI系统等。
use bevy::DefaultPlugins;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(add_people)
.add_system(hello_world)
.add_system(greet_people)
.run();
}现在我们重新执行cargo run之后,你会发现有一个窗口弹了出来,这是因为这个DefaultPlugin里面包含了WindowPlugin[25]。
实际上这个窗口还用到了WinitPlugin[26],这个插件基于winit library[27],调用操作系统原生的窗口api。
不过我们的窗口黑色一片,因为我们没有通知要画啥。
另外还有一点你应该可注意到了,我们的终端print的内容一直在刷屏。这是因为DefaultPlugin中给我们的应用加上了事件循环(event loop)。现在我们的应用调度会按一定的频率一直重复执行。
上面注册DefaultPlugins相当于下面这样
fn main() {
App::new()
.add_plugin(CorePlugin::default())
.add_plugin(InputPlugin::default())
.add_plugin(WindowPlugin::default())
/* more plugins omitted for brevity */
.run();
}搞一个自己的插件
前面说过我们的游戏自身都是可以当做一个插件,那么我们写的代码自然以插件的形式来封装会比较好些。
我们的插件需要实现Plugin[29]这个trait
这个trait必须实现build方法。
然后我们把我们之前写的玩意儿都放到这个插件里。
这个trait必须实现build方法。
然后我们把我们之前写的玩意儿都放到这个插件里。
既然要迁移,我们顺便迁移到另一个文件里,新建lib.rs和my_plugin.rs
其中my_plugin.rs
use bevy::prelude::*;
use bevy::app::Plugin;
pub struct MyPlugin;
#[derive(Component)]
struct Person;
#[derive(Component)]
struct Name(String);
impl Plugin for MyPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(add_people)
.add_system(hello_world)
.add_system(greet_people);
}
}
fn hello_world() {
println!("hello world!");
}
fn add_people(mut commands: Commands) {
commands.spawn((Person, Name("Elaina Proctor".to_string())));
commands.spawn((Person, Name("Renzo Hume".to_string())));
commands.spawn((Person, Name("Zayna Nieves".to_string())));
}
fn greet_people(query: Query<&Name, With<Person>>) {
for name in query.iter() {
println!("hello {}!", name.0);
}
}lib.rs导出
mod my_plugin;
pub use my_plugin::MyPlugin; main.rs则是引入这个插件并加载即可。
use bevy::app::App;
use bevy::DefaultPlugins;
use my_bevy_game::MyPlugin;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MyPlugin)
.run();
}这里把三个系统注册放到了MyPlugin中build里注册,这样main也就是主入口就干净了很多。
资源#
实体和组件搭配起来已经可以用于各种复杂的场景了,但是有的时候我们还是需要有全局唯一的数据(globally unique data)。在Bevy中将这种数据称之为Resources也就是资源,比如:
Elapsed Time也就是运行时间Asset Collections (sounds, textures, meshes)也就是传统意义上的资源Renderers渲染器
使用资源跟踪时间[31]
我们前面print疯狂输出,现在该减少它们的频率了。DefaultPlugins里面包含了Time[32]这个resource,我们可以基于它去print。
不过在这之前,我们得把hello_world去掉了,他不需要存在了。
那么就剩下一个greet_people有print,我们来改下。
#[derive(Resource)]
struct GreetTimer(Timer);
fn greet_people(
time: Res<Time>, mut timer: ResMut<GreetTimer>, query: Query<&Name, With<Person>>) {
// update our timer with the time elapsed since the last update
// if that caused the timer to finish, we say hello to everyone
if timer.0.tick(time.delta()).just_finished() {
for name in query.iter() {
println!("hello {}!", name.0);
}
}
}timer可以理解为定时器,tick方法则是用来通知这个timer前进多少时间。
delta方法返回的是当前距离上次刷新的时间间隔。
那么这个time也就是时间间隔在哪里设置呢?根据之前的写法,自然是有app提供的api。
impl Plugin for HelloPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(GreetTimer(Timer::from_seconds(2.0, TimerMode::Repeating)))
.add_startup_system(add_people)
.add_system(greet_people);
}
} 这里调用insert_resource插入我们的resource也就是Timer,它的时间间隔也就是Time是2秒。模式是重复执行。
现在我们重新运行下cargo run,打印的频率应该变成了两秒一次。

那么我们的demo就简单完成了。
指引
到这里其实入门文档实战相关的已经没了,不过我们接触的东西相当的少,并不足以让我们上手开发。
这里有些指引,能帮助我们去熟练使用。
examples:https://github.com/bevyengine/bevy/tree/latest/examples#examples#
这里面主要的features都有对应的example。这一部分应该是对api的使用最有帮助的一部分了。
浏览器直接访问例子:https://bevyengine.org/examples
Breakout:https://github.com/bevyengine/bevy/blob/latest/examples/games/breakout.rs#
这个小游戏涉及到很多bevy的知识,对于入门来说非常推荐学习它的代码。
API Docs: https://docs.rs/bevy#
不多说,如果遇到不知道啥意思的,直接看这个文档。
Bevy Assets: Bevy - Assets#
这里包含了一系列社区项目:
- Bevy - Assets learning resources
- Bevy - Assets plugin
- Bevy - Assets games
Bevy CheatBook:Unofficial Bevy Cheat Book#
一个深入的(in-depth)、独断己见的(opinionated)、非官方的(unofficial)API和生态指引。
故障排除
对于新的事务来说,对于bug的处理和反馈是一件让人头疼的问题。
东西太新没多少人用会导致搜索不到对应bug处理方案。
而错误反馈的话目前最好的方式自然还是去Github上提issue,前提是真的是bug并且issue中搜索不到对应的处理方案。
如果找不到GPU#
hread 'main' panicked at 'Unable to find a GPU! Make sure you have installed required drivers!'这个报错表示系统找不到GPU,它可能源自于下面两种场景:
Vulkan-compatible这个driver没有安装,修复方式自然就是手动安装,linux则是安装:vulkan-intel或者vulkan-radeon。- 无头机器(
headless machine),也就是这玩意儿自身就没有GPU。这种修复方式自然是要安装GPU。
吐槽:这俩问题解决方案说了和没说貌似没啥差别。。。。
实战#
篇幅问题,就不放这里了
rust基础学习--基于Bevy实现扫雷小游戏索引 - 知乎 (zhihu.com)
总结#
莫得总结
参考#
- ^Bevy https://bevyengine.org/
- ^Godot-engine https://godotengine.org/
- ^Bevy-Getting-Start! https://bevyengine.org/learn/book/getting-started/
- ^example https://bevyengine.org/learn/book/getting-started/#try-the-examples
- ^add-Bevy-as-dependency https://bevyengine.org/learn/book/getting-started/#add-bevy-as-a-dependency
- ^setup https://bevyengine.org/learn/book/getting-started/setup/
- ^vs2019-build-tool https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16
- ^rust-analyzer https://github.com/rust-lang/rust-analyzer
- ^create-a-bevy-project https://bevyengine.org/learn/book/getting-started/setup/#create-a-new-bevy-project
- ^compile-with-performance-optimizations https://bevyengine.org/learn/book/getting-started/setup/#compile-with-performance-optimizations
- ^apps https://bevyengine.org/learn/book/getting-started/apps/
- ^ECS https://bevyengine.org/learn/book/getting-started/ecs/
- ^Bevy-ECS https://bevyengine.org/learn/book/getting-started/ecs/#bevy-ecs
- ^your-first-system https://bevyengine.org/learn/book/getting-started/ecs/#your-first-system
- ^add_system https://docs.rs/bevy_app/latest/bevy_app/struct.App.html#method.add_system
- ^Schedule https://docs.rs/bevy_ecs/latest/bevy_ecs/schedule/struct.Schedule.html
- ^your-first-components https://bevyengine.org/learn/book/getting-started/ecs/#your-first-components
- ^World https://docs.rs/bevy_ecs/latest/bevy_ecs/world/struct.World.html
- ^Commands https://docs.rs/bevy_ecs/latest/bevy_ecs/system/struct.Commands.html
- ^plugins https://bevyengine.org/learn/book/getting-started/plugins/
- ^UiPlugin https://docs.rs/bevy_ui/latest/bevy_ui/struct.UiPlugin.html
- ^RenderPlguin https://docs.rs/bevy_render/latest/bevy_render/struct.RenderPlugin.html
- ^Bevy-default-plugin https://bevyengine.org/learn/book/getting-started/plugins/#bevy-s-default-plugins
- ^DefaultPlugins https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html
- ^WindowPlugin https://docs.rs/bevy_window/latest/bevy_window/struct.WindowPlugin.html
- ^WinitPlugin https://docs.rs/bevy_winit/latest/bevy_winit/struct.WinitPlugin.html
- ^winit library https://github.com/rust-windowing/winit
- ^create-out-first-plugin https://bevyengine.org/learn/book/getting-started/plugins/#creating-your-first-plugin
- ^Plugin https://docs.rs/bevy_app/latest/bevy_app/trait.Plugin.html
- ^Resources https://bevyengine.org/learn/book/getting-started/resources/
- ^tracking-time-with-resources https://bevyengine.org/learn/book/getting-started/resources/#tracking-time-with-resources
- ^Time https://docs.rs/bevy_core/latest/bevy_core/struct.Time.html
- ^next-steps https://bevyengine.org/learn/book/next-steps/
- ^troubleshooting https://bevyengine.org/learn/book/troubleshooting/
编辑于 2023-12-08 17:23・IP 属地广东
