Bevy 0.4

发布于 2020 年 12 月 19 日 由 Carter Anderson ( GitHub 的吉祥物 Octocat 的剪影 @cart X(原 Twitter)的旧标志,一只灰色的飞鸟 @cart_cart YouTube 标志,一个指向右边的三角形在圆角矩形中 cartdev )

在发布 Bevy 0.3 一个多月后,感谢 66 位贡献者、178 个 pull 请求以及我们 慷慨的赞助商,我很高兴地宣布 Bevy 0.4 版本已在 crates.io 上发布!

对于那些不了解的人,Bevy 是一个用 Rust 构建的,令人耳目一新的简单数据驱动的游戏引擎。您可以查看 快速入门指南 来开始使用。Bevy 也是永久免费和开源的!您可以在 GitHub 上获取完整的 源代码

以下是本版本的一些亮点

WASM + WebGL2 #

作者: @mrk-its

Bevy 现在拥有一个 WebGL2 渲染后端!@mrk-its 一直在努力构建 Bevy WebGL2 插件 并扩展 bevy_render 以满足 Web 的需求。他还创建了一个很棒的网站来展示在 Web 上运行的各种 Bevy 示例和游戏。

我认为结果不言而喻

Bevy WebGL2 展示 #

webgl2 showcase

跨平台主函数 #

作者: @cart

在大多数受支持的 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();
}

实时着色器重载 #

作者: @yrns

Bevy 现在可以更新运行时对着色器的更改,让您在不重新启动应用程序的情况下获得即时反馈。此视频没有加速!

ECS 优化 #

作者: @cart

如果没有新一轮的 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 组件发生了更改的实体的 TransformVelocity 组件的不可变引用”。

首先,通过 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())
        )
    );

Schedule

运行条件 #

您可以为任何 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 系统看起来很不错,有时可以节省一些输入。为什么要移除它们?

  1. For-each 系统在根本上存在一些限制。它们无法迭代删除的组件、过滤、控制迭代或同时使用多个查询。这意味着一旦需要这些功能,就需要将它们转换为“查询系统”。
  2. Bevy 通常应该有一种“做事方式”。For-each 系统是定义一小部分系统类型的稍微更符合人体工程学的方式。这迫使人们在不需要的时候做出“设计决策”。它还会使示例和教程根据人们对一种或另一种方式的偏好而变得不一致。
  3. 对于新手来说,存在一些在我们的支持论坛中不断出现并让新手感到困惑的“陷阱”
    • 用户期望 &mut T 查询在 foreach 系统中工作(例如:fn system(a: &mut A) {})。这些无法工作,因为我们要求 Mut<T> 跟踪指针以确保更改跟踪始终按预期工作。等效的 Query<&mut A> 可以工作,因为我们可以在迭代查询时返回跟踪指针。
    • 一个“在某些条件下运行此 for-each 系统”的错误,这个错误很常见,以至于我们不得不在 Bevy Book 中对其进行说明。
  4. 它们增加了编译时间。移除 for-each 系统为我节省了大约 ~5 秒的 Bevy 清理编译时间)。
  5. 它们的内部实现需要一个复杂的宏。这会影响可维护性。

状态 #

作者: @cart

根据大众需求,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 改进 #

作者: @iwikal, @FuriouZz, @rod-salazar

Bevy 的 GLTF 加载器现在导入相机。这是一个在 Blender 中简单的场景设置。

gltf_camera_blender

这就是它在 Bevy 中的样子(照明不同,因为我们还没有导入灯光)。

gltf_camera_bevy

还有一些其他改进。

  • 从 GLTF 导入图像时进行像素格式转换。
  • 默认材质加载。
  • 层次结构修复。

将场景作为子级生成 #

作者: @mockersf

场景现在可以像这样作为子级生成。

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"));
    });

通过在父级下生成,这使您可以执行诸如平移/旋转/缩放同一场景的多个实例之类的操作。

