迁移指南

迁移指南: 0.11 到 0.12

Bevy 严重依赖 Rust 语言和编译器的改进。因此,最低支持的 Rust 版本 (MSRV) 是 Rust 的“最新稳定版本”。

动画播放器 API 更新 #

动画

AnimationPlayer 上的一些方法已更改。

  • elapsed 已被删除。使用 seek_time
  • set_elapsed 已被删除。使用 seek_to
  • stop_repeating 已被删除。使用 set_repeat(RepeatAnimation::Never)

如果您手动重置动画状态,可以使用新的 replay 方法。

修复一次性运行器 #

App

app.ready() 已被 app.plugins_state() 替换,后者将返回有关应用程序中插件当前状态的更多详细信息

添加对 KHR_materials_emissive_strength 的支持 #

资产

GLTF 资产加载器现在将在转换为 Bevy 的 StandardMaterial::emissive 时考虑 emissiveStrength。Blender 将使用此字段导出发射材料。从您的 GLTF 文件中删除该字段,或在资产加载后手动修改您的材料,以匹配 Bevy 在先前版本中加载这些文件的方式。

Bevy 资产 V2 #

资产

迁移自定义资产加载器 #

现有的资产加载器需要一些小改动才能与 Bevy 资产 V2 协同工作。

首先,您需要将资产类型添加为加载器的关联类型。此类型称为 Asset,表示加载器生成的“默认资产”的类型。

您还需要添加一个 Settings 类型,它表示在您请求资产时可以传递给加载器的选项。如果您的资产没有设置,那么您可以将其设置为单位类型。

pub struct MyAssetLoader;

