由于88个贡献者,283次拉出请求,以及我们慷慨的赞助商,我很高兴宣布封口的BEVY 0.5释放!
对于那些不知道的人来说,Bevy是一款令人耳目无驰的简单数据驱动的游戏引擎。您可以查看快速入门指南即可开始。 Bevy也是免费和开放的源!您可以在GitHub上获取完整的源代码。
Bevy 0.5比我们过去的释放(并花费了一些更长),因为我们制作了许多基础变化。如果计划将应用程序或插件更新为Bevy 0.5,请查看我们的0.4到0.5迁移指南。
BEVY现在在渲染时使用PBR着色器。 PBR是一种半标准方法,渲染试图使用现实世界&#34的近似;物理基于"照明和材料特性。我们在很大程度上利用来自灯丝PBR实施的技术,但我们还从不真实和迪士尼融合了一些想法。
BEVY' SDARDITEDMATERIAL现在具有Base_COLOR,粗糙度,金属,反射和发光性。它现在还支持Base_color,Normal_map,Metallic_roughness,发射和遮挡属性的纹理。
GLTF装载机现在支持正常地图,金属/粗糙度,遮挡和发光纹理。我们的"飞行头盔" GLTF示例利用新的PBR纹理支持,结果如此:
以前,很难与GLTF资产互动,因为场景/网格/纹理/和材料仅加载为"子资产"由于新的顶级GLTF资产类型,现在可以导航GLTF资产的内容:
//在启动时加载gltf资产fn setup(mut命令:命令,资产:res< assetserver>){让掌握=资产。加载(" flight_helmet.gltf");命令。 INSERT_RESOURCE(手柄);} //在时间稍后的时间点(手柄:res< gltf>,gltfs:res< gltfs<,GLTF资产,GLTF资产访问GLTF资产res< squarymaterial>>){令gltf = gltfs。获得(& handle)。 unwrap();让matering_handle = gltf。 named_materials。得到(" Metalpartsmat")。 unwrap();让材料=材料。得到(Material_Handle)。 unwrap();}
此版本标志着Bevy' ECS的巨大一步。它对Bevy应用程序的组成以及它们的执行程度有重大影响:
直到这一点,bevy为我们的ECS核心使用了一系列重叉的HEC。自从Bevy'第一个发布,我们学到了很多关于Bevy' ECS的需求。我们还与其他ECS项目领导者合作,如桑德·佩佩斯(Lead Flecs Developer)和Gijs-Jan Roelofs(Xenonauts ECS框架开发商)。作为一个" ECS社区",我们' Ve开始归零ECS的未来。
Bevy ECS V2是我们进入该未来的第一步。这也意味着BEVY ECS不再是A" HECS FORK"我们自己出去了!
原型ECS:存储&#34的组件;表"使用静态模式。每个"列"存储给定类型的组件。每个"行"是一个实体。
为实体和#39; S组件的更昂贵的添加/删除操作提供的成本,因为所有组件都需要复制到新的原型' s"表"
友好友好:实体只在一个原型中存在,因此访问相同组件但在不同的原型中可以并行运行
稀疏设置ECS:在密集包装阵列中存储相同类型的组件,这些组件由密集包装的无符号整数(实体ID)稀疏地索引
查询迭代比原型ECS慢(默认情况下)慢,因为每个实体' S组件可以处于稀疏集中的任何位置。这个"随机访问"模式isn' t缓存友好。此外,有一个额外的间接层,因为您必须首先将实体ID映射到组件数组中的索引。
"组件包"用于通过案例的基础来优化迭代性能(但包装彼此冲突)
不太平行友好:系统需要锁定整个组件存储(不粒状)或单个实体(昂贵)
选择ECS框架的开发人员被困难了。选择一个" archetypal" &#34的框架;到处快速迭代"但没有能力廉价添加/删除组件,或选择A"稀疏集合"框架可廉价地添加/删除组件,但具有较慢的迭代性能或手动(和冲突)Pack优化。
在Bevy ECS v2中,我们也可以吃蛋糕并吃它。它现在具有上面的两个组件存储类型(如果需要,可以在稍后添加更多):
这些存储类型完全相互补充。默认情况下,查询迭代快。如果开发人员知道他们想要在高频下添加/删除组件,他们可以将存储设置为"稀疏集合&#34 ;:
该基准测试说明了从具有5个其他4x4矩阵组件的实体添加和删除单个4x4矩阵组件10,000次。 "其他"包括组件以帮助说明"表存储" (由Bevy 0.4,Bevy 0.5(表)和军团使用,需要移动"其他"组件到新表。
您可能已经注意到Bevy 0.5(表)也比Bevy 0.4更快,即使它们都使用"表存储"这主要是新原型图的结果,这显着降低了原型变化的成本。
世界查询(和其他系统参数)现在是有状态的。这允许我们:缓存原型(和表)匹配,这可以解决(天真)原型型ECS的另一个问题:查询性能随着原型的数量而越来越差(发生碎片)。
缓存查询获取和筛选状态昂贵的Fetch / Filter操作部分(例如散列TypeId以查找ComponentID),现在只有在首次构造查询时发生一次
逐步构建状态在添加新原型时,我们只处理新的原型(无需重建旧原型的状态)
然而,对于系统来说,这是一个不破坏的变化。查询状态管理由相关的SystemParam内部完成。
由于新的查询系统,我们已经实现了一些非常重要的性能。
该基准测试运行一个匹配单个原型和#39; t匹配的5个实体的查询匹配100匹配100其他原型。这是&#34的合理测试;现实世界"在游戏中的查询,通常有许多不同的实体"类型",大多数Don' t匹配给定查询。此测试使用"表存储"穿过董事会。
Bevy 0.5为像新的案件标志着它的巨大改进,感谢新的"有状态疑问"每次运行迭代器时,都需要检查每个原型的每个原型,而Bevy 0.5摊销成本为零。
这是ECS_Bench_Suite frag_iter基准测试。它以27个原型运行查询,每个ISIchetypes都有20个实体。然而,与&#34不同;稀疏碎片迭代店基准"没有"无与伦比的"原型。此测试使用"表存储"穿过董事会。
这里的收益与最后一个基准相比较小,因为没有任何无与伦比的原型'然而,由于更好的迭代器/查询iclip,仍然仍然获得了一个很好的提升,将匹配原型的成本摊销到零,而for_each迭代器。
开发人员现在可以选择使用快速查询:: for_each()迭代器,从而产生&#34的迭代速度改进;碎片迭代",对未经用迭代的次要〜1.2x迭代速度改进。
FN系统(查询:查询<(& a,& mut b)>){//您现在可以选择为速度提升查询执行此操作。 for_ach_mut(|(a,mut b)| {}); //然而,普通迭代器仍然可用于查询中的(a,mut b)。 ITER_MUT(){}}
我们将继续鼓励"正常"迭代器,因为它们更灵活,更多"生锈惯用"但是当额外的" oomf"需要,for_each会在那里......等你:)
明确定义系统顺序的唯一方法是创建新阶段。这两个都是batterplate-ey并防止并行性(因为阶段运行"一个逐个"按顺序)。我们注意到系统订购是一个常见的要求和阶段只是Weren' t切割它。
系统有"隐含的"在访问冲突的资源时订购。这些排序很难理由。
"隐式排序"产生的执行策略,通常在桌面上留下了很多平行潜力。
幸运的是,@ratysz一直在这一领域做了很多研究,并自愿捐助一个新的执行官。新的执行程序解决了上述所有问题,并添加了一堆新的可用性改进。 "订购"规则现在已经死了 - 简单:
现在可以分配一个或多个SystemLabels。然后可以通过其他系统(阶段内)引用这些标签以在具有该标签的系统之前或之后运行:
可以使用实现SystemLabel特征的任何类型。在大多数情况下,我们建议为它们定义自定义类型和派生SystemLabel。这可以防止拼写错误,允许封装(在需要时),并允许IDES到AutoComplete标签:
#[派生(调试,散列,零件,eq,clone,systemlabel)] Pub枚举物理系统{UpdateVelocity,Moreforms,Moreforms}应用程序。 add_system(update_velocity。system()。标签(物理系统:: updatevelocity)))。 add_system(移动。系统()。标签(物理系统::移动)。之后(物理系统:: UpdateVelocity));
多对多标签是一个强大的概念,使得依赖于产生给定行为/结果的许多系统方便。例如,如果您有一个毕竟需要运行的系统"物理"已经完成更新(见上面的示例),您可以标记所有"物理系统"使用相同的物理标签:
#[派生(调试,哈希,零件,eq,克隆,systemlabel)] pub struct物理;#[派生(调试,散列,偏见,eq,clone,systemlabel)] pub枚举物理系统{updatevelocity,moreform,moreface。 add_system(update_velocity。系统()。标签(物理系统:: updateVelocity)。标签(物理))。 add_system(移动。系统()。标签(物理系统::移动)。标签(物理)。(物理系统:: updateVelocity)之后。 add_system(runs_after_physics。系统()。在(物理)之后);
Bevy Plugin作者应该在其公共API中将标签导出,使其用户能够在插件提供的逻辑之前/之后插入系统。
Systemsets是一种对一组系统应用相同配置的新方法,这显着削减了样板。 "物理"上面的示例可以是如下所示的:应用程序。 add_system_set(systemset :: new()//将此标签添加到集合中的所有系统。标签(物理)。with_system(update_velocity。系统()。标签(物理系统:: updatevelocity))。with_system(移动。系统() 。标签(物理系统::运动)。之后(物理系统:: UpdateVelocity))))
Systemsets也可以在(标签)和之后(标签)之前使用,以在给定标签之前/之后的设置中运行所有系统。
这对于需要运行相同的运行频率的系统组也非常有用。
APP //此集合中的所有系统都将每两秒运行一次。 add_system_set(systemset :: new()。with_run_criteria(fixedtimestep :: step(2.0))。with_system(foo。system())。with_system(bar。system()))
运行标准现在与系统分离,并在可能的情况下重新使用。例如,上面示例中的固定数据项标准只会运行一次运行一次。执行者将重新使用Foo和Bar系统的标准和#39; S结果。
fn every_other_time(mut has_ran:local< bool>) - >肩膀{* has_ran =!* has_ran;如果* has_ran {wallrun ::是} els {wallrun :: no}}应用程序。 add_stage(systemstage :: parallel()。with_system_run_criteria(every_other_time。system()。标签(" every_other_time")))))。 add_system(foo。system()。with_run_criteria(" every_other_time"))
运行标准的结果也可以是"管道和#34;进入其他标准,这使得有趣的组成行为:
fn direcle_in_a_blue_moon(In(输入):在< antrun>,moon:res< moon>) - >杨柳{如果月亮。 is_blue(){input} else {wallrun :: no}}应用程序。 add_system(foo with_run_criteria(" every_other_time" pipe(are_in_a_blue_moon。system()))
虽然新的执行程序现在更容易理解,但它确实介绍了一类新的错误:"系统订单含糊不清"当两个系统与相同的数据交互时,但没有明确的排序定义,它们产生的输出是非确定性的(并且通常不是作者预期的)。
fn increntment_counter(mut计数器:Resmut<){*计数器+ = 1;} fn print_every_other_time(计数器:res< Usize>){如果*计数器%2 == 0 {println! (" ran"); }} 应用程序 。 add_system(increntment_counter。system())。 add_system(print_every_other_time。system())
作者清楚地预定print_every_other_time来运行其他更新。但是,由于这些系统没有定义了订单,它们可以以不同的顺序运行每个更新,并在两个更新的过程中打印任何内容的情况:
更新 - increment_counter(柜台现在等于1) - print_every_other_time(没有打印)更新 - print_very_other_time(没有打印) - increment_counter(柜台现在等于2)
旧的executor将隐式强制increntment_counter首先运行,因为它与print_every_other_time冲突,它首先插入。但是新的执行官要求您在此处(我们认为是一件好事)。
为了帮助检测到这类错误,我们构建了一个选择的工具,检测这些含糊不点并记录它们:
然后在我们运行我们的应用程序时,我们将看到打印到我们终端的以下消息:
检测到的执行顺序歧义,您可能希望在其中一些系统之间添加显式依赖关系:*并行系统: - "&应用程序:: increntment_counter"和#34;& app :: print_every_other_time"冲突:[" USIZE"]
歧义探测器发现了一个冲突和提到,添加了显式依赖性将解决冲突:
有一些案例,歧义不是一个错误,例如无序集合的操作等资产。这就是为什么我们不默认启用探测器。您可以自由地忽略这些歧义,但如果要抑制探测器中的消息(无需定义依赖项),则可以将您的系统添加到AN" ambiguity集合&#34 ;:
我想强调这是完全可选的。 Bevy代码应该是符合人体工程学和"有趣"来写。如果洒在歧义落在任何地方且#39;你的一杯茶,只是不要担心它!
我们也正在积极寻求关于新执行官的反馈。我们相信新的实施更容易理解并鼓励自我记录代码。改善的并行性也很好!但是我们希望听到用户(新用户都开始新的用户,并将其代码库移植到新的执行程序)。这个空间都是关于设计权衡和反馈,并有助于我们确保我们做出正确的电话。
全局变更检测,在任何ECS组件或资源的更改/添加状态下运行查询的能力,刚刚获得了一个重大的可用性提升:现在在帧/更新中检测到更改:
//这仍然是相同的变化检测API我们都知道和爱,//唯一的区别是它"只是作品"在每种情况下。 FN系统(查询:查询<实体,已更改为>){//迭代所有实体,其组件自//在查询中的e的上次运行此系统的最后运行。 erter(){}}
全局变更检测已经是一个特征,它与其他ECS框架相比设置了BEVY,但现在它是完全"傻瓜证明"无论系统订购,阶段成员资格或系统运行标准如何,它都可以工作。
旧行为是"系统检测到在它们之前遇到的系统中的变化"这是因为当添加/修改每个组件/资源时,我们使用BOOL跟踪。此标志对于帧结束时的每个组件被清除。因此,用户必须非常谨慎地了解操作顺序,并使用像&#34这样的功能;系统运行标准"如果系统在给定更新中运行,则可能导致更改删除。
我们现在使用聪明的"世界蜱"设计允许系统检测自上次运行以来任何时间点发生的更改。
最后一个Bevy发布添加状态,它使开发人员能够根据状态< t&gt的值运行ECS系统组;资源。系统可以根据&#34运行系统生命周期事件",例如on_enter,on_update和on_exit。各国像单独的东西一样制造;加载屏幕"和#34;在游戏中"在Bevy ECS中更容易编码逻辑。
旧的实现在很大程度上工作,但它有很多怪癖和局限性。首先,它需要添加一个新的Statesge,它在并行,增加的样板,并强制订购所需的地方,而且需要。此外,一些生命周期事件没有始终按预期行事。
新的状态实现是基于新的并行执行程序和#39; s systemset和runcrieria特征的顶部构建了更自然,灵活的,并行API,这些API在现有概念上构建而不是创建新的:
#[派生(调试,克隆,eq,partialeq,哈希)] enum appstate {menu,ingame,} fn main(){app :: build()。 add_state(appstate ::菜单)。 add_system_set(systemset :: on_enter(appstate ::菜单)。with_system(setup_menu。system()))。 add_system_set(systemset :: on_update(appstate :: menu)。with_system(menu_logic。system()))。 add_system_set(systemset :: on_exit(appstate ::菜单)。with_system(cleanup_menu。system()))。 add_system_set(systemset :: On_Enter(AppState :: Invame)。with_system(setup_game。system()))。 add_system_set(systemset :: on_update(appstate :: gamene)。with_system(game_logic。system())。with_system(more_game_logic。system()))。跑步 ();}
现在使用A"基于堆栈的状态机"模型。这为州过渡的许多选项打开了:
FN系统(MUT状态:Resmut<状态>>){//队列向上推出一个状态更改,将新状态推送到//堆栈(保留以前的状态)状态。推(AppState :: Invame)。 unwrap(); //延出一个状态更改,该更改删除//堆栈上的当前状态并恢复为先前的状态状态。流行音乐 ()。 unwrap(); //向上延迟覆盖///#34;顶部&#34的当前状态的状态更改;堆栈状态。 SET(AppState :: Invame)。 unwrap(); //队列替换状态更改的状态更改。替换(AppState :: Invame)。 unwrap();}
就像旧的实现一样,状态更改应用于同一帧。这意味着可以从状态A-> b-> c,并在不跳过框架的情况下运行相关的状态生命周期事件。这是在&#34的顶部构建;循环运行标准"我们也用于我们的我们的"修复了TimeStep&#3
......