2个版本
0.1.1 | 2024年7月14日 |
---|---|
0.1.0 | 2024年7月14日 |
#386 在 游戏开发
每月下载量:202
97KB
662 行
Bevy Fabulous
概述
Bevy fabulous旨在提供一个框架,用于封装和耦合加载的GLTF及其游戏组件,而无需使用重型工具或具有偏见的插件。
Bevy Fabulous提供了丰富GLTF加载场景的机制:**预制体**和**后置预制体**,以及自动覆盖GLTF中发现的材质的实用工具。
以下是一个使用bevy_fabulous可以与加载的GLTF资产一起完成的示例
fn init_fireminion(
mut fabs: ResMut<FabManager>,
mut fabmats: ResMut<FabMaterialOverrides<StandardMaterial, StandardMaterial>>,
assets: ResMut<MinionAssets>,
relic_oneshots: Res<RelicOneshots>,
){
fabmats.register_main_mat("FireMana", assets.fire_mana.clone());
fabs.register_postfab(PostFab {
scene: FabTarget::Gltf(assets.fire_minion.clone()),
pipes: vec![
PostfabPipe::entity(AttachBundle::new(Bitsploder(MagicElement::Fire, 100))).root_only(),
PostfabPipe::entity(AttachBundle::new((ShopCleanup, ExplosionDebris))).with_components(vec![TypeId::of::<Handle<Mesh>>()]),
PostfabPipe::system(relic_oneshots.on_minion_spawn_system)
]
})
}
计划的功能
- 为
PrefabPipes
提供相同的组件/名称/根过滤,理想情况下统一预制体+后置预制体管道功能集 - 允许PostfabPipes对任何实体及其子实体进行操作,而不仅仅是GLTF/场景(这可能已经通过仅为实体添加后置预制体来实现)
- 在crate中包含电池,提供一些预制的管道以进行常见操作
预制体
预制体直接修改加载的场景世界,将游戏组件应用到场景世界中的实体。这些组件只需要运行一次,因为它们将成为场景的一部分,所以任何后续的SceneBundle
都会具有这些组件。
预制体适用于添加无上下文的游戏组件,即您希望添加到模型的节点上的组件,无论何时何地创建它
预制体包含一个指向资产的路径,该路径用作FabManager
资源中HashMap<String, Prefab>
的键。
预制体是PrefabPipe trait对象Vec的包装器,并按照插入的顺序运行
/// Applies ScenePipes to the loaded scene `World`
pub struct Prefab {
/// The path to the asset on the filesystem
pub target: FabTarget,
/// Pipes to run on load
pub pipeline: Vec<Box<dyn PrefabPipe + Send + Sync>>,
}
PrefabPipe
最终看起来非常像命令
///Used to transform a scene, but avoid Transform as a term - it's already overloaded
pub trait PrefabPipe {
// Applies the pipe to the entity
fn apply(&mut self, world: &mut World);
}
//Define a prefab pipe as a struct
pub struct HeadPipe {
pub rotation_rate: f32,
}
impl PrefabPipe for HeadPipe {
fn apply(&mut self, world: &mut World) {
info!("Running Head Rotate Pipe");
//Iterate over the entities in the world and find the homie with the a head on him
let mut q = world.query::<(Entity, &Name)>();
let q = q.iter(world);
let mut ent: Option<Entity> = None;
for (entity, name) in q {
if *name.to_string() == "MinionHead".to_string() {
ent = Some(entity);
break;
}
}
if let Some(entity) = ent {
info!("Found Thing and attaching to stuff");
world.entity_mut(entity).insert((
Rotate {
rotation_rate: self.rotation_rate,
},
Bob {
amplitude: 0.4,
frequency: 0.8,
anchor: 2.0,
},
));
}
world.flush();
}
}
PrefabPipe
还有一个针对BoxedSystem的泛型实现,允许您将任何系统用作PrefabPipe
//In Some Startup/Loaded System
//---
//Register to the `FabManager`
fabs.register_prefab(
Prefab::new(FabTarget::Gltf(gltf_handle.clone()))
.with_system(inner_gear_rotate)
);
//---
//Define a prefab pipe as a system
fn inner_gear_rotate(entities: Query<(Entity, &Name)>, mut cmds: Commands) {
info!("Inner Gear Rotate");
let mut gear_ent = None;
let mut orbiters = vec![];
for (ent, name) in entities.iter() {
let name_str = name.as_str();
if name_str == "Gear" {
gear_ent = Some(ent);
} else if name_str.contains("Orbiter") {
orbiters.push(ent);
}
}
let Some(entity) = gear_ent else {
return;
};
cmds.entity(entity).insert(Rotate {
rotation_rate: -0.5,
});
for orbiter in orbiters {
cmds.entity(orbiter).insert(Rotate { rotation_rate: 0.5 });
}
}
后置预制体
每次创建特定场景时都会运行后置预制体。它们在实体被创建后运行,并且不会修改原始场景。
- 它们可以以直观的方式根据上下文信息转换创建的场景。
- 它们可以用于用主Bevy世界中的“水合”资产替换场景中的材质/资产,例如
StandardMaterial
- 如果可能,请使用预制体,因为一次性成本比每次场景创建时运行逻辑/查询更可取
Postfabs 可以与 FabTarget
注册,它允许用户直接使用 Handle<Scene>
,或者一个 Handle<Gltf>
,这将使 Postfab 注册到 gltf 中的第一个场景。
#[derive(Clone, Component)]
pub struct PostFab {
pub scene: FabTarget,
pub pipes: Vec<PostfabPipe>,
}
与 Prefabs
类似,Postfabs
由一个 FabTarget 和一系列按顺序应用的管道组成。PostfabPipe
具有更多的先进过滤选项,以指定管道是否应在给定的实体上运行。
#[derive(Clone)]
pub struct PostfabPipe {
pub system: SystemId<Entity, ()>,
pub with_components: Vec<TypeId>,
pub without_components: Vec<TypeId>,
pub with_name: Option<NameCriteria>,
}
/// Name component criteria for determining whether a pipe should run on a given entity
#[derive(Clone)]
pub enum NameCriteria {
Equals(String),
Contains(String),
StartsWith(String),
EndsWith(String),
}
材质覆盖
材质覆盖用于自动将实体上的材质句柄替换为另一个材质。这对于替换场景/gltf 中作为标准材质加载的部分非常有用。名称从 GLTF 资产的 NamedMaterials
映射中获取。在撰写本文时,用户需要直接加载 gltf 资产,以便在 FabulousMaterialsPlugin
资产监视器运行时保持活跃和可用。这意味着使用 "myAsset.gltf#Scene0" 或 GltfAssetLabel::Scene(0)
加载的场景可能不会按预期工作。
//Create and register new material to be swapped out
let earth_mana = StandardMaterial {
emissive: (palettes::css::LIMEGREEN * 2.0).into(),
..default()
};
mat_index.register_main_mat("EarthMana", mats.add(earth_mana));
生成 Gltf 场景
由于命名材质要求在场景资产加载时 GLTF 场景可用,最好直接加载 Gltf 而不是包含在其中的场景。为了使这一点更容易处理,这个包提供了 SpawnGltfScene
命令和一些辅助工具。您可以从 GLTF 中生成特定的场景,如下所示:
// Spawn Minion at Default location
cmds.spawn_gltf(GltfScene::new(ex.asset_scene.clone()).with_bundle(Name::new("Minion")));
GltfScene 还提供了 at_location(Transform)
、with_scene(usize)
和 build()
,用于指定变换、gltf 中的场景以及在没有附加组件的场景根上生成场景。
依赖关系
~23MB
~411K SLoC