impl AssetLoader for MyAssetLoader {
    type Asset = MyAsset;
    type Settings = ();

您还需要对 load 函数进行一些小改动。load 函数现在接受一个 settings 参数,其类型是 Settings

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {

同样,如果您没有使用设置,那么您可以忽略该参数(以“_”作为前缀)。

此外,第二个参数现在是一个 reader 而不是字节向量。如果您的现有代码期望字节,您可以简单地读取整个流

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;

最后,您需要编写返回默认资产的代码。这以前是通过调用 load_context.set_default_asset() 完成的,但在 V2 中,您只需从 load 函数中返回资产即可

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a Self::Settings,
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;
        let mut asset: MyAsset =
            serde_json::from_slice(&bytes).expect("unable to decode asset");
        Ok(asset)
    }

要使用新的加载器,请确保注册加载器和资产类型

app.register_asset_loader(MyAssetLoader)
    .init_asset::<MyAsset>()

资产热重载 #

特性 filesystem_watcher 已重命名为 file_watcher。此外,您不再需要在 AssetPlugin 中手动配置 ChangeWatcher,因为现在在启用特性时会自动配置。

带标签的资产 #

如果您的加载器允许带标签的资产,则有几种不同的方法可以处理它们。最简单的方法是调用 load_context.labeled_asset_scope

// Assume `asset.children` is a HashMap or something.
// Using `drain` here so that we take ownership and don't end up with
// multiple references to the same asset.
asset.children.drain().for_each(|(label, mut item)| {
    load_context.labeled_asset_scope(label, |lc| {
        // Do any additional processing on the item
        // Use 'lc' to load dependencies
        item
    });
});

您可以使用提供的加载上下文 (lc) 来加载其他资产。这些资产将自动注册为带标签资产的依赖项。

使用资产 #

load 的实际调用没有改变

let handle = server.load("path/to/my/asset.json");

// ...

let data = assets.get(&handle).unwrap();

资产事件 #

资产事件有一些变化。事件不再包含 handle 字段,而是包含一个名为 id 的字段

for ev in ev_template.read() {
    match ev {
        AssetEvent::Added { id } => {
            println!("Asset added");
        }
        AssetEvent::LoadedWithDependencies { id } => {
            println!("Asset loaded");
        }
        AssetEvent::Modified { id } => {
            println!("Asset modified");
        }
        AssetEvent::Removed { id } => {
            println!("Asset removed");
        }
    }
}

id 可用于访问资产数据、资产的路径或加载状态。资产句柄也包含一个 id 字段,可用于比较是否相等

AssetEvent::Modified { id } => {
    for cmp in query.iter() {
       if cmp.handle.id() == id {
           println!("Found it!");
       }
    }
}

此外,您可能已经注意到事件集已更改。其中最重要的一个是 LoadedWithDependencies,它告诉您资产及其所有依赖项已完成加载到内存中。

UntypedHandle #

HandleUntyped 已重命名为 UntypedHandleHandleId 已被 UntypedAssetId 及其类型化等效项 AssetId<T> 替换。

构建非类型化句柄的新方法如下所示

// 0.11
const MESH_HANDLE: HandleUntyped =
    HandleUntyped::weak_from_u64(Mesh::TYPE_UUID, 0x1f40128bac02a9b);
// 0.12
const MESH_HANDLE: UntypedHandle =
    UntypedHandle::Weak(UntypedAssetId::Uuid { type_id: TypeId::of::<Mesh>(), uuid: Uuid::from_u128(0x1f40128bac02a9b) });

写时复制 AssetPaths #

资产
// 0.11
AssetPath::new("logo.png", None);

// 0.12
AssetPath::from("logo.png");

// 0.11
AssetPath::new("scene.gltf", Some("Mesh0"));

// 0.12
AssetPath::from("scene.gltf").with_label("Mesh0");

AssetPath 现在序列化为 AssetPath("some_path.extension#Label") 而不是 AssetPath { path: "some_path.extension", label: Some("Label) }

删除 anyhow #

资产
  • bevy_asset 不再导出 anyhow;将其添加到您自己的项目中(如果需要)。
  • AssetLoaderAssetSaver 具有关联类型 Error;定义适当的错误类型(例如,使用 thiserror),或使用预制错误类型(例如,anyhow::Error)。请注意,使用 anyhow::Error 是一个直接替换。
  • AssetLoaderError 已被删除;定义一个新的错误类型,或使用替代方案(例如,anyhow::Error
  • 所有第一方 AssetLoaderAssetSaver 现在返回相关的(且狭窄的)错误类型,而不是单个模糊的类型;匹配特定错误类型,或封装(Box<dyn>thiserroranyhow 等)

使用包装器资产实现非阻塞 load_untyped #

资产

尽可能使用类型化 API 来直接获取对您资产的句柄。如果您不知道类型或需要出于其他原因使用 load_untyped,则 Bevy 0.12 引入了额外的间接层。资产服务器将返回对 LoadedUntypedAsset 的句柄,该句柄将在后台加载。一旦加载完成,就可以从 LoadedUntypedAsset 的字段 handle 中检索到对资产文件的非类型化句柄。

reflect: TypePath 第 2 部分 #

资产
反射
  • 在所有需要稳定性保证和反射环境中使用 TypePath 来代替 std::any::type_name,可以通过以下 API 来使用:
    • 如果你拥有具体类型,而非值,请使用 TypePath::type_path
    • 如果你拥有 dyn Reflect 值,但没有具体类型,请使用 DynamicTypePath::reflect_type_path
    • 如果你要通过注册表使用,或者你想使用 DynamicFoo 的表示类型,请使用 TypeInfo::type_path
  • 从手动 Reflect 实现中删除 type_name
  • TypeInfo 类结构体中使用 type_pathtype_path_table 来代替 type_name
  • 使用 get_with_type_path(_mut) 来代替 get_with_type_name(_mut)

更符合人体工程学的空间音频 #

音频

空间音频现在会自动使用 AudioBundle 的变换以及具有 SpatialListener 组件的实体的变换。

如果你之前手动缩放发射器/监听器位置,你可以使用 AudioPluginspatial_scale 字段来代替。

// 0.11
commands.spawn(
    SpatialAudioBundle {
        source: asset_server.load("sounds/Windless Slopes.ogg"),
        settings: PlaybackSettings::LOOP,
        spatial: SpatialSettings::new(listener_position, gap, emitter_position),
    },
);

fn update(
    emitter_query: Query<(&Transform, &SpatialAudioSink)>,
    listener_query: Query<&Transform, With<Listener>>,
) {
    let listener = listener_query.single();

    for (transform, sink) in &emitter_query {
        sink.set_emitter_position(transform.translation);
        sink.set_listener_position(*listener, gap);
    }
}

// 0.12
commands.spawn((
    SpatialBundle::from_transform(Transform::from_translation(emitter_position)),
    AudioBundle {
        source: asset_server.load("sounds/Windless Slopes.ogg"),
        settings: PlaybackSettings::LOOP.with_spatial(true),
    },
));

commands.spawn((
    SpatialBundle::from_transform(Transform::from_translation(listener_position)),
    SpatialListener::new(gap),
));

简化并行迭代方法 #

ECS

方法 QueryParIter::for_each_mut 已经被弃用,不再起作用。请使用 for_each 来代替,它现在支持可变查询。

// 0.11
query.par_iter_mut().for_each_mut(|x| ...);

// 0.12
query.par_iter_mut().for_each(|x| ...);

方法 QueryParIter::for_each 现在会获取 QueryParIter 的所有权,而不是获取共享引用。

// 0.11
let par_iter = my_query.par_iter().batching_strategy(my_batching_strategy);
par_iter.for_each(|x| {
    // ...Do stuff with x...
    par_iter.for_each(|y| {
        // ...Do nested stuff with y...
    });
});

// 0.12
my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|x| {
    // ...Do stuff with x...
    my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|y| {
        // ...Do nested stuff with y...
    });
});

修复 WorldQuery::fetch 的安全不变式,并简化克隆 #

ECS

fetch 不变式

函数 WorldQuery::fetch 添加了以下安全不变式:

如果 update_component_access 包含任何可变访问,那么调用者必须确保在每个 entity/table_row 的每个原型中,fetch 的调用次数不超过一次。如果 Self 实现 ReadOnlyWorldQuery,那么可以安全地多次调用它。

此不变式对于健全性始终是必要的,但之前没有文档记录。如果你在任何地方手动调用此函数,你应该检查以确保不违反此不变式。

删除 clone_fetch

函数 WorldQuery::clone_fetch 已被删除。关联类型 WorldQuery::Fetch 现在具有 Clone 绑定。

// 0.11
struct MyFetch<'w> { ... }

unsafe impl WorldQuery for MyQuery {
    ...
    type Fetch<'w> = MyFetch<'w>
    unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
        MyFetch {
            field1: fetch.field1,
            field2: fetch.field2.clone(),
            ...
        }
    }
}

// 0.12
#[derive(Clone)]
struct MyFetch<'w> { ... }

unsafe impl WorldQuery for MyQuery {
    ...
    type Fetch<'w> = MyFetch<'w>;
}

选择退出 multi-threaded 特性标记 #

ECS

bevy_ecsbevy_tasks 中的 multi-threaded 特性不再默认启用。但是,它仍然是 bevy 伞形板条箱的默认特性。

如果你使用 bevy 但没有使用 default-features,或者如果你直接依赖 bevy_ecsbevy_tasks,你很可能希望启用它,以允许系统并行运行。

ECS

ScheduleBuildError 现在在更多变体中包含字符串。你可能需要调整处理这些变体的代码。

添加 system.map(...) 用于转换系统的输出 #

ECS

system_adapter 函数已被弃用:请使用 .map 来代替,它是一个轻量级的 .pipe 替代方案。

// 0.11
my_system.pipe(system_adapter::ignore)
my_system.pipe(system_adapter::unwrap)
my_system.pipe(system_adapter::new(T::from))

// 0.12
my_system.map(std::mem::drop)
my_system.map(Result::unwrap)
my_system.map(T::from)

// 0.11
my_system.pipe(system_adapter::info)
my_system.pipe(system_adapter::dbg)
my_system.pipe(system_adapter::warn)
my_system.pipe(system_adapter::error)

// 0.12
my_system.map(bevy_utils::info)
my_system.map(bevy_utils::dbg)
my_system.map(bevy_utils::warn)
my_system.map(bevy_utils::error)

HashMap 替换 EntityMap #

ECS
  • EntityMap::world_scope 的调用可以直接用以下内容替换:map.world_scope(&mut world) -> world.world_scope(&mut map)
  • 对旧版 EntityMap 方法(例如 EntityMap::get)的调用必须显式包含 de/引用符号:let entity = map.get(parent); -> let &entity = map.get(&parent);

重命名 ManualEventIterator #

ECS

类型 ManualEventIterator 已重命名为 EventIterator。此外,ManualEventIteratorWithId 已重命名为 EventIteratorWithId

替换了 FnOnceEntityCommand 实现 #

ECS

1. 新类型 FnOnce

创建一个 EntityCommand 类型,它实现了你之前编写的那个方法。

pub struct ClassicEntityCommand<F>(pub F);

impl<F> EntityCommand for ClassicEntityCommand<F>
where
    F: FnOnce(Entity, &mut World) + Send + 'static,
{
    fn apply(self, id: Entity, world: &mut World) {
        (self.0)(id, world);
    }
}

commands.add(ClassicEntityCommand(|id: Entity, world: &mut World| {
    /* ... */
}));

2. 从 EntityMut 中提取 (Entity, &mut World)

方法 into_world_mut 可用于从 EntityMut 获取对 World 的访问。

let old = |id: Entity, world: &mut World| {
    /* ... */
};

let new = |mut entity: EntityWorldMut| {
    let id = entity.id();
    let world = entity.into_world_mut();
    /* ... */
};

将调度名称移入 Schedule #

ECS

Schedule::newApp::add_schedule

// 0.11
let schedule = Schedule::new();
app.add_schedule(MyLabel, schedule);

// 0.12
let schedule = Schedule::new(MyLabel);
app.add_schedule(schedule);

如果你没有将调度插入世界,而是直接使用调度,你可以使用默认构造函数,它会重用默认标签。

// 0.11
let schedule = Schedule::new();
schedule.run(world);

// 0.12
let schedule = Schedule::default();
schedule.run(world);

Schedules::insert

// 0.11
let schedule = Schedule::new();
schedules.insert(MyLabel, schedule);

// 0.12
let schedule = Schedule::new(MyLabel);
schedules.insert(schedule);

World::add_schedule

// 0.11
let schedule = Schedule::new();
world.add_schedule(MyLabel, schedule);

// 0.12
let schedule = Schedule::new(MyLabel);
world.add_schedule(schedule);

重构 EventReader::iterread #

ECS
  • EventReader::iterEventReader::iter_with_id 的现有用法将必须分别更改为 EventReader::readEventReader::read_with_id
  • ManualEventReader::iterManualEventReader::iter_with_id 的现有用法将必须分别更改为 ManualEventReader::readManualEventReader::read_with_id

IntoSystemSetConfigs 替换 IntoSystemSetConfig #

ECS
  • 使用 App::configure_sets 来代替 App::configure_set
  • 使用 Schedule::configure_sets 来代替 Schedule::configure_set

get_component(_unchecked_mut)Query 移到 QueryState #

ECS

use bevy_ecs::system::QueryComponentError; -> use bevy_ecs::query::QueryComponentError;

修复 tick 列和 ComponentSparseSet 方法的命名 #

ECS

以下方法名称已重命名,从 foo_ticks_barfoo_tick_barticks 现在是单数,tick

