3 个不稳定版本
0.2.0 | 2022 年 11 月 19 日 |
---|---|
0.1.1 | 2022 年 8 月 21 日 |
0.1.0 | 2022 年 8 月 18 日 |
在 游戏开发 中排名 #1421
31KB
329 行(不包括注释)
用于与 Bevy 场景交互的辅助工具
版本兼容性表格
Bevy 版本 | Crates 版本 |
---|---|
0.9 |
0.2 |
0.8 |
0.1 |
这是关于什么的?
对于初学者来说:Bevy 场景是存储预定义的 Bevy ECS 数据(具有组件的任意实体)的一种方式,并且可以在以后按需实例化它们!
您可以使用场景进行许多用途
- 加载您的游戏关卡/地图(或其部分)
- 预配置的游戏单元/模块(某些其他引擎称为“预制体”)
- 保存游戏状态
- ……
到目前为止,创建 Bevy 场景以及使用 Bevy 场景格式非常难以接近。虽然 Bevy 使您能够轻松地在游戏中使用现有的场景(只需用 DynamicSceneBundle
生成即可),但没有简单的方法来创建它们。Bevy 并未提供内置功能来轻松将内容导出为场景,也没有提供帮助您创建场景的 API。
感谢这个包,现在您可以通过从任何 Bevy 应用程序导出自定义选择的内容来轻松创建自己的场景!
场景导出
您可以为 Bevy 创建 DynamicScene
,包括您想要的任何确切实体和组件选择!
选择可以使用与 Bevy 查询类似的语法进行。
然后,此创建将从您的 World
中根据您的选择复制相关数据,并从中创建一个场景!
组件选择有两种“模式”
- “所有组件”:当您仅选择实体,未指定组件时。在生成场景时,将扫描每个实体来自动检测所有 兼容组件 并将它们包含在场景中
- “显式组件列表”:您指定要包含的确切组件(它们可能是必需的或可选的),并且仅将它们导出(不兼容的类型将被跳过)
// quick: make a scene with all entities that match a given query filter
// (all components will be included)
let my_scene = scene_from_query_filter::<(
With<ComponentA>,
Without<ComponentB>,
)>(&mut world);
// quick: same thing, but only with specific components
let my_scene = scene_from_query_components::<
// the components to include
// (require A and B, only select entities that have them)
// (C is optional, include it if it is present)
(&ComponentA, &ComponentB, Option<&ComponentC>),
// additional filter, to select only specific entities
(With<IWantInMyScene>, Without<DevOnlyDoNotExport>),
>(&mut world);
如果您需要更多灵活性,可以使用 SceneBuilder
,它允许您增量累积多个选择,然后创建包含所有添加内容的场景。组件选择可以以实体粒度进行控制。
let mut builder = SceneBuilder::new(&mut world);
// include entities using query filter:
// all entities with `GameItem`
// all of their components will be included
builder.add_from_query_filter::<With<GameItem>>();
// only specific components for these entities
builder.add_with_components::<
// the components to select
(&Transform, &Health, &BaseStats, Option<&SpecialAbility>),
// query filter to select entities
Or<(With<Player>, With<Enemy>)>
>();
// also add some special entities
builder.add_entity(e);
builder.add_entities(&[magic1, magic2, magic3]);
builder.add_components_to_entities::<
&Transform
>(special_entities.iter());
// we can ignore some components;
// they will never be implicitly included, unless they were
// explicitly selected for specific entities
builder.ignore_components::<(&GlobalTransform, &ComputedVisibility)>();
// now that we have selected everything, make a scene from it!
let my_scene = builder.build_scene();
导出到场景资产文件
上面的示例将创建一个 DynamicScene
实例。但是,如果您只是想创建资产文件,有方便的方法可以直接导出到 Bevy Scene RON 资产文件。
// the standalone (simple) functions:
// like `scene_from_query_components`, but takes a file path
scene_file_from_query_components::</* … */>(world, "my_scene.scn.ron")
.expect("Scene file output failed");
// like `scene_from_query_filter`, but takes a file path
scene_file_from_query_filter::</* … */>(world, "my_scene2.scn.ron")
.expect("Scene file output failed");
// for `SceneBuilder`:
let mut builder = SceneBuilder::new(world);
// ... add stuff ...
// instead of `.build_scene()`:
builder.export_to_file("fancy_scene.scn.ron")
.expect("Scene file output failed");
所有上述方法在导出成功的情况下,都会在 Ok
结果中返回 DynamicScene
,以便您可以对生成的场景进行其他操作。
如果您不希望使用方便的文件导出方法,可以手动输出到场景资产文件,如下所示
// create the scene, using any of the methods shown before
let my_scene = /* ... */;
// need the type registry
let type_registry = world.resource::<TypeRegistry>();
// output the contents as a String
let data = my_scene.serialize_ron(type_registry)
.expect("Scene serialization failed");
// create a scene file (ending in `.scn.ron`)
std::fs::write("file.scn.ron", &data)
.expect("Writing to file failed");
直接使用生成的场景
如果您想立即生成场景并使用它,而不导出/加载资产文件,请按照以下步骤操作。
要在您的应用程序中使用生成的场景,需要将其添加到应用程序的资产中(Assets<DynamicScene>
资源),以获取句柄。
有方便的方法为您完成此操作,它返回 Handle<DynamicScene>
而不是裸露的 DynamicScene
。
// the standalone (simple) functions:
// like `scene_from_query_components`, but adds it to the app for you
let handle = add_scene_from_query_components::</* … */>(world);
// like `scene_from_query_filter`, but adds it to the app for you
let handle = add_scene_from_query_filter::</* … */>(world);
// for `SceneBuilder`:
let mut builder = SceneBuilder::new(world);
// … add stuff …
// instead of `.build_scene()`:
let handle = builder.build_scene_and_add();
如果您想手动进行而不使用辅助函数
// get the `Assets<DynamicScene>` resource:
// (if we are in an exclusive system)
let mut assets = world.resource_mut::<Assets<DynamicScene>>();
// (in a regular system, you can use `ResMut<Assets<DynamicScene>>`)
// add it
let handle = assets.add(my_scene);
稍后,您可以从任何地方生成您的场景。
从一个常规系统
commands.spawn_bundle(DynamicSceneBundle {
scene: handle,
..default()
});
通过直接访问世界
world.spawn().insert_bundle(DynamicSceneBundle {
scene: handle,
..default()
});
警告
警告!您必须确保您的组件类型
- 实现
Reflect
- 反射
Component
- 在类型注册表中注册
否则,它们将被静默忽略,并从您的场景中消失!
如果您正在将场景序列化到资产文件中,您可能还需要 FromReflect
,否则您以后将无法加载您的场景!
#[derive(Component, Default, Reflect, FromReflect)]
#[reflect(Component)]
struct MyComponent;
(注意:Bevy 要求实现 FromWorld
或 Default
,以推导 Reflect
)
app.register_type::<MyComponent>();
这是所有您想与场景一起使用的组件所需的必需样板!否则,事情将静默地不起作用。
"蓝图" 模式
这是如何使您的工作流程更具灵活性,并充分利用 Bevy 场景的推荐。
Bevy 中有许多组件类型表示在运行时计算内部状态,例如:GlobalTransform
、ComputedVisibility
、Interaction
等。
它们的值不需要在场景中持久化。您可能希望省略它们。这也有助于使场景不那么臃肿。
您可能还希望省略其他一些组件,如果您更愿意使用代码设置它们,或将它们初始化为默认值。
let mut builder = SceneBuilder::new(world);
// add our game entities
builder.add_from_query_filter::<With<Enemy>>();
builder.add_from_query_filter::<With<Player>>();
builder.add_from_query_filter::<With<Powerup>>();
// …
// for our UI Nodes, only persist hierarchy + `Style`, `UiColor`, `Text`, `Button`
builder.add_with_components::<
(
(Option<&Parent>, Option<&Children>),
(&Style, &UiColor, Option<&Text>, Option<&Button>),
),
With<Node>
>();
// never include these components in any entity
builder.ignore_components::<
(&GlobalTransform, &Visibility, &ComputedVisibility, &CalculatedSize)
>();
let my_scene = builder.build_scene();
如果您正在创建这样的“稀疏”场景(我们可以称之为“蓝图”),其中只包含一些组件而缺少其他组件,您可以编写一些代码来填充实体以“完成”它们的设置。
这可以通过使用具有 Added
查询过滤器系统的代码轻松完成。这样,您可以检测到此类实体何时被生成到世界中,并可以使用代码对它们进行任何其他设置。
// ensure everything with a transform has all the transform/visibility stuff
fn setup_spatial(
mut commands: Commands,
// detect anything that was just added and needs setup
q_new: Query<
(Entity, &Transform),
(Added<Transform>, Without<GlobalTransform>)
>,
) {
for (e, transform) in q_new.iter() {
commands.entity(e).insert_bundle(SpatialBundle {
// preserve the transform
transform,
..Default::default()
});
}
}
/// complete the setup of our UI
/// (btw, this could be the starting point for the development
/// of a nice automatic theming system ;) hehe)
fn setup_ui(
mut commands: Commands,
// detect anything that was just added and needs setup
q_new: Query<
(Entity, &Style, Option<&UiColor>, Option<&Text>, Option<&Button>),
(Added<Style>, Without<Node>)
>,
) {
for (e, style, color, text, button) in q_new.iter() {
if let Some(text) = text {
commands.entity(e).insert_bundle(TextBundle {
text: text.clone(),
style: style.clone(),
..Default::default()
});
} else if let Some(_button) = button {
// (`Button` is just a marker)
commands.entity(e).insert_bundle(ButtonBundle {
style: style.clone(),
color: color.cloned().unwrap_or(UiColor(Color::NONE)),
..Default::default()
});
} else {
// this is a generic ui node
commands.entity(e).insert_bundle(NodeBundle {
style: style.clone(),
color: color.cloned().unwrap_or(UiColor(Color::NONE)),
..Default::default()
});
}
}
}
依赖关系
~35–50MB
~687K SLoC