Bevy 0.3:锈迹斑斑的游戏引擎

2020-11-04 05:28:25

在发布Bevy 0.2一个多月后,感谢59位贡献者、122个拉请求和我们慷慨的赞助商,我很高兴地在crates.io!上宣布Bevy 0.3发布。

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

您可以按照这里的说明试用bevy Android示例。虽然很多东西都能用,但请注意这是印出来的很热的东西。一些功能可以工作,而其他功能可能无法工作。现在是深入研究并帮助我们缩小差距的好时机!

您可以按照此处的说明试用bevy iOS示例。这本书也是热印的:一些功能可以工作,其他的可能不会。

@Mr k-its一直在努力扩大Bevy';的WASM支持。在此版本中,我们完成了WASM资产加载。现在,您可以在发布到WASM时加载资源,就像在任何其他平台上一样:

如果资产尚未加载,这将发出一个FETCH()请求以通过HTTP检索资产。

@mrk-its也一直在构建定制的WebGL2bevy_ender后端。它已经很好用了,但还没有完全准备好。期待更多关于这方面的消息!

Fn touch_system(Touches:res<;Touches>;){//您可以迭代所有当前的触摸并检索其状态,如下所示:用于触摸。ITER(){println!(";active touch:{:?}";,touch);}用于触摸。Iter_Just_Press(){println!(";刚才按了{:?}";,touch);}进行触摸。ITER_Just_Release(){println!(";刚刚发布{:?}";,touch);}用于触摸。Iter_Just_Cancel(){println!(";刚才取消了{:?}";,touch);}}

现在,当资产的";句柄引用计数为零时,资产将自动释放。这意味着您不再需要考虑手动释放资产:

//调用load()现在返回一个强句柄:let Handle=ASSET_SERVER。Load(";sprite.png";);//请注意,您不再需要解开()加载的句柄。致胜的人体工程学!//克隆句柄会使引用计数增加一个let Second_HANDLE=HANDLE。Clone();//生成一个精灵并给它我们的Handle命令。Screen(SpriteComponents{材质:材质。添加(句柄。变成()),..。Default::default()});//稍后在某些其他系统中:命令。Despawn(SPRITE_ENTITY);//";sprite.png";没有更多的活动句柄,因此将在下次更新之前将其释放。

在以前的版本中,资源加载器只能生成单一类型的单个资源。在Bevy 0.3中,它们现在可以为任何类型生成任意数量的资源。加载资源(如GLTF文件)时,旧的行为受到极大限制,这可能会生成许多网格、纹理和场景。

有时,您只想从资源源加载特定资源。现在可以像这样加载子资源:

AssetServer现在由AssetIo特性支持。这允许我们从任何我们想要的存储中加载资产。这意味着在桌面上我们现在从文件系统加载,在Android上我们使用Android Asset Manager,在Web上我们使用Fetch()API发出HTTP请求。

资源现在可以依赖于其他资源,这些资源将在加载原始资源时自动加载。当加载可能引用其他资源源的类似场景的内容时,此选项非常有用。我们在新的GLTF加载器中利用了这一点。

这可能会惹恼一些人,但是AssetServer::Load_sync()必须去掉!此API对WASM不友好,鼓励用户出于方便(这会导致搭便车)而阻止游戏执行,并且与新的AssetLoader API不兼容。资产加载现在始终是异步的。Load_sync()的用户应该改为load()他们的资源,检查其系统中的加载状态,并相应地更改游戏状态。

在此之前,GLTF加载器都受到极大的限制。它只能加载GLTF文件中具有单个纹理的第一个网格。对于bevy 0.3,我们利用资源系统的改进编写了一个新的GltfLoader,它将GLTF文件加载为bevy场景,以及文件中的所有网格和纹理。

这里是装载Khronos飞行头盔的小猎犬,它由多个网格和纹理组成!

以下是加载GLTF文件并将其派生为场景的系统的完整代码:

在这个版本中,我终于能够删除我在bevy ECS中真正鄙视的一件事。在Bevy的以前版本中,迭代查询中的组件如下所示:

用于(a,b)in&;mut查询。Iter(){//这里的`&;mu`感觉非常不自然}//或者如果您愿意,可以对查询中的(a,b)执行此操作。ITER()。Iter(){//query.iter().iter()?真的吗?}。

如果让OK(MUT RESULT)=QUERY。Entity(Entity){if设Some((a,b))=Result。Get(){//此处访问组件}}。

//查询中(a,b)的迭代。Iter(){//甜蜜的人体工程学幸福}//实体查找if let OK((a,b))=query。实体(实体){//样板消失!}。

旧的API看起来是这样的是有原因的。这是良好的设计选择的结果,可以防止在并行环境中进行不安全的内存访问。

Query.iter()实际上没有返回迭代器。它返回一个包装器,该包装器在组件存储上持有原子锁。Query.entity()返回的类型也是如此

删除这些包装器类型将允许不安全的行为,因为另一个查询可能会以违反Rust的可变性规则的方式访问相同的组件。

由于Rust编译器中的迭代器实现和怪癖,删除包装器类型会使迭代性能下降约2-3倍。

幸运的是,我们终于找到了解决所有这些问题的方法。新添加的QuerySet允许我们完全删除锁(和包装器类型)。通过完全重写QueryIter,我们能够避免删除包装器带来的性能影响。请继续阅读以了解详细信息!

Bevy ECS现在完全解锁了。在BEVY 0.2中,我们使直接全球访问和每个系统的锁都是自由的。这是可能的,因为bevy ECS调度器确保系统只以遵守Rust的易变性规则的方式并行运行。

Fn Conflicting_Query_System(mut q0:查询<;&;mut A>;,mut q1:查询<;(&;mut A,&;B)>;){设a=q0.。Get_mut(某些实体)。UNWRAP();设(Another_a,b)=q1。Get_mut(某些实体)。Unwork();//aaah!我们有两个对SOME_ENTITY A组件的可变引用!//非常不安全!}。

这些锁确保了第二个q1.get_mut(Ome_Entity)访问死机,保证了我们的安全。在bevy 0.3中,像conflicting_query_system这样的系统在构建时间表时会失败。默认情况下,系统不能有冲突的查询。

但是,在某些情况下,系统需要冲突的查询来执行它需要做的事情。对于这些情况,我们添加了QuerySets:

Fn system(mut查询:QuerySet<;(query<;&;mut A>;,query<;(&;mut A,&;B)>;)>;){用于输入查询。Q0_mut()。查询中(a,b)的iter_mut(){}。Q1_mut()。Iter_mut(){}}

通过将冲突的查询放在QuerySet中,Rust借入检查器保护我们免受不安全的查询访问。

正因为如此,我们能够从query.iter()和query.get(Entity)中删除所有安全检查,这意味着这些方法现在的速度与它们的World对应方法(我们在bevy 0.2中使其无锁)完全一样快。

从查询访问中删除了原型";安全检查";。在这一点上,我们已经验证了给定的查询访问是安全的,因此我们不需要在每次调用时再次检查。

重写了QueryIter以使其更简单(因此更容易控制优化),这允许我们删除迭代器包装器,而不会影响性能。这也解决了一些性能不一致的问题,其中一些系统排列执行得最好,而另一些则不是。现在一切都在快速进行中!

从上游HECS移植了一些性能改进,这改进了大量碎片原型的迭代,并缩短了组件插入时间。

这就是大获全胜的地方。通过从查询系统中移除锁定和安全检查,我们能够显著降低从系统中检索特定实体组件的成本。

我包括了一个与军团ECS(另一个伟大的原型ECS与并行调度程序)的比较,以说明为什么贝维的新方法是如此酷。军团在其系统中公开了一个类似于世界的直接API(称为亚世界)。子世界的入口API无法提前知道将向其传递哪些类型,这意味着它必须进行(相对)昂贵的安全检查,以确保用户不会请求访问他们不应该访问的内容。

Bevy';的调度程序提前对查询进行一次预检查,这使得系统无需任何额外检查即可访问其结果。

测试是在每次系统迭代中查找(和修改)特定实体的组件100,000次。以下是这些测试在每种情况下是如何执行的快速概要:

值得注意的是,使用query.get_component::<;T>;(实体)而不是query.get(实体)确实需要进行安全检查,原因与军团进入API相同。我们不能提前知道调用者将向方法传递什么组件类型,这意味着我们必须检查它以确保它与查询匹配。

有些资源类型不能(或不应该)在线程之间传递。对于窗口、输入和音频等低级API通常都是如此。现在可以将";线程本地资源";添加到资源集合,该资源集合只能使用";线程本地系统";从主线程访问:

//在您的应用设置应用程序中。Add_thread_local_resource(MyResource);//a线程本地系统fn system(world:&;mut World,resources:&;mut Resources){let my_resource=resources。GET_THREAD_LOCAL::<;MyResource>;()。UnWrap();}。

首先,为了提高清晰度,我们将query.get::<;Component>;(Entity)重命名为query.get_component::<;Component>;(entity).。现在,我们使用query.get(Entity)返回特定实体的";完整";查询结果。

为了允许查询的多个并发读取(在安全的情况下),我们添加了单独的query.iter()和query.iter_mut()API,以及query.get(实体)和query.get_mut(实体)。只读查询现在可以通过不变的借阅检索其结果。

Bevy网格过去只需要三个顶点属性:位置、法线和UV。这适用于大多数情况,但也有许多情况需要其他属性,例如动画的顶点颜色&34;或骨骼权重";。Bevy 0.3添加了对自定义顶点属性的支持。网格可以定义他们想要的任何属性,着色器可以使用他们想要的任何属性!

下面是一个示例,说明如何定义使用具有添加的顶点颜色属性的网格的自定义着色器。

渲染网格通常涉及使用顶点索引来减少重复的顶点信息。Bevy过去常常将这些索引的精度硬编码为u16,这对于某些情况来说太小了。现在,渲染管道可以根据配置的索引缓冲区进行专门化,索引缓冲区现在默认为u32以覆盖大多数用例。

转换是正确的,这一点很重要。它们用在引擎的许多部分中,用户代码经常接触到它们,而且它们的计算成本相对较高:尤其是转换层次结构。

在上一个版本中,我们大大简化了bevy的变换系统,使用统一的变换和全局变换,而不是多个独立的平移、旋转和缩放组件(它们与变换和全局变换同步)。这使得面向用户的API/数据流以及底层实现变得更简单。变换分量由4x4矩阵支持。我按下了大绿色的合并按钮...。很高兴我们一劳永逸地解决了变形问题!

原来还有更多的工作要做!@ATHilenius指出,使用4x4矩阵作为仿射变换的真值来源会随着时间的推移累积误差。此外,转换API的使用仍然有点麻烦。在@Termhn的建议下,我们决定用相似性作为真相来源进行调查。这有以下好处:

我们共同认为这是一条很好的前进道路,现在我们重写了一次,甚至更好。是的,这是另一个突破性的变化,但这就是为什么我们给Bevy贴上了实验阶段的标签。现在是时候尽可能多地破坏东西,以确保我们找到经得起时间考验的好API。

FN系统(mut变换:mut<;变换>;){//沿着正x轴变换移动。平移+=ve3::new(1.0,0.0,0.0);//围绕y轴变换旋转180度(Pi)。Rotation*=Quat::From_Rotation_y(PI);//缩放2x变换。Scale*=2.0;}。

与上一个版本相比,这个版本更容易使用,更正确,而且速度也应该会稍微快一些。

新添加的GamepadSettings资源允许开发人员基于每个控制器、每个轴/按钮自定义游戏手柄设置:

Fn system(mut gamepad_settings:ResMut<;GamepadSettings>;){gamepad_settings。AXIS_SETTINGS。Insert(GamepadAxis(Gamepad(0),GamepadAxisType::LeftStickX),AxisSettings{Positive_High:0.8,Positive_Low:0.01,..。Default::Default()},);}。

这将添加所有核心引擎功能(渲染、输入、音频、窗口等)的插件。它是直截了当的,但也非常静态。如果您不想添加所有默认插件,该怎么办?如果您想创建自己的自定义插件集,该怎么办?

为了解决这个问题,我们添加了PluginGroups,它们是可以单独启用或禁用的插件的有序集合:

//这个:APP。Add_default_plugins()//已被替换为:app。Add_plugins(DefaultPlugins)//您可以关闭PluginGroup:APP中的特定插件。Add_plugins_with(DefaultPlugins,|GROUP|{GROUP。禁用::<;RenderPlugin>;()。禁用::<;AudioPlugin>;()});//您可以创建自己的PluginGroups:pub struct HelloWorldPlugins;Iml PluginGroup for HelloWorldPlugins{fn build(&;mut self,group:&;mut PluginGroupBuilder){group。添加(PrintHelloPlugin)。添加(PrintWorldPlugin);}}应用。Add_plugins(HelloWorldPlugins);

Bevy提供与后端无关的窗口API。在此之前,窗口设置只能在应用程序启动时设置一次。如果您想要动态设置窗口设置,则必须直接与窗口后端交互(例如:winit)。

在此版本中,我们添加了使用bevy窗口抽象在运行时动态设置窗口属性的功能:

//该系统将窗口标题动态设置为启动后的秒数。有何不可呢?Fn change_title(time:res<;time>;,mut windows:ResMut<;windows>;){let Window=windows.。Get_primary_mut()。UnWrap();Window。SET_TITLE(FORMAT!(";秒自启动:{}";,时间。秒_自_启动));}。

Bevy crate文档搜索函数现在返回所有子箱的结果(如bevy_spite)。由于为再出口的板条箱生成文档的方式,默认情况下,bevy搜索索引仅覆盖";前奏";。@Mememyruins找到了一种解决此问题的方法,方法是创建新模块并导出这些模块中每个板条箱的内容(而不是给板条箱设置别名)。

现在假定在非线性sRGB颜色空间中提供对颜色的sRGB感知。Color::RGB和Color::RGBA等构造函数将转换为线性sRGB。

新方法Color::RGB_LINEAR和Color::RGBA_LINEAR将接受已在线性sRGB中的颜色(旧行为)。

使用缺少属性的自定义顶点属性进行网格大修(着色器请求,但未由网格定义),bevy将提供填充为零的回退缓冲区。

多次取消绘制实体会导致发出调试级日志消息,而不是死机:#649、#651。

非常感谢59位贡献者,他们使这个版本(以及相关文档)成为可能!