#bevy #scenes #component #entities #gamedev #helper #times

iyes_scene_tools

用于与 Bevy 场景交互的额外辅助工具

3 个不稳定版本

0.2.0 2022 年 11 月 19 日
0.1.1 2022 年 8 月 21 日
0.1.0 2022 年 8 月 18 日

游戏开发 中排名 #1421

MIT/Apache

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 要求实现 FromWorldDefault,以推导 Reflect

app.register_type::<MyComponent>();

这是所有您想与场景一起使用的组件所需的必需样板!否则,事情将静默地不起作用。

"蓝图" 模式

这是如何使您的工作流程更具灵活性,并充分利用 Bevy 场景的推荐。


Bevy 中有许多组件类型表示在运行时计算内部状态,例如:GlobalTransformComputedVisibilityInteraction 等。

它们的值不需要在场景中持久化。您可能希望省略它们。这也有助于使场景不那么臃肿。

您可能还希望省略其他一些组件,如果您更愿意使用代码设置它们,或将它们初始化为默认值。

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