  • ComponentSparseSet::get_added_ticksget_added_tick
  • ComponentSparseSet::get_changed_ticksget_changed_tick
  • Column::get_added_ticksget_added_tick
  • Column::get_changed_ticksget_changed_tick
  • Column::get_added_ticks_uncheckedget_added_tick_unchecked
  • Column::get_changed_ticks_uncheckedget_changed_tick_unchecked

set_if_neq 返回布尔值 #

ECS

特征方法 DetectChangesMut::set_if_neq 现在会返回一个布尔值,指示值是否已更改。如果你之前手动实现此函数,现在必须在值被覆盖时返回 true,在值没有被覆盖时返回 false

RemovedComponents::iter/iter_with_id 重命名为 read/read_with_id #

ECS
fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
    // 0.11
    for entity in removed.iter() { /* ... */ }
    for (entity, id) in removed.iter_with_id() { /* ... */ }
    for entity in &mut removed { /* ... */ }

    // 0.12
    for entity in removed.read() { /* ... */ }
    for (entity, id) in removed.read_with_id() { /* ... */ }
    for entity in removed.read() { /* ... */ }
}

删除 States::variants 并移除其派生中的仅枚举限制 #

ECS

States::variants 不再存在。如果你依赖此函数,请考虑使用提供枚举迭代器的库。

将所有标签替换为内联标签 #