scene_children

动态链接 #

作者: @bjorn3, @cart

@bjorn3 发现您可以强制 Bevy 进行动态链接。

显着减少了迭代编译时间。查看使用 快速编译配置以及动态链接来编译对 3d_scene.rs 示例所做更改需要多长时间。

fast dynamic

编译对 3d_scene 示例的更改所需时间(以秒为单位,越少越好) #

fast_compiles

我们添加了一个货物特性,以便在开发期间轻松启用动态链接。

# for a bevy app
cargo run --features bevy/dynamic

# for bevy examples
cargo run --features dynamic --example breakout

请记住,在发布游戏时应禁用该特性。

文本布局改进 #

作者: @AlisCode, @tigregalis

以前的 Bevy 版本使用自定义的、简单的文本布局系统。它存在许多错误和限制,例如臭名昭著的“波浪文本”错误。

wavy_text

新的文本布局系统使用 glyph_brush_layout,它修复了布局错误并添加了许多新的布局选项。请注意,示例中使用的“Fira Sans”字体具有一些风格上的“波浪”... 这不是错误。

text_layout

渲染器优化 #

作者: @cart

Bevy 的渲染 API 的设计是为了易于使用和扩展。我想要先确定一个好的 API,但这导致了一些性能 TODO,这些 TODO 导致了一些相当严重的开销。

对于Bevy 0.4,我决定尽可能地解决这些 TODO。还有很多事情要做(例如实例化和批处理),但 Bevy 的性能已经比以前好多了。

将所有内容增量化 #

Bevy 的大多数高级渲染抽象都是为增量更新而设计的,但当我第一次构建引擎时,ECS 更改检测尚未实现。现在我们拥有了所有这些不错的优化工具,使用它们是有意义的!

对于第一轮优化,我尽可能地进行了增量化。

  • 向 RenderResourceNode、Sprites 和 Transforms 添加了更改检测,当这些值不改变时,这提高了性能。
  • 仅在资产更改时同步资产 GPU 数据。
  • 在所有引用资产的实体之间共享资产 RenderResourceBindings。
  • 网格提供程序系统现在仅在需要时更新网格专业化。
  • 停止每帧清除绑定组,并每隔一帧删除陈旧的绑定组。
  • 缓存不匹配的渲染资源绑定结果(这可以防止每实体每帧的冗余计算)。
  • 当状态实际上没有更改时,不要发送渲染通道状态更改命令。

绘制 10,000 个静态 Sprites 所需的帧时间(以毫秒为单位,越少越好) #

bevy_round1_static

绘制 10,000 个移动 Sprites 所需的帧时间(以毫秒为单位,越少越好) #

bevy_round1_dynamic

优化文本渲染(以及其他立即渲染) #

文本渲染(以及使用 SharedBuffers 立即渲染抽象的任何其他内容)在以前的 Bevy 版本中非常慢。这是因为 SharedBuffers 抽象是一个占位符实现,它实际上没有共享缓冲区。通过实现“真实”的 SharedBuffers 抽象,我们获得了相当大的文本渲染速度提升。

绘制“text_debug”示例所需的时间(以毫秒为单位,越少越好) #

text_rendering

邮箱 Vsync #

Bevy 现在默认使用 wgpu 的“邮箱 Vsync”。这减少了支持该功能的平台上的输入延迟。

反射 #

作者: @cart

Rust 有一个相当大的“反射”差距。对于那些不知道的人来说,“反射”是一类语言特性,使您能够在运行时与语言结构进行交互。它们为传统上静态的语言概念添加了一种“动态性”。

我们在 Rust 中有一些反射的片段,例如 TypeIdtype_name。但是当涉及到与数据类型交互时... 我们还没有任何东西。这很不幸,因为某些问题本质上是动态的。

