Bevy 0.4
发布于 2020 年 12 月 19 日 由 Carter Anderson ( @cart @cart_cart cartdev )
在发布 Bevy 0.3 一个多月后,感谢 66 位贡献者、178 个 pull 请求以及我们 慷慨的赞助商,我很高兴地宣布 Bevy 0.4 版本已在 crates.io 上发布!
对于那些不了解的人,Bevy 是一个用 Rust 构建的,令人耳目一新的简单数据驱动的游戏引擎。您可以查看 快速入门指南 来开始使用。Bevy 也是永久免费和开源的!您可以在 GitHub 上获取完整的 源代码。
以下是本版本的一些亮点
WASM + WebGL2 #
Bevy 现在拥有一个 WebGL2 渲染后端!@mrk-its 一直在努力构建 Bevy WebGL2 插件 并扩展 bevy_render
以满足 Web 的需求。他还创建了一个很棒的网站来展示在 Web 上运行的各种 Bevy 示例和游戏。
我认为结果不言而喻
Bevy WebGL2 展示 #
跨平台主函数 #
在大多数受支持的 Bevy 平台上,您可以直接使用普通的主函数(例如:Windows、MacOS、Linux 和 Web)。以下是这些平台上运行的最小 Bevy 应用
use bevy::prelude::*;
fn main() {
App::build().run();
}
但是,某些平台(目前包括 Android 和 iOS)需要额外的样板代码。这种神秘的代码容易出错,占用空间,而且看起来不太美观。到目前为止,Bevy 用户不得不提供自己的样板代码……但现在不用了!Bevy 0.4 添加了一个新的 #[bevy_main]
proc-macro,它会为您插入相关样板代码。这是我们迈向“一次编写,随处运行”目标的一大步。
这个 Bevy 应用包含在 Windows、MacOS、Linux、Android、iOS 和 Web 上运行所需的所有代码
use bevy::prelude::*;
#[bevy_main]
fn main() {
App::build().run();
}
实时着色器重载 #
Bevy 现在可以更新运行时对着色器的更改,让您在不重新启动应用程序的情况下获得即时反馈。此视频没有加速!
ECS 优化 #
如果没有新一轮的 ECS 优化,Bevy 更新就不完整!
灵活的 ECS 参数 #
之前的 Bevy 版本要求您以特定顺序提供系统参数
/// This system followed the [Commands][Resources][Queries] order and compiled as expected
fn valid_system(mut commands: Commands, time: Res<Time>, query: Query<&Transform>) {
}
/// This system did not follow the required ordering, which caused compilation to fail
fn invalid_system(query: Query<&Transform>, mut commands: Commands, time: Res<Time>) {
}
新手经常会遇到这个问题。这些完全任意的约束是内部实现的一个怪癖。IntoSystem
特征只对特定顺序进行了实现。支持所有顺序会对编译时间产生指数级影响。内部实现也是使用一个 臭名昭著的复杂宏 构建的。
为了解决这个问题,我彻底重写了我们生成系统的方式。我们现在使用 SystemParam
特征,对每个参数类型都实现了该特征。这样做有许多好处
- 显著加快编译时间:我们看到干净编译时间的减少幅度约为 ~25%
- 按任何您想要的顺序使用参数:不再有任意的顺序限制!
- 轻松添加新参数:现在我们(以及用户)可以轻松地创建新参数。只需实现
SystemParam
特征即可! - 更简单的实现:新的实现更小,也更容易维护和理解。
// In Bevy 0.4 this system is now perfectly valid. Cool!
fn system(query: Query<&Transform>, commands: &mut Commands, time: Res<Time>) {
}
请注意,在 Bevy 0.4 中,命令现在看起来像 commands: &mut Commands
而不是 mut commands: Commands
。
简化的查询过滤器 #
到目前为止,Bevy 的查询过滤器与组件交织在一起
fn system(query: Query<With<A, Without<B, (&Transform, Changed<Velocity>)>>>) {
}
困惑了吗?您不是第一个!您可以将上面的查询解释为“给我所有拥有 A
组件、没有 B
组件并且 Velocity 组件发生了更改的实体的 Transform
和 Velocity
组件的不可变引用”。
首先,通过 With / Without 嵌套类型使得不清楚正在发生什么。此外,很难判断 Changed<Velocity>
参数的作用。它只是一个过滤器吗?它还会返回 Velocity 组件吗?如果是,是不可变的还是可变的?
将这些概念分开是有意义的。在 Bevy 0.4 中,查询过滤器与查询组件是分开的。上面的查询看起来像这样
// Query with filters
fn system(query: Query<(&Transform, &Velocity), (With<A>, Without<B>, Changed<Velocity>)>) {
}
// Query without filters
fn system(query: Query<(&Transform, &Velocity)>) {
}
这样更容易一目了然地了解查询的作用。它还使行为更具可组合性。例如,您现在可以在不实际检索 Velocity
组件的情况下,根据 Changed<Velocity>
进行过滤。
现在过滤器已成为一个单独的类型,您可以为要重复使用的过滤器创建类型别名
type ChangedVelocity = (With<A>, Without<B>, Changed<Velocity>);
fn system(query: Query<(&Transform, &Velocity), ChangedVelocity>) {
}
系统输入、输出和链式调用 #
系统现在可以拥有输入和输出。这开启了各种有趣的行为,例如系统错误处理
fn main() {
App::build()
.add_system(result_system.system().chain(error_handler.system()))
.run();
}
fn result_system(query: Query<&Transform>) -> Result<()> {
let transform = query.get(SOME_ENTITY)?;
println!("found entity transform: {:?}", transform);
Ok(())
}
fn error_handler_system(In(result): In<Result<()>>, error_handler: Res<MyErrorHandler>) {
if let Err(err) = result {
error_handler.handle_error(err);
}
}
System
特征现在看起来像这样
// Has no inputs and no outputs
System<In = (), Out = ()>
// Takes a usize as input and return a f32
System<In = usize, Out = f32>
我们在新的 Schedule 实现中使用了此功能。
Schedule V2 #
Bevy 的旧 Schedule 很好用。系统注册易于阅读和组合。但它也存在显著的限制
- 只允许一个 Schedule
- 非常静态:您只能使用我们提供的工具
- 阶段只是一些系统列表
- 阶段被添加到 Schedule 中
- 阶段使用硬编码的系统运行器
- 无法在运行时切换 Schedule
- 无法轻松支持“固定时间步长”场景
为了解决这些问题,我从头开始编写了一个新的 Schedule 系统。在您担心之前,这些变化在很大程度上是非侵入式的。您所熟悉和喜爱的顶级“应用程序构建器”语法仍然可用
app.add_system(my_system.system())
阶段特征 #
阶段现在是一个特征。您现在可以实现您自己的 Stage
类型!
struct MyStage;
impl Stage for MyStage {
fn run(&mut self, world: &mut World, resources: &mut Resources) {
// Do stage stuff here.
// You have unique access to the World and Resources, so you are free to do anything
}
}
阶段类型:SystemStage
#
这基本上是一个“普通”阶段。您可以向其中添加系统,并决定如何执行这些系统(并行、串行或自定义逻辑)
// runs systems in parallel (using the default parallel executor)
let parallel_stage =
SystemStage::parallel()
.with_system(a.system())
.with_system(b.system());
// runs systems serially (in registration order)
let serial_stage =
SystemStage::serial()
.with_system(a.system())
.with_system(b.system());
// you can also write your own custom SystemStageExecutor
let custom_executor_stage =
SystemStage::new(MyCustomExecutor::new())
.with_system(a.system())
.with_system(b.system());
阶段类型:[Schedule
] #
您没看错![Schedule
] 现在实现了 Stage
特征,这意味着您可以在其他 Schedule 中嵌套 Schedule
let schedule = Schedule::default()
.with_stage("update", SystemStage::parallel()
.with_system(a.system())
.with_system(b.system())
)
.with_stage("nested", Schedule::default()
.with_stage("nested_stage", SystemStage::serial()
.with_system(b.system())
)
);
运行条件 #
您可以为任何 SystemStage
或 [Schedule
] 添加“运行条件”。
// A "run criteria" is just a system that returns a `ShouldRun` result
fn only_on_10_criteria(value: Res<usize>) -> ShouldRun {
if *value == 10 {
ShouldRun::Yes
} else {
ShouldRun::No
}
}
app
// this stage only runs when Res<usize> has a value of 10
.add_stage_after(stage::UPDATE, "only_on_10_stage", SystemStage::parallel()
.with_run_criteria(only_on_10_criteria.system())
.with_system(my_system.system())
)
// this stage only runs once
.add_stage_after(stage::RUN_ONCE, "one_and_done", Schedule::default()
.with_run_criteria(RunOnce::default())
.with_system(my_system.system())
)
固定时间步长 #
您现在可以在“固定时间步长”上运行阶段。
// this stage will run once every 0.4 seconds
app.add_stage_after(stage::UPDATE, "fixed_update", SystemStage::parallel()
.with_run_criteria(FixedTimestep::step(0.4))
.with_system(my_system.system())
)
这建立在 ShouldRun::YesAndLoop
的基础上,它确保 Schedule 持续循环,直到它消耗了所有累积时间。
如果您想了解有关固定时间步长的更多信息,请查看优秀的 "Fix Your Timestep!" 文章。
类型化的阶段构建器 #
现在阶段可以是任何类型,我们需要一种方法让 Plugin
与任意阶段类型交互
app
// this "high level" builder pattern still works (and assumes that the stage is a SystemStage)
.add_system(some_system.system())
// this "low level" builder is equivalent to add_system()
.stage(stage::UPDATE, |stage: &mut SystemStage|
stage.add_system(some_system.system())
)
// this works for custom stage types too
.stage(MY_CUSTOM_STAGE, |stage: &mut MyCustomStage|
stage.do_custom_thing()
)
弃用 For-Each 系统 #
之前的 Bevy 版本支持“for-each”系统,看起来像这样
// on each update this system runs once for each entity with a Transform component
fn system(time: Res<Time>, entity: Entity, transform: Mut<Transform>) {
// do per-entity logic here
}
从现在起,上面的系统应该像这样编写
// on each update this system runs once and internally iterates over each entity
fn system(time: Res<Time>, query: Query<(Entity, &mut Transform)>) {
for (entity, mut transform) in query.iter_mut() {
// do per-entity logic here
}
}
For-each 系统看起来很不错,有时可以节省一些输入。为什么要移除它们?
- For-each 系统在根本上存在一些限制。它们无法迭代删除的组件、过滤、控制迭代或同时使用多个查询。这意味着一旦需要这些功能,就需要将它们转换为“查询系统”。
- Bevy 通常应该有一种“做事方式”。For-each 系统是定义一小部分系统类型的稍微更符合人体工程学的方式。这迫使人们在不需要的时候做出“设计决策”。它还会使示例和教程根据人们对一种或另一种方式的偏好而变得不一致。
- 对于新手来说,存在一些在我们的支持论坛中不断出现并让新手感到困惑的“陷阱”
- 用户期望
&mut T
查询在 foreach 系统中工作(例如:fn system(a: &mut A) {}
)。这些无法工作,因为我们要求Mut<T>
跟踪指针以确保更改跟踪始终按预期工作。等效的Query<&mut A>
可以工作,因为我们可以在迭代查询时返回跟踪指针。 - 一个“在某些条件下运行此 for-each 系统”的错误,这个错误很常见,以至于我们不得不在 Bevy Book 中对其进行说明。
- 用户期望
- 它们增加了编译时间。移除 for-each 系统为我节省了大约 ~5 秒的 Bevy 清理编译时间)。
- 它们的内部实现需要一个复杂的宏。这会影响可维护性。
状态 #
根据大众需求,Bevy 现在支持状态。这些是逻辑上的“应用程序状态”,允许您根据应用程序所处的状态启用/禁用系统。
状态被定义为普通的 Rust 枚举
#[derive(Clone)]
enum AppState {
Loading,
Menu,
InGame
}
然后,您可以将它们作为资源添加到您的应用程序中,如下所示
// add a new AppState resource that defaults to the Loading state
app.add_resource(State::new(AppState::Loading))
要根据当前状态运行系统,请添加一个 StateStage
app.add_stage_after(stage::UPDATE, STAGE, StateStage::<AppState>::default())
然后,您可以为每个状态值/生命周期事件添加系统,如下所示
app
.on_state_enter(STAGE, AppState::Menu, setup_menu.system())
.on_state_update(STAGE, AppState::Menu, menu.system())
.on_state_exit(STAGE, AppState::Menu, cleanup_menu.system())
.on_state_enter(STAGE, AppState::InGame, setup_game.system())
.on_state_update(STAGE, AppState::InGame, movement.system())
请注意,存在不同的“生命周期事件”
- on_enter:首次进入状态时运行一次
- on_exit:退出状态时运行一次
- on_update: 在每个阶段运行时仅执行一次(在任何 on_enter 或 on_exit 事件运行之后)。
您可以从这样的系统中排队状态更改。
fn system(mut state: ResMut<State<AppState>>) {
state.set_next(AppState::InGame).unwrap();
}
排队的状态更改将在 StateStage
结束时应用。如果您在 StateStage
中更改状态,生命周期事件将在同一个更新/帧中发生。您可以这样做任意多次(即它将继续运行状态生命周期系统,直到不再有更改被排队)。这确保了可以在同一帧内应用多个状态更改。
GLTF 改进 #
Bevy 的 GLTF 加载器现在导入相机。这是一个在 Blender 中简单的场景设置。
这就是它在 Bevy 中的样子(照明不同,因为我们还没有导入灯光)。
还有一些其他改进。
- 从 GLTF 导入图像时进行像素格式转换。
- 默认材质加载。
- 层次结构修复。
将场景作为子级生成 #
场景现在可以像这样作为子级生成。
commands
.spawn((
Transform::from_translation(Vec3::new(0.5, 0.0, 0.0)),
GlobalTransform::default(),
))
.with_children(|parent| {
parent.spawn_scene(asset_server.load("scene.gltf"));
});
通过在父级下生成,这使您可以执行诸如平移/旋转/缩放同一场景的多个实例之类的操作。
动态链接 #
@bjorn3 发现您可以强制 Bevy 进行动态链接。
这显着减少了迭代编译时间。查看使用 快速编译配置以及动态链接来编译对 3d_scene.rs
示例所做更改需要多长时间。
编译对 3d_scene 示例的更改所需时间(以秒为单位,越少越好) #
我们添加了一个货物特性,以便在开发期间轻松启用动态链接。
# for a bevy app
cargo run --features bevy/dynamic
# for bevy examples
cargo run --features dynamic --example breakout
请记住,在发布游戏时应禁用该特性。
文本布局改进 #
以前的 Bevy 版本使用自定义的、简单的文本布局系统。它存在许多错误和限制,例如臭名昭著的“波浪文本”错误。
新的文本布局系统使用 glyph_brush_layout,它修复了布局错误并添加了许多新的布局选项。请注意,示例中使用的“Fira Sans”字体具有一些风格上的“波浪”... 这不是错误。
渲染器优化 #
Bevy 的渲染 API 的设计是为了易于使用和扩展。我想要先确定一个好的 API,但这导致了一些性能 TODO,这些 TODO 导致了一些相当严重的开销。
对于Bevy 0.4,我决定尽可能地解决这些 TODO。还有很多事情要做(例如实例化和批处理),但 Bevy 的性能已经比以前好多了。
将所有内容增量化 #
Bevy 的大多数高级渲染抽象都是为增量更新而设计的,但当我第一次构建引擎时,ECS 更改检测尚未实现。现在我们拥有了所有这些不错的优化工具,使用它们是有意义的!
对于第一轮优化,我尽可能地进行了增量化。
- 向 RenderResourceNode、Sprites 和 Transforms 添加了更改检测,当这些值不改变时,这提高了性能。
- 仅在资产更改时同步资产 GPU 数据。
- 在所有引用资产的实体之间共享资产 RenderResourceBindings。
- 网格提供程序系统现在仅在需要时更新网格专业化。
- 停止每帧清除绑定组,并每隔一帧删除陈旧的绑定组。
- 缓存不匹配的渲染资源绑定结果(这可以防止每实体每帧的冗余计算)。
- 当状态实际上没有更改时,不要发送渲染通道状态更改命令。
绘制 10,000 个静态 Sprites 所需的帧时间(以毫秒为单位,越少越好) #
绘制 10,000 个移动 Sprites 所需的帧时间(以毫秒为单位,越少越好) #
优化文本渲染(以及其他立即渲染) #
文本渲染(以及使用 SharedBuffers
立即渲染抽象的任何其他内容)在以前的 Bevy 版本中非常慢。这是因为 SharedBuffers
抽象是一个占位符实现,它实际上没有共享缓冲区。通过实现“真实”的 SharedBuffers
抽象,我们获得了相当大的文本渲染速度提升。
绘制“text_debug”示例所需的时间(以毫秒为单位,越少越好) #
邮箱 Vsync #
Bevy 现在默认使用 wgpu 的“邮箱 Vsync”。这减少了支持该功能的平台上的输入延迟。
反射 #
Rust 有一个相当大的“反射”差距。对于那些不知道的人来说,“反射”是一类语言特性,使您能够在运行时与语言结构进行交互。它们为传统上静态的语言概念添加了一种“动态性”。
我们在 Rust 中有一些反射的片段,例如 TypeId
和 type_name
。但是当涉及到与数据类型交互时... 我们还没有任何东西。这很不幸,因为某些问题本质上是动态的。
当我第一次构建 Bevy 时,我决定引擎将从这些功能中受益。反射是场景系统、类似 Godot(或 Unity)的属性动画系统和编辑器检查工具的良好基础。我构建了 bevy_property
和 bevy_type_registry
板条箱来满足这些需求。
它们完成了工作,但它们是专门针对 Bevy 的需求量身定制的,充满了自定义术语(而不是直接反映 Rust 语言结构),没有处理特征,并且对如何访问数据有许多基本限制。
在此版本中,我们用新的 bevy_reflect
板条箱替换了旧的 bevy_property
和 bevy_type_registry
板条箱。Bevy Reflect 旨在成为一个“通用”Rust 反射板条箱。我希望它对非 Bevy 项目也像对 Bevy 一样有用。我们现在将其用于我们的场景系统,但将来我们将将其用于动画组件字段和自动生成 Bevy 编辑器检查器小部件。
Bevy Reflect 使您能够通过派生 Reflect
特征来动态地与 Rust 类型进行交互。
#[derive(Reflect)]
struct Foo {
a: u32,
b: Vec<Bar>,
c: Vec<u32>,
}
#[derive(Reflect)]
struct Bar {
value: String
}
// I'll use this value to illustrate `bevy_reflect` features
let mut foo = Foo {
a: 1,
b: vec![Bar { value: "hello world" }]
c: vec![1, 2]
};
使用字段名称与字段交互 #
assert_eq!(*foo.get_field::<u32>("a").unwrap(), 1);
*foo.get_field_mut::<u32>("a").unwrap() = 2;
assert_eq!(foo.a, 2);
使用新值修补您的类型 #
let mut dynamic_struct = DynamicStruct::default();
dynamic_struct.insert("a", 42u32);
dynamic_struct.insert("c", vec![3, 4, 5]);
foo.apply(&dynamic_struct);
assert_eq!(foo.a, 42);
assert_eq!(foo.c, vec![3, 4, 5]);
使用“路径字符串”查找嵌套字段 #
let value = *foo.get_path::<String>("b[0].value").unwrap();
assert_eq!(value.as_str(), "hello world");
遍历结构字段 #
for (i, value: &Reflect) in foo.iter_fields().enumerate() {
let field_name = foo.name_at(i).unwrap();
if let Ok(value) = value.downcast_ref::<u32>() {
println!("{} is a u32 with the value: {}", field_name, *value);
}
}
使用 Serde 自动序列化和反序列化 #
这不需要手动 Serde 实现!
let mut registry = TypeRegistry::default();
registry.register::<u32>();
registry.register::<String>();
registry.register::<Bar>();
let serializer = ReflectSerializer::new(&foo, ®istry);
let serialized = ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap();
let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap();
let reflect_deserializer = ReflectDeserializer::new(®istry);
let value = reflect_deserializer.deserialize(&mut deserializer).unwrap();
let dynamic_struct = value.take::<DynamicStruct>().unwrap();
/// reflect has its own partal_eq impl
assert!(foo.reflect_partial_eq(&dynamic_struct).unwrap());
特征反射 #
您现在可以在给定的 &dyn Reflect
引用上调用特征,而无需知道底层类型!这是一种魔法,在大多数情况下应该避免。但在极少数情况下,它非常有用。
#[derive(Reflect)]
#[reflect(DoThing)]
struct MyType {
value: String,
}
impl DoThing for MyType {
fn do_thing(&self) -> String {
format!("{} World!", self.value)
}
}
#[reflect_trait]
pub trait DoThing {
fn do_thing(&self) -> String;
}
// First, lets box our type as a Box<dyn Reflect>
let reflect_value: Box<dyn Reflect> = Box::new(MyType {
value: "Hello".to_string(),
});
/*
This means we no longer have direct access to MyType or it methods. We can only call Reflect methods on reflect_value. What if we want to call `do_thing` on our type? We could downcast using reflect_value.get::<MyType>(), but what if we don't know the type at compile time?
*/
// Normally in rust we would be out of luck at this point. Lets use our new reflection powers to do something cool!
let mut type_registry = TypeRegistry::default()
type_registry.register::<MyType>();
/*
The #[reflect] attribute we put on our DoThing trait generated a new `ReflectDoThing` struct, which implements TypeData. This was added to MyType's TypeRegistration.
*/
let reflect_do_thing = type_registry
.get_type_data::<ReflectDoThing>(reflect_value.type_id())
.unwrap();
// We can use this generated type to convert our `&dyn Reflect` reference to an `&dyn DoThing` reference
let my_trait: &dyn DoThing = reflect_do_thing.get(&*reflect_value).unwrap();
// Which means we can now call do_thing(). Magic!
println!("{}", my_trait.do_thing());
3D 纹理资产 #
Texture 资产现在支持 3D 纹理。新的 array_texture.rs
示例说明了如何加载 3D 纹理并从每个“层”进行采样。
日志记录和分析 #
Bevy 终于有了内置的日志记录功能,现在通过新的 LogPlugin
默认启用。我们评估了各种日志记录库,最终选择了新的 tracing
板条箱。tracing
是一个结构化日志记录器,可以很好地处理异步/并行日志记录(非常适合像 Bevy 这样的引擎),除了“正常”日志记录外,还可以启用分析。
该 LogPlugin
默认配置每个平台以记录到相应的后端:桌面上的终端,Web 上的控制台以及 Android 上的 Android 日志/logcat。我们构建了一个新的 Android tracing
后端,因为之前没有这样的后端。
日志记录 #
Bevy 的内部插件现在生成 tracing
日志。您也可以像这样轻松地将日志添加到您自己的应用程序逻辑中。
// these are imported by default in bevy::prelude::*
trace!("very noisy");
debug!("helpful for debugging");
info!("helpful information that is worth printing by default");
warn!("something bad happened that isn't a failure, but thats worth calling out");
error!("something failed");
这些行将生成漂亮的终端日志。
tracing
具有许多有用的功能,例如结构化日志记录和过滤。 查看他们的文档以获取更多信息。
分析 #
我们添加了通过启用 trace
特性向所有 ECS 系统添加“跟踪跨度”的选项。我们还内置了对 tracing-chrome
扩展的支持,该扩展会导致 Bevy 以“Chrome 跟踪”格式输出跟踪。
如果您使用 cargo run --features bevy/trace,bevy/trace_chrome
运行您的应用程序,您将获得一个 JSON 文件,可以通过访问 chrome://tracing
URL 在 Chrome 浏览器中打开它。
@superdump 为上游 tracing_chrome
添加了对这些漂亮的“跨度名称”的支持。
HIDPI #
Bevy 现在可以正确处理 HIDPI/Retina/高像素密度显示器。
- 操作系统报告的像素密度现在在创建窗口时被考虑在内。如果 Bevy 应用程序在 2x 像素密度显示器上请求一个 1280x720 的窗口,它将创建一个 2560x1440 的窗口。
- 窗口宽度/高度现在以“逻辑单位”报告(在上面的示例中为 1280x720)。可以使用
window.physical_width()
和window.physical_height()
方法仍然可以使用物理单位。 - 窗口“交换链”使用物理分辨率创建,以确保我们仍然具有清晰的渲染(在上面的示例中为 2560x1440)。
- Bevy UI 已适应以正确处理 HIDPI 缩放。
这里还需要做一些工作。虽然 Bevy UI 以清晰的 HIDPI 分辨率渲染图像和框,但文本仍然使用逻辑分辨率渲染,这意味着它在 HIDPI 显示器上不会像它可以的那样清晰。
计时器改进 #
Bevy 的 Timer 组件/资源获得了一些生活质量改进:暂停、字段访问器方法、人体工程学改进以及内部重构/代码质量改进。计时器组件默认不再滴答作响。计时器资源和新的类型计时器组件无法默认滴答作响,因此让(相对不常见的)“未包装的组件计时器”自动滴答作响有点不一致。
计时器 API 现在看起来像这样。
struct MyTimer {
timer: Timer,
}
fn main() {
App::build()
.add_resource(MyTimer {
// a five second non-repeating timer
timer: Timer::from_seconds(5.0, false),
})
.add_system(timer_system.system())
.run();
}
fn timer_system(time: Res<Time>, my_timer: ResMut<MyTimer>) {
if my_timer.timer.tick(time.delta_seconds()).just_finished() {
println!("five seconds have passed");
}
}
任务系统改进 #
@aclysma 更改了 Bevy 任务调度方式,这使 breakout.rs
示例游戏的性能提高了 ~20%,并解决了当任务池配置为只有一个线程时出现的 死锁。当只有一个任务要运行时,任务现在立即在调用线程上执行,这减少了将工作转移到其他线程/阻塞等待它们完成的开销。
Apple Silicon 支持 #
由于 winit (@scoopr) 和 coreaudio-sys (@wyhaya) 的上游工作,Bevy 现在可以在 Apple 硅芯片上运行。@frewsxcv 和 @wyhaya 更新了 Bevy 的依赖项,并验证了它可以在 Apple 的新芯片上构建和运行。
新示例 #
Bevy 贡献者 #
@karroffel 添加了一个有趣的示例,将每个 Bevy 贡献者表示为一只“Bevy 鸟”。它从 Git 中抓取最新的贡献者列表。
BevyMark #
一个“类似 bunnymark 的”基准测试,说明了 Bevy 的精灵渲染性能。这在实现上面提到的渲染器优化时很有用。
更改日志 #
添加 #
- 添加 bevymark 基准测试示例
- gltf:支持相机并修复层次结构
- 向调度程序、阶段和系统添加跟踪跨度
- 添加一个示例,将贡献者表示为 bevy 图标
- 添加接收到的字符
- 将 bevy_dylib 添加到强制动态链接 bevy
- 添加 RenderPass::set_scissor_rect
bevy_log
- 将日志记录功能添加为插件。
- 更改内部日志记录以使用新的实现。
- 跨平台主函数
- 可控环境光颜色
- 添加了一个资源来更改 PBR 的当前环境光颜色。
- 添加了更多基本颜色常量
- 添加盒子形状
- 为事件公开一个 EventId
- 系统输入、输出和链式调用
- 为事件公开一个
EventId
- 向
Window
添加了set_cursor_position
- 添加了新的 Bevy 反射系统
- 替换属性系统
- 添加对 Apple 硅芯片的支持
- 着色器的实时重新加载
- 将鼠标光标位置存储在 Window 中
- 添加 removal_detection 示例
- 其他顶点属性值类型
- 添加 WindowFocused 事件
- 跟踪 Chrome 跨度名称
- 允许窗口最大化
- GLTF:加载默认材质
- 可以从 ChildBuilder 生成场景,或者在生成场景时直接设置其父级
- 添加加载
.dds
、.tga
和.jpeg
纹理格式的能力 - 添加提供自定义
AssetIo
实现的能力
已更改 #
- 将布局反射委托给 RenderResourceContext
- 在无法删除捆绑包时回退到逐个删除组件
- 移植 hecs 派生宏改进
- 使用 glyph_brush_layout 并添加文本对齐支持
- 升级 glam 和 hexasphere
- 灵活的 ECS 参数
- 使 Timer.tick 返回 &Self
- FileAssetIo 在错误中包含完整路径
- 从公共接口中删除了可能轻易违反安全性的 ECS 查询 API
- 更改了查询过滤器 API 以使其更易于理解
- bevy_render:将缓冲区对齐委托给 render_resource_context
- wasm32:非 spirv 着色器专门化
- 将 XComponents 重命名为 XBundle
- 检查冲突的系统资源参数
- 对 TextureAtlasBuilder.finish() 的调整
- 不要浪费时间绘制 is_visible = false 的文本
- 扩展 Texture 资产类型以支持 3D 数据
- 对计时器 API 的重大更改
- 创建了 getter 和 setter,而不是公开结构成员。
- 删除了计时器自动滴答系统
- 添加了如何手动滴答计时器的示例。
- 当任务范围生成 <= 1 个要运行的任务时,立即在调用线程上运行它
- 对 Time API 的重大更改
- 创建了 getter 来获取
Time
状态,并将成员设为私有。 - 修改
Time
的值直接在 bevy 之外不再可能。
- 创建了 getter 来获取
- 在支持的系统上使用
mailbox
而不是fifo
进行 vsync - 切换 winit 大小到逻辑以实现与 DPI 无关
- 更改 bevy_input::Touch API 以匹配类似的 API
- 在“启动后”阶段(而不是“启动”阶段)运行父级更新和变换传播
- 渲染器优化第一轮
- 更改
TextureAtlasBuilder
为预期的 Builder 约定 - 优化文本渲染/共享缓冲区
- hidpi 交换链
- 优化资产 GPU 数据传输
- 摄像机的命名一致性
- Schedule v2
- 对 aarch64-apple-darwin 使用 shaderc
- 更新
Window
的width
和height
方法以返回f32
- 将 Visible 组件从 Draw 中分离
- 现在,用户设置
Draw::is_visible
或Draw::is_transparent
应该设置Visible::is_visible
和Visible::is_transparent
- 现在,用户设置
winit
从版本 0.23 升级到版本 0.24- 默认情况下将 is_transparent 设置为 true,用于 UI 捆绑包
已修复 #
- 修复了 KeyCode 标识符中的拼写错误
- 删除了 TextureCopyNode 中冗余的纹理复制
- 修复了在系统内从 ComputeTaskPool 使用 scope() 时可能发生的死锁
- 不要绘制不可见的文本
- 使用
instant::Instant
以实现 WASM 兼容性 - 修复了 bevy_gltf 中的像素格式转换
- 修复了生成场景时重复的子节点
- 纠正了 UI 深度系统的行为
- 允许在线程本地系统中解除层次结构的绑定
- 修复
RenderResources
索引切片 - 在“启动后”阶段运行父级更新和变换传播
- 通过比较 abs() 穿透深度来修复碰撞检测
- 处理创建交换链时的舍入问题
- 仅更新地图中实体的组件
- 在尝试从尚未加载的资产设置着色器定义时不要恐慌
贡献者 #
对 66 位贡献者表示衷心的感谢,感谢他们使本次发布(以及相关的文档)成为可能!
- @0x6273
- @aclysma
- @ak-1
- @alec-deason
- @AlisCode
- @amberkowalski
- @bjorn3
- @blamelessgames
- @blunted2night
- @bonsairobo
- @cart
- @CleanCut
- @ColdIce1605
- @dallenng
- @e00E
- @easynam
- @frewsxcv
- @FuriouZz
- @Git0Shuai
- @iMplode-nZ
- @iwikal
- @jcornaz
- @Jerald
- @joshuajbouw
- @julhe
- @karroffel
- @Keats
- @Kurble
- @lassade
- @lukors
- @marcusbuffett
- @marius851000
- @memoryruins
- @MGlolenstine
- @milkybit
- @MinerSebas
- @mkhan45
- @mockersf
- @Moxinilian
- @mrk-its
- @mvlabat
- @nic96
- @no1hitjam
- @octtep
- @OptimisticPeach
- @Plecra
- @PrototypeNM1
- @rmsthebest
- @RobDavenport
- @robertwayne
- @rod-salazar
- @sapir
- @sburris0
- @sdfgeoff
- @shirshak55
- @smokku
- @steveyen
- @superdump
- @SvenTS
- @tangmi
- @thebluefish
- @Tiagojdferreira
- @tigregalis
- @toothbrush7777777
- @Veykril
- @yrns