ECS
  • BoxedScheduleLabelBox<dyn ScheduleLabel> 替换为 InternedScheduleLabelInterned<dyn ScheduleLabel>

  • BoxedSystemSetBox<dyn SystemSet> 替换为 InternedSystemSetInterned<dyn SystemSet>

  • AppLabelId 替换为 InternedAppLabelInterned<dyn AppLabel>

  • 手动实现 ScheduleLabelAppLabelSystemSet 的类型需要实现

    • dyn_hash,而不是实现 DynHash
    • as_dyn_eq
  • 将标签按值而不是按引用传递给 World::try_schedule_scopeWorld::schedule_scopeWorld::try_run_scheduleWorld::run_scheduleSchedules::removeSchedules::remove_entrySchedules::containsSchedules::getSchedules::get_mut

configure_schedules 添加到 AppSchedules,以将 ScheduleBuildSettings 应用于所有调度 #

ECS
App
  • 没有重大更改。
  • 添加了 Schedule::get_build_settings() 获取器,用于获取调度的 ScheduleBuildSettings
  • 可以替换所有调度的手动配置
// 0.11
for (_, schedule) in app.world.resource_mut::<Schedules>().iter_mut() {
    schedule.set_build_settings(build_settings);
}

// 0.l2
app.configure_schedules(build_settings);

仅在事件系统有实际工作要做时才运行它们 #

ECS
App

Events<T>::update_system 已从类型中分离出来,可以在 bevy_ecs::event::event_update_system 中找到。

通过 EntityMut 允许不连续的可变世界访问 #

ECS
反射

已删除方法 EntityRef::world,以修复查询的健全性问题。如果你需要在使用 EntityRef 时访问 &World,请考虑将世界作为单独的参数传递。

EntityMut 不再能够执行“结构性”世界变异,例如添加或删除组件,或销毁实体。此外,EntityMut::worldEntityMut::world_mutEntityMut::into_world_mutEntityMut::world_scope 已被删除。请使用新添加的类型 EntityWorldMut 来代替,它是一个用于处理 &mut World 的辅助类型。

使生成器类型获取和返回 Self #

ECS
场景

使用 bevy_ecs::DynamicSceneBuilderbevy_ecs::SceneBuilder 时,不要将生成器绑定到变量,而是直接使用它。这些类型上的方法现在会使用 Self,因此如果你没有立即 build,你将需要重新绑定生成器。

// 0.11
let mut scene_builder = DynamicSceneBuilder::from_world(&world);
let scene = scene_builder.extract_entity(a).extract_entity(b).build();

// 0.12
let scene = DynamicSceneBuilder::from_world(&world)
   .extract_entity(a)
   .extract_entity(b)
   .build();

更改 AxisSettings 的活动区域默认值 #

输入

活动区域边界默认值已从 -0.95..=0.95 更改为 -1.0..=1.0,以与更常见的用法保持一致。如果你依赖旧的默认值,可以通过修改 GamepadSettings::default_axis_settings 来更改它。

重命名 bevy_math::rects 转换方法 #

数学

Rect::as_urect 替换为 Rect::as_irect,将 Rect::as_rect 替换为 Rect::as_urect,将 URect::as_urect 替换为 URect::as_irect

为清晰起见,将 Bezier 重命名为 CubicBezier #

数学

将所有 Bezier 引用更改为 CubicBezier

在所有三次曲线生成器中添加 Cubic 前缀 #

数学
  • 重命名:BSpline -> CubicBSpline
  • 重命名:CardinalSpline -> CubicCardinalSpline
  • 重命名:Hermite -> CubicHermite

删除 bevy_dylib 特性 #

元数据

如果你之前使用过 Bevy 的 bevy_dylib 特性,请改用 Bevy 的 dynamic_linking 特性。

# 0.11
cargo run --features bevy/bevy_dylib

# 0.12
cargo run --features bevy/dynamic_linking
[dependencies]
# 0.11
bevy = { version = "0.11", features = ["bevy_dylib"] }

# 0.12
bevy = { version = "0.12", features = ["dynamic_linking"] }

重构 bevy_reflectpath 模块 #

反射

如果您之前匹配由 GetPathParsedPath 方法返回的 Err(ReflectPathError) 值,现在只有解析相关的错误和偏移量是公开可访问的。您始终可以使用 fmt::Display 获取清晰的错误消息,但如果您需要以编程方式访问错误类型,请打开一个问题。

使 ParsedPath 可以传递给 GetPath #

反射

GetPath 现在需要 Reflect。这减少了 Bevy 方面的大量样板代码。如果您在自己的类型上手动实现 GetPath,请与我们联系!

ParsedPath::element[_mut] 不是 ParsedPath 的内在方法,您现在必须导入 ReflectPath。这仅与您没有导入 Bevy 前置代码时相关。

-use bevy::reflect::ParsedPath;
+use bevy::reflect::{ParsedPath, ReflectPath};

parsed_path.element(reflect_type).unwrap()

删除 TypeRegistry 重新导出重命名 #

反射
  • 包装器 bevy crate 重新导出的 TypeRegistry 现在是 TypeRegistryArc
  • 包装器 bevy crate 重新导出的 TypeRegistryInternal 现在是 TypeRegistry

为 ReflectFromPtr 的字段提供 getter #

反射
  • ReflectFromPtr::as_reflect_ptr 现在是 ReflectFromPtr::as_reflect
  • ReflectFromPtr::as_reflect_ptr_mut 现在是 ReflectFromPtr::as_reflect_mut

bevy_reflect: 修复被忽略/跳过的字段顺序 #

反射
  • 标记为 #[reflect(skip_serializing)] 的字段现在必须实现 Default 或使用 #[reflect(default = "path::to::some_func")] 指定自定义默认函数
#[derive(Reflect)]
struct MyStruct {
  #[reflect(skip_serializing)]
  #[reflect(default = "get_foo_default")]
  foo: Foo, // <- `Foo` does not impl `Default` so requires a custom function
  #[reflect(skip_serializing)]
  bar: Bar, // <- `Bar` impls `Default`
}