当我第一次构建 Bevy 时,我决定引擎将从这些功能中受益。反射是场景系统、类似 Godot(或 Unity)的属性动画系统和编辑器检查工具的良好基础。我构建了 bevy_propertybevy_type_registry 板条箱来满足这些需求。

它们完成了工作,但它们是专门针对 Bevy 的需求量身定制的,充满了自定义术语(而不是直接反映 Rust 语言结构),没有处理特征,并且对如何访问数据有许多基本限制。

在此版本中,我们用新的 bevy_reflect 板条箱替换了旧的 bevy_propertybevy_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, &registry);
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(&registry);
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 纹理资产 #

作者: @bonsairobo

Texture 资产现在支持 3D 纹理。新的 array_texture.rs 示例说明了如何加载 3D 纹理并从每个“层”进行采样。

array_texture

日志记录和分析 #

作者: @superdump, @cart

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");

这些行将生成漂亮的终端日志。

logs

tracing 具有许多有用的功能,例如结构化日志记录和过滤。 查看他们的文档以获取更多信息。

分析 #

我们添加了通过启用 trace 特性向所有 ECS 系统添加“跟踪跨度”的选项。我们还内置了对 tracing-chrome 扩展的支持,该扩展会导致 Bevy 以“Chrome 跟踪”格式输出跟踪。

如果您使用 cargo run --features bevy/trace,bevy/trace_chrome 运行您的应用程序,您将获得一个 JSON 文件,可以通过访问 chrome://tracing URL 在 Chrome 浏览器中打开它。

profiling

@superdump 为上游 tracing_chrome 添加了对这些漂亮的“跨度名称”的支持。

HIDPI #

作者: @mockersf, @blunted2night, @cart

Bevy 现在可以正确处理 HIDPI/Retina/高像素密度显示器。

  • 操作系统报告的像素密度现在在创建窗口时被考虑在内。如果 Bevy 应用程序在 2x 像素密度显示器上请求一个 1280x720 的窗口,它将创建一个 2560x1440 的窗口。
  • 窗口宽度/高度现在以“逻辑单位”报告(在上面的示例中为 1280x720)。可以使用 window.physical_width()window.physical_height() 方法仍然可以使用物理单位。
  • 窗口“交换链”使用物理分辨率创建,以确保我们仍然具有清晰的渲染(在上面的示例中为 2560x1440)。
  • Bevy UI 已适应以正确处理 HIDPI 缩放。

这里还需要做一些工作。虽然 Bevy UI 以清晰的 HIDPI 分辨率渲染图像和框,但文本仍然使用逻辑分辨率渲染,这意味着它在 HIDPI 显示器上不会像它可以的那样清晰。

计时器改进 #

作者: @amberkowalski, @marcusbuffett, @CleanCut

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

@aclysma 更改了 Bevy 任务调度方式,这使 breakout.rs 示例游戏的性能提高了 ~20%,并解决了当任务池配置为只有一个线程时出现的 死锁。当只有一个任务要运行时,任务现在立即在调用线程上执行,这减少了将工作转移到其他线程/阻塞等待它们完成的开销。

Apple Silicon 支持 #

作者: @frewsxcv, @wyhaya, @scoopr

由于 winit (@scoopr) 和 coreaudio-sys (@wyhaya) 的上游工作,Bevy 现在可以在 Apple 硅芯片上运行。@frewsxcv 和 @wyhaya 更新了 Bevy 的依赖项,并验证了它可以在 Apple 的新芯片上构建和运行。

新示例 #

Bevy 贡献者 #

作者:@karroffel

@karroffel 添加了一个有趣的示例,将每个 Bevy 贡献者表示为一只“Bevy 鸟”。它从 Git 中抓取最新的贡献者列表。

contributors

BevyMark #

作者:@robdavenport

一个“类似 bunnymark 的”基准测试,说明了 Bevy 的精灵渲染性能。这在实现上面提到的渲染器优化时很有用。

bevymark

更改日志 #

添加 #

已更改 #

已修复 #

贡献者 #

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