#[derive(Reflect)]
struct Foo(i32);

#[derive(Reflect, Default)]
struct Bar(i32);

fn get_foo_default() -> Foo {
  Foo(123)
}
  • SerializationData::new 已更改为预期 (usize, SkippedField) 的迭代器而不是仅 usize 的迭代器
// 0.11
SerializationData::new([0, 3].into_iter());

// 0.12
SerializationData::new([
  (0, SkippedField::new(field_0_default_fn)),
  (3, SkippedField::new(field_3_default_fn)),
].into_iter());
  • Serialization::is_ignored_field 已重命名为 Serialization::is_field_skipped
  • 标记为 #[reflect(skip_serializing)] 的字段现在包含在反序列化输出中。这可能会影响期望这些字段不存在的逻辑。

在 Camera::physical_viewport_rect 中返回 URect 而不是 (UVec2, UVec2) #

渲染
// 0.11
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
    let camera = camera_query.single();
    let Some((min, max)) = camera.physical_viewport_rect() else { return };
    dbg!(min, max);
}

// 0.12
fn view_physical_camera_rect(camera_query: Query<&Camera>) {
    let camera = camera_query.single();
    let Some(URect { min, max }) = camera.physical_viewport_rect() else { return };
    dbg!(min, max);
}

更新 bevy_window::PresentMode 以反映 wgpu::PresentMode #

渲染

在手动调整窗口呈现模式时处理 bevy_window::PresentMode::FifoRelaxed

对 MeshUniform 使用 GpuArrayBuffer #

渲染

以旧方式访问单个网格对象的着色器 Mesh 结构的 model 成员,其中每个 MeshUniform 都存储在它自己的动态偏移量处

struct Vertex {
    @location(0) position: vec3<f32>,
};

fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;
    out.clip_position = mesh_position_local_to_clip(
        mesh.model,
        vec4<f32>(vertex.position, 1.0)
    );
    return out;
}

新方法需要索引到批次的 Mesh 数组中

struct Vertex {
    @builtin(instance_index) instance_index: u32,
    @location(0) position: vec3<f32>,
};

fn vertex(vertex: Vertex) -> VertexOutput {
    var out: VertexOutput;
    out.clip_position = mesh_position_local_to_clip(
        mesh[vertex.instance_index].model,
        vec4<f32>(vertex.position, 1.0)
    );
    return out;
}

请注意,使用 instance_index 是将每个对象索引传递到着色器的默认方式,但如果您希望进行自定义渲染方法,您可以按照您喜欢的任何方式传递它。

减小 MeshUniform 的大小以提高性能 #

渲染

Sphere::intersects_obbFrustum::intersects_obb 现在接受 Affine3A 而不是 Mat4。您可以使用 Affine3A::from_mat4Transform::compute_affine 获取 Affine3A

MeshUniform 现在将其当前和之前的模型变换存储为 4x3 矩阵。为 bevy_pbr::mesh_functions 添加了帮助器函数来解包数据。

// 0.11
var model = mesh[instance_index].model;

// 0.12
#import bevy_pbr::mesh_functions::affine_to_square

var model = affine_to_square(mesh[instance_index].model);

重新排序渲染集,重构 bevy_sprite 以利用它 #

渲染
  • 资产(如材质和网格)现在应该在 PrepareAssets 中创建,例如 prepare_assets<Mesh>
  • 将实体排队到 RenderPhase 继续在 Queue 中完成,例如 queue_sprites
  • 准备资源(纹理、缓冲区等)现在应该在 PrepareResources 中完成,例如 prepare_prepass_texturesprepare_mesh_uniforms
  • 准备绑定组现在应该在 PrepareBindGroups 中完成,例如 prepare_mesh_bind_group
  • 任何批处理或实例化现在可以在 Prepare 中完成,其中阶段项目的顺序已知,例如 prepare_sprites

ComputedVisibility 分成两个组件以允许进行准确的更改检测并加快可见性传播 #

渲染

ComputedVisibility 组件已拆分为 InheritedVisibilityViewVisibility。将 ComputedVisibility::is_visible_in_hierarchy 的任何用法替换为 InheritedVisibility::get,并将 ComputedVisibility::is_visible_in_view 替换为 ViewVisibility::get

// 0.11:
commands.spawn(VisibilityBundle {
    visibility: Visibility::Inherited,
    computed_visibility: ComputedVisibility::default(),
});

// 0.12:
commands.spawn(VisibilityBundle {
    visibility: Visibility::Inherited,
    inherited_visibility: InheritedVisibility::default(),
    view_visibility: ViewVisibility::default(),
});
// 0.11:
fn my_system(q: Query<&ComputedVisibility>) {
    for vis in &q {
        if vis.is_visible_in_hierarchy() {

// 0.12:
fn my_system(q: Query<&InheritedVisibility>) {
    for inherited_visibility in &q {
        if inherited_visibility.get() {
// 0.11:
fn my_system(q: Query<&ComputedVisibility>) {
    for vis in &q {
        if vis.is_visible_in_view() {

// 0.12:
fn my_system(q: Query<&ViewVisibility>) {
    for view_visibility in &q {
        if view_visibility.get() {
// 0.11:
fn my_system(mut q: Query<&mut ComputedVisibility>) {
    for vis in &mut q {
        vis.set_visible_in_view();

// 0.12:
fn my_system(mut q: Query<&mut ViewVisibility>) {
    for view_visibility in &mut q {
        view_visibility.set();

清理 visibility 模块 #

渲染

check_visibility 系统的 Option<&NoFrustumCulling> 参数已被 Has<NoFrustumCulling> 替换,如果您正在手动调用它,则应更改类型以使其匹配

允许其他插件创建渲染器资源 #

渲染

RenderPlugin 现在接受 RenderCreation 枚举而不是 WgpuSettingsRenderSettings::default() 返回 RenderSettings::Automatic(WgpuSettings::default())RenderSettings 也实现了 From<WgpuSettings>

// 0.11
RenderPlugin {
    wgpu_settings: WgpuSettings {
    ...
    },
}

// 0.12
RenderPlugin {
    render_creation: RenderCreation::Automatic(WgpuSettings {
    ...
    }),
}
// or
RenderPlugin {
    render_creation: WgpuSettings {
    ...
    }.into(),
}

对渲染世界实体存储使用 EntityHashMap<Entity, T> 以获得更好的性能 #

渲染

以前,渲染应用程序从主世界中提取网格实体及其组件数据,并将它们作为实体和组件存储在渲染世界中。现在它们被提取到本质上是 EntityHashMap<Entity, T> 中,其中 T 是包含适当数据组的结构。这意味着,虽然提取集系统将继续对主世界运行提取查询,但它们会将数据存储在哈希映射中。此外,后面集合中的系统将需要在可用资源中查找实体,例如 RenderMeshInstances,或者维护自己的 EntityHashMap<Entity, T> 用于自己的数据。

// 0.11
fn queue_custom(
    material_meshes: Query<(Entity, &MeshTransforms, &Handle<Mesh>), With<InstanceMaterialData>>,
) {
    ...
    for (entity, mesh_transforms, mesh_handle) in &material_meshes {
        ...
    }
}

// 0.12
fn queue_custom(
    render_mesh_instances: Res<RenderMeshInstances>,
    instance_entities: Query<Entity, With<InstanceMaterialData>>,
) {
    ...
    for entity in &instance_entities {
        let Some(mesh_instance) = render_mesh_instances.get(&entity) else { continue; };
        // The mesh handle in `AssetId<Mesh>` form, and the `MeshTransforms` can now
        // be found in `mesh_instance` which is a `RenderMeshInstance`
        ...
    }
}

方向光/聚光灯阴影的 PCF #

渲染

方向光或聚光灯投射的阴影现在具有更平滑的边缘。要恢复到旧的行为,请将 ShadowFilteringMethod::Hardware2x2 添加到您的摄像机中。

对线框使用 Material #

渲染

WireframePipeline 已被删除。如果您直接使用它,请创建一个问题来解释您的用例。

延迟渲染器 #

渲染

pbr 着色器清理 #

渲染

在自定义材质着色器中

  • pbr_functions::pbr 不再调用 pbr_functions::alpha_discard。如果您在带有 alpha 蒙版模式的自定义着色器中使用 pbr 函数,现在您还需要手动调用 alpha_discard
  • bevy_pbr::mesh_vertex_output 的导入重命名为 bevy_pbr::forward_io
  • MeshVertexOutput 的实例重命名为 VertexOutput

在自定义材质预处理着色器中

  • VertexOutput::clip_position 的实例重命名为 VertexOutput::position

*_PREPASS 着色器定义清理 #

渲染

在使用 bevy_pbr::prepass_utilsprepass_depth()prepass_normal()prepass_motion_vector())中的函数时,如果可能禁用这些预处理,则现在应该使用相应的 #ifdef 保护符包装您的调用,(#ifdef DEPTH_PREPASS#ifdef NORMAL_PREPASS#ifdef MOTION_VECTOR_PREPASS),在适用时提供回退逻辑。

允许对 StandardMaterial 进行扩展 #

渲染

AsBindGroup 的手动实现将需要调整,更改非常简单,可以在例如 texture_binding_array 示例的差异中看到。

可变 MeshPipeline 视图绑定组布局 #

渲染

MeshPipeline::view_layoutMeshPipeline::view_layout_multisampled 已被一个私有数组替换,以适应可变视图绑定组布局。要为当前管道状态获取视图绑定组布局,请使用新的 MeshPipeline::get_view_layout()MeshPipeline::get_view_layout_from_key() 方法。

更新着色器导入 #

渲染

naga_oil 0.10 重做了导入机制以支持更多语法使其更像 Rust,并在导入之前测试项目使用以确定哪些导入是模块,哪些是项目,这允许

  • 使用 Rust 风格的导入
#import bevy_pbr::{
    pbr_functions::{alpha_discard as discard, apply_pbr_lighting},
    mesh_bindings,
}
  • 导入部分路径
#import part::of::path
// ...
path::remainder::function();

这将调用 part::of::path::remainder::function

  • 在不导入的情况下使用完全限定的路径
// #import bevy_pbr::pbr_functions
bevy_pbr::pbr_functions::pbr()
  • 在不限定的情况下使用导入的项目
#import bevy_pbr::pbr_functions::pbr
// for backwards compatibility the old style is still supported:
// #import bevy_pbr::pbr_functions pbr
// ...
pbr()
  • 允许大多数导入的项目以 _ 和数字结尾 (naga_oil#30)。仍然不允许结构成员以 _ 或数字结尾,但这已经有所进步。
  • 绝大多数现有的着色器代码将在无需更改的情况下运行,但会发出“已弃用”警告以提醒旧式导入。这些可以通过 allow-deprecated 功能来抑制。
  • 部分打破了覆盖(据我所知,还没有人使用过这些)——现在覆盖只有在覆盖模块被添加为 Composer::make_naga_moduleComposer::add_composable_module 的参数中的附加导入时才会应用。这是支持确定导入是模块还是项目所必需的。

绑定组条目 #

渲染
  • RenderDevice::create_bind_group({BindGroupDescriptor { label, layout, entries }) 的调用必须改为 RenderDevice::create_bind_group(label, layout, entries)
  • 如果 label 已指定为 "bind_group_name".into(),则需要将其更改为 "bind_group_name"Some("bind_group_name")None 仍然有效,但 Some("bind_group_name") 可以选择性地简化为 "bind_group_name"

检测 dds 纹理的立方体贴图 #

渲染

如果您正在匹配 TextureError,则需要添加一个新的分支来处理 TextureError::IncompleteCubemap

为 Image 添加便捷方法 #

渲染

将对 Image::size() 方法的调用替换为 size_f32()。将对 Image::aspect_2d() 方法的调用替换为 aspect_ratio()

使用“镜面遮挡”一词来一致地熄灭环境光和环境贴图灯光上的菲涅耳 #

渲染
  • 如果环境光和环境贴图灯光上的菲涅耳高光在您的材质中不再可见,请确保您使用的是更高的、物理上更合理的 reflectance 值 (⪆ 0.35)。

修复雾色不准确 #

渲染

FogSettings 结构中的颜色 (colordirectional_light_color) 现在以线性空间发送到 GPU。如果您使用的是 Color::rgb()/Color::rgba() 并希望保留以前的颜色,您可以通过切换到 Color::rgb_linear()/Color::rgba_linear() 来快速修复它。

图像采样器改进 #

渲染
  • 使用Image API 时,使用ImageSamplerDescriptor 而不是wgpu::SamplerDescriptor
  • 如果编写与Image一起使用的自定义 wgpu 渲染器功能,请调用&image_sampler.as_wgpu() 以转换为 wgpu 描述符。

StandardMaterial 光线透射 #

渲染
  • SsaoPipelineKey::temporal_noise 已重命名为SsaoPipelineKey::temporal_jitter
  • TAA 着色器定义(由相机中是否存在TemporalAntiAliasSettings 组件控制)已被TEMPORAL_JITTER 着色器定义(由相机中是否存在TemporalJitter 组件控制)取代。
  • MeshPipelineKey::TAA 已被MeshPipelineKey::TEMPORAL_JITTER 取代。
  • TEMPORAL_NOISE 着色器定义已与TEMPORAL_JITTER 合并。

增加默认法线偏移以避免常见伪影 #

渲染

DirectionalLightSpotLight 的默认shadow_normal_bias 值已更改以适应新的阴影 PCF 更改引入的伪影。 虽然不太可能(尤其是在这些值下新的 PCF 阴影行为),但如果您的场景需要较低的偏差并且依赖于先前的默认值,您可能需要手动调整此值。

使DirectionalLight Cascades 计算对CameraProjection 通用 #

渲染

如果您有一个实现CameraProjection 的组件MyCustomProjection

  • 您需要实现一个新的必需关联方法get_frustum_corners,该方法返回给定z_nearz_far 的视锥体子集的角点数组,以局部相机空间表示。
  • 您现在可以在SimulationLightSystems::UpdateDirectionalLightCascades 中的clear_directional_light_cascades 之后添加build_directional_light_cascades::<MyCustomProjection> 系统,以使您的投影能够与定向光源一起工作。

将蒙皮代码移动到单独的模块 #

渲染
动画

重命名蒙皮系统、资源和组件

  • extract_skinned_meshes -> extract_skins
  • prepare_skinned_meshes -> prepare_skins
  • SkinnedMeshUniform -> SkinUniform
  • SkinnedMeshJoints -> SkinIndex

将场景生成器系统移动到 SpawnScene 调度程序 #

场景

scene_spawner_system 已移至新的SpawnScene 调度程序,该调度程序在UpdatePostUpdate 之间运行。

如果您之前在Update 中对自己的系统进行排序以在scene_spawner_system 之前运行,则可能不再需要这样做。 如果您的系统需要在scene_spawner_system 之后运行,则应将其移至SpawnScenePostUpdate 调度程序。

从 TaskPoolOptions 中删除 Resource 并添加 Debug #

任务

如果出于某种原因,任何人仍在将TaskPoolOptions 用作 Resource,他们现在必须使用包装器类型

#[derive(Resource)]
pub struct MyTaskPoolOptions(pub TaskPoolOptions);

全局 TaskPool API 改进 #

任务

ComputeTaskPool::initAsyncComputeTaskPool::initIoTaskPool::init 的使用应更改为::get_or_init

统一FixedTimeTime,同时修复多个问题 #

时间
  • 将所有访问raw_delta()raw_elapsed() 和相关方法的Res<Time> 实例更改为Res<Time<Real>>delta()elapsed() 等。
  • 将对Res<FixedTime> 中的period 的访问更改为Res<Time<Fixed>> 并使用delta()
  • 默认时间步长已从 60 Hz 更改为 64 Hz。如果您希望恢复旧的行为,请使用app.insert_resource(Time::<Fixed>::from_hz(60.0))
  • app.insert_resource(FixedTime::new(duration)) 更改为app.insert_resource(Time::<Fixed>::from_duration(duration))
  • app.insert_resource(FixedTime::new_from_secs(secs)) 更改为app.insert_resource(Time::<Fixed>::from_seconds(secs))
  • system.on_fixed_timer(duration) 更改为system.on_timer(duration)。放置在FixedUpdate 调度程序中的系统中的计时器会自动使用固定时间时钟。
  • ResMut<Time> 调用更改为ResMut<Time<Virtual>> 调用,用于pause()is_paused()set_relative_speed() 和相关方法。 API 相同,不同之处在于relative_speed() 将返回实际的最后一步相对速度,而effective_relative_speed() 如果时间暂停将返回 0.0,并且对应于当前帧更新开始时设置的速度。

ContentSizemeasure_func 字段的默认值更改为 None。 #

UI

ContentSize 的默认值现在将其measure_func 设置为None,而不是返回Vec2::ZERO 的固定大小度量。 助手函数fixed_size 可以使用ContentSize::fixed_size(Vec2::ZERO) 调用,以获得以前的行为。

UiScale 更改为元组结构体 #

UI

UiScale 的初始化(如UiScale { scale: 1.0 })替换为UiScale(1.0)

清理一些 bevy_text pipeline.rs #

UI
  • measure_text_systemResMut<TextPipeline> 参数不再存在。 如果您是手动调用此系统,则应删除该参数。
  • TextMeasureInfo{min,max}_width_content_size 字段分别重命名为minmax
  • 如果手动构建TextMeasureInfo,则其他更改也可能会破坏您的代码。 请考虑使用新的TextMeasureInfo::from_text 来构建它。
  • TextPipeline::create_text_measure 已被TextMeasureInfo::from_text 取代。

使GridPlacement 的字段非零并添加访问器函数。 #

UI

GridPlacement 的构造函数不再接受值为0 的值。 给定任何值为0 的参数,它们将使用GridPlacementError 抛出异常。

删除Valtry_* 算术方法 #

UI

Valtry_* 算术方法已被删除。 要对Val 执行算术运算,请使用模式匹配对其进行解构。

Val evaluate 重命名为resolve 并实现视口变体支持 #

UI
  • 重命名了以下Val 方法并添加了viewport_size 参数
    • evaluate to resolve
    • try_add_with_size to try_add_with_context
    • try_add_assign_with_size to try_add_assign_with_context
    • try_sub_with_size to try_sub_with_context
    • try_sub_assign_with_size to try_sub_assign_with_context

TextLayoutInfo::size 应该保存文本的绘制大小,而不是缩放后的值。 #

UI

TextLayoutInfosize 值以逻辑像素存储,并且已被重命名为logical_size。 不再需要除以窗口的比例因子来获取逻辑大小。

每个根节点都有一个独立的隐式视口节点,并将视口节点设为Display::Grid #

UI
  • Bevy UI 现在在独立的布局上下文中独立地布局根节点。 如果您依赖于根节点能够影响彼此的布局,那么您可能需要将它们包装在一个根节点中。
  • 隐式视口节点(包含每个用户指定的根节点)现在为Display::Grid,其align_itemsjustify_items 都设置为Start。 如果您之前依赖于隐式设置,您可能需要在根节点中添加height: Val::Percent(100.)

num_font_atlases 重命名为len #

UI

FontAtlasSetnum_font_atlases 方法已被重命名为len

各种辅助功能 API 更新。 #

UI

AccessibilityRequested 的直接访问更改为使用AccessibilityRequested.::get()/AccessibilityRequested::set()

// 0.11
use std::sync::atomic::Ordering;

// To access
accessibility_requested.load(Ordering::SeqCst)
// To update
accessibility_requested.store(true, Ordering::SeqCst);

// 0.12
// To access
accessibility_requested.get()
// To update
accessibility_requested.set(true);

为 bevy_text 添加更多文档。 #

UI

bevy_text 中对TextSettings.max_font_atlases 的使用必须更改为TextSettings.soft_max_font_atlases

更新 UI 对齐文档 #

UI

JustifyContents 枚举已扩展为包含JustifyContents::Stretch

添加选项以切换窗口控制按钮 #

窗口

Window 结构体中添加了一个enabled_buttons 成员,用户可以通过该成员启用或禁用特定的窗口控制按钮。

改进bevy_winit 文档 #

窗口
  • UpdateMode::Reactive { max_wait: .. } -> UpdateMode::Reactive { wait: .. }
  • UpdateMode::ReactiveLowPower { max_wait: .. } -> UpdateMode::ReactiveLowPower { wait: .. }

解决 naga/wgpu WGSL instance_index -> GLSL gl_InstanceID 在 WebGL2 上的错误 #

渲染

之前的着色器代码

struct Vertex {
    @builtin(instance_index) instance_index: u32,
...
}

@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
    // ...
    var model = mesh[vertex_no_morph.instance_index].model;
}

之后

#import bevy_render::instance_index

struct Vertex {
    @builtin(instance_index) instance_index: u32,
    // ...
}

@vertex
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
    // ...
    let instance_index = bevy_render::instance_index::get_instance_index(vertex_no_morph.instance_index);
    var model = mesh[instance_index].model;
}

删除&mut EventReaderIntoIterator 实现 #

无区域标签

&mut EventReader 不再实现IntoIterator。 将for foo in &mut events 替换为for foo in events.iter()

更新默认ClearColor 以更好地匹配 Bevy 的品牌 #

渲染

默认应用程序背景颜色已更改。 要使用旧的默认值,请添加ClearColor 资源。

App::new()
    .insert_resource(ClearColor(Color::rgb(0.4, 0.4, 0.4)))
    .add_plugins(DefaultPlugins)

视图变换 #

mesh_functions::mesh_position_world_to_clip 已被移动并重命名为view_transformations::position_world_to_clip。 它现在还接受vec3 而不是vec4,因此您需要使用vec4.xyz 来获取vec3

// 0.11
#import bevy_pbr::mesh_functions::mesh_position_world_to_clip
fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
    let world_position = mesh_position_local_to_world(model, vertex_position);
    return mesh_position_world_to_clip(world_position);
}

// 0.12
#import bevy_pbr::view_transformations::position_world_to_clip;
fn mesh_position_local_to_clip(model: mat4x4<f32>, vertex_position: vec4<f32>) -> vec4<f32> {
    let world_position = mesh_position_local_to_world(model, vertex_position);
    return position_world_to_clip(world_position.xyz);
}