Migrate scenes to required components (#15579)

# Objective

A step in the migration to required components: scenes!

## Solution

As per the [selected
proposal](https://hackmd.io/@bevy/required_components/%2FPJtNGVMMQhyM0zIvCJSkbA):
- Deprecate `SceneBundle` and `DynamicSceneBundle`.
- Add `SceneRoot` and `DynamicSceneRoot` components, which wrap a
`Handle<Scene>` and `Handle<DynamicScene>` respectively.

## Migration Guide
Asset handles for scenes and dynamic scenes must now be wrapped in the
`SceneRoot` and `DynamicSceneRoot` components. Raw handles as components
no longer spawn scenes.

Additionally, `SceneBundle` and `DynamicSceneBundle` have been
deprecated. Instead, use the scene components directly.

Previously:
```rust
let model_scene = asset_server.load(GltfAssetLabel::Scene(0).from_asset("model.gltf"));

commands.spawn(SceneBundle {
    scene: model_scene,
    transform: Transform::from_xyz(-4.0, 0.0, -3.0),
    ..default()
});
```
Now:
```rust
let model_scene = asset_server.load(GltfAssetLabel::Scene(0).from_asset("model.gltf"));

commands.spawn((
    SceneRoot(model_scene),
    Transform::from_xyz(-4.0, 0.0, -3.0),
));
```
This commit is contained in:
Tim 2024-10-01 22:42:11 +00:00 committed by GitHub
parent ead84e0e3d
commit eb51b4c28e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 272 additions and 309 deletions

View file

@ -653,7 +653,7 @@ impl ActiveAnimation {
/// Animation controls.
///
/// Automatically added to any root animations of a `SceneBundle` when it is
/// Automatically added to any root animations of a scene when it is
/// spawned.
#[derive(Component, Default, Reflect)]
#[reflect(Component, Default)]

View file

@ -22,15 +22,14 @@
//! # use bevy_gltf::prelude::*;
//!
//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
//! commands.spawn(SceneBundle {
//! commands.spawn((
//! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"
//! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.
//! // If this isn't specified bevy doesn't know which part of the glTF file to load.
//! scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
//! SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
//! // You can use the transform to give it a position
//! transform: Transform::from_xyz(2.0, 0.0, -5.0),
//! ..Default::default()
//! });
//! Transform::from_xyz(2.0, 0.0, -5.0),
//! ));
//! }
//! ```
//! # Loading parts of a glTF asset
@ -72,18 +71,14 @@
//! };
//! *loaded = true;
//!
//! commands.spawn(SceneBundle {
//! // Gets the first scene in the file
//! scene: gltf.scenes[0].clone(),
//! ..Default::default()
//! });
//! // Spawns the first scene in the file
//! commands.spawn(SceneRoot(gltf.scenes[0].clone()));
//!
//! commands.spawn(SceneBundle {
//! // Gets the scene named "Lenses_low"
//! scene: gltf.named_scenes["Lenses_low"].clone(),
//! transform: Transform::from_xyz(1.0, 2.0, 3.0),
//! ..Default::default()
//! });
//! // Spawns the scene named "Lenses_low"
//! commands.spawn((
//! SceneRoot(gltf.named_scenes["Lenses_low"].clone()),
//! Transform::from_xyz(1.0, 2.0, 3.0),
//! ));
//! }
//! ```
//!

View file

@ -1,4 +1,5 @@
use bevy_asset::Handle;
#![expect(deprecated)]
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
bundle::Bundle,
@ -11,21 +12,25 @@ use bevy_ecs::{
use bevy_render::prelude::{InheritedVisibility, ViewVisibility, Visibility};
use bevy_transform::components::{GlobalTransform, Transform};
use crate::{DynamicScene, InstanceId, Scene, SceneSpawner};
use crate::{DynamicSceneRoot, InstanceId, SceneRoot, SceneSpawner};
/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to
/// interact with the spawned scene.
#[derive(Component, Deref, DerefMut)]
pub struct SceneInstance(pub(crate) InstanceId);
/// A component bundle for a [`Scene`] root.
/// A component bundle for a [`Scene`](crate::Scene) root.
///
/// The scene from `scene` will be spawned as a child of the entity with this component.
/// Once it's spawned, the entity will have a [`SceneInstance`] component.
#[derive(Default, Bundle, Clone)]
#[deprecated(
since = "0.15.0",
note = "Use the `SceneRoot` component instead. Inserting `SceneRoot` will also insert the other components required by scenes automatically."
)]
pub struct SceneBundle {
/// Handle to the scene to spawn.
pub scene: Handle<Scene>,
pub scene: SceneRoot,
/// Transform of the scene root entity.
pub transform: Transform,
/// Global transform of the scene root entity.
@ -42,14 +47,18 @@ pub struct SceneBundle {
pub view_visibility: ViewVisibility,
}
/// A component bundle for a [`DynamicScene`] root.
/// A component bundle for a [`DynamicScene`](crate::DynamicScene) root.
///
/// The dynamic scene from `scene` will be spawn as a child of the entity with this component.
/// Once it's spawned, the entity will have a [`SceneInstance`] component.
#[derive(Default, Bundle, Clone)]
#[deprecated(
since = "0.15.0",
note = "Use the `DynamicSceneRoot` component instead. Inserting `DynamicSceneRoot` will also insert the other components required by scenes automatically."
)]
pub struct DynamicSceneBundle {
/// Handle to the scene to spawn.
pub scene: Handle<DynamicScene>,
pub scene: DynamicSceneRoot,
/// Transform of the scene root entity.
pub transform: Transform,
/// Global transform of the scene root entity.
@ -66,21 +75,21 @@ pub struct DynamicSceneBundle {
pub view_visibility: ViewVisibility,
}
/// System that will spawn scenes from [`SceneBundle`].
/// System that will spawn scenes from the [`SceneRoot`] and [`DynamicSceneRoot`] components.
pub fn scene_spawner(
mut commands: Commands,
mut scene_to_spawn: Query<
(Entity, &Handle<Scene>, Option<&mut SceneInstance>),
(Changed<Handle<Scene>>, Without<Handle<DynamicScene>>),
(Entity, &SceneRoot, Option<&mut SceneInstance>),
(Changed<SceneRoot>, Without<DynamicSceneRoot>),
>,
mut dynamic_scene_to_spawn: Query<
(Entity, &Handle<DynamicScene>, Option<&mut SceneInstance>),
(Changed<Handle<DynamicScene>>, Without<Handle<Scene>>),
(Entity, &DynamicSceneRoot, Option<&mut SceneInstance>),
(Changed<DynamicSceneRoot>, Without<SceneRoot>),
>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
for (entity, scene, instance) in &mut scene_to_spawn {
let new_instance = scene_spawner.spawn_as_child(scene.clone(), entity);
let new_instance = scene_spawner.spawn_as_child(scene.0.clone(), entity);
if let Some(mut old_instance) = instance {
scene_spawner.despawn_instance(**old_instance);
*old_instance = SceneInstance(new_instance);
@ -89,7 +98,7 @@ pub fn scene_spawner(
}
}
for (entity, dynamic_scene, instance) in &mut dynamic_scene_to_spawn {
let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.clone(), entity);
let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.0.clone(), entity);
if let Some(mut old_instance) = instance {
scene_spawner.despawn_instance(**old_instance);
*old_instance = SceneInstance(new_instance);
@ -101,7 +110,7 @@ pub fn scene_spawner(
#[cfg(test)]
mod tests {
use crate::{DynamicScene, DynamicSceneBundle, ScenePlugin, SceneSpawner};
use crate::{DynamicScene, DynamicSceneRoot, ScenePlugin, SceneSpawner};
use bevy_app::{App, ScheduleRunnerPlugin};
use bevy_asset::{AssetPlugin, Assets};
use bevy_ecs::{
@ -111,7 +120,6 @@ mod tests {
};
use bevy_hierarchy::{Children, HierarchyPlugin};
use bevy_reflect::Reflect;
use bevy_utils::default;
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
@ -143,13 +151,10 @@ mod tests {
.resource_mut::<Assets<DynamicScene>>()
.add(scene);
// spawn the scene as a child of `entity` using the `DynamicSceneBundle`
// spawn the scene as a child of `entity` using `DynamicSceneRoot`
let entity = app
.world_mut()
.spawn(DynamicSceneBundle {
scene: scene_handle.clone(),
..default()
})
.spawn(DynamicSceneRoot(scene_handle.clone()))
.id();
// run the app's schedule once, so that the scene gets spawned

View file

@ -0,0 +1,36 @@
use bevy_asset::Handle;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::component::Component;
use bevy_reflect::Reflect;
use bevy_transform::components::Transform;
#[cfg(feature = "bevy_render")]
use bevy_render::view::visibility::Visibility;
use crate::{DynamicScene, Scene};
/// Adding this component will spawn the scene as a child of that entity.
/// Once it's spawned, the entity will have a [`SceneInstance`](crate::SceneInstance) component.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
#[require(Transform)]
#[cfg_attr(feature = "bevy_render", require(Visibility))]
pub struct SceneRoot(pub Handle<Scene>);
impl From<Handle<Scene>> for SceneRoot {
fn from(handle: Handle<Scene>) -> Self {
Self(handle)
}
}
/// Adding this component will spawn the scene as a child of that entity.
/// Once it's spawned, the entity will have a [`SceneInstance`](crate::SceneInstance) component.
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq)]
#[require(Transform)]
#[cfg_attr(feature = "bevy_render", require(Visibility))]
pub struct DynamicSceneRoot(pub Handle<DynamicScene>);
impl From<Handle<DynamicScene>> for DynamicSceneRoot {
fn from(handle: Handle<DynamicScene>) -> Self {
Self(handle)
}
}

View file

@ -18,10 +18,7 @@ use serde::Serialize;
/// Each dynamic entity in the collection contains its own run-time defined set of components.
/// To spawn a dynamic scene, you can use either:
/// * [`SceneSpawner::spawn_dynamic`](crate::SceneSpawner::spawn_dynamic)
/// * adding the [`DynamicSceneBundle`](crate::DynamicSceneBundle) to an entity
/// * adding the [`Handle<DynamicScene>`](bevy_asset::Handle) to an entity (the scene will only be
/// visible if the entity already has [`Transform`](bevy_transform::components::Transform) and
/// [`GlobalTransform`](bevy_transform::components::GlobalTransform) components)
/// * adding the [`DynamicSceneRoot`](crate::components::DynamicSceneRoot) component to an entity.
/// * using the [`DynamicSceneBuilder`] to construct a `DynamicScene` from `World`.
#[derive(Asset, TypePath, Default)]
pub struct DynamicScene {

View file

@ -14,6 +14,7 @@
extern crate alloc;
mod bundle;
mod components;
mod dynamic_scene;
mod dynamic_scene_builder;
mod scene;
@ -29,6 +30,7 @@ pub use bevy_asset::ron;
use bevy_ecs::schedule::IntoSystemConfigs;
pub use bundle::*;
pub use components::*;
pub use dynamic_scene::*;
pub use dynamic_scene_builder::*;
pub use scene::*;
@ -39,16 +41,17 @@ pub use scene_spawner::*;
/// The scene prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
#[expect(deprecated)]
pub mod prelude {
#[doc(hidden)]
pub use crate::{
DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, Scene, SceneBundle, SceneFilter,
SceneSpawner,
DynamicScene, DynamicSceneBuilder, DynamicSceneBundle, DynamicSceneRoot, Scene,
SceneBundle, SceneFilter, SceneRoot, SceneSpawner,
};
}
use bevy_app::prelude::*;
use bevy_asset::{AssetApp, Handle};
use bevy_asset::AssetApp;
/// Plugin that provides scene functionality to an [`App`].
#[derive(Default)]
@ -61,13 +64,15 @@ impl Plugin for ScenePlugin {
.init_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.register_type::<SceneRoot>()
.register_type::<DynamicSceneRoot>()
.add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain());
// Register component hooks for DynamicScene
// Register component hooks for DynamicSceneRoot
app.world_mut()
.register_component_hooks::<Handle<DynamicScene>>()
.register_component_hooks::<DynamicSceneRoot>()
.on_remove(|mut world, entity, _| {
let Some(handle) = world.get::<Handle<DynamicScene>>(entity) else {
let Some(handle) = world.get::<DynamicSceneRoot>(entity) else {
return;
};
let id = handle.id();
@ -82,9 +87,9 @@ impl Plugin for ScenePlugin {
}
});
// Register component hooks for Scene
// Register component hooks for SceneRoot
app.world_mut()
.register_component_hooks::<Handle<Scene>>()
.register_component_hooks::<SceneRoot>()
.on_remove(|mut world, entity, _| {
if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) {
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {

View file

@ -9,10 +9,7 @@ use bevy_reflect::{PartialReflect, TypePath};
/// To spawn a scene, you can use either:
/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn)
/// * adding the [`SceneBundle`](crate::SceneBundle) to an entity
/// * adding the [`Handle<Scene>`](bevy_asset::Handle) to an entity (the scene will only be
/// visible if the entity already has [`Transform`](bevy_transform::components::Transform) and
/// [`GlobalTransform`](bevy_transform::components::GlobalTransform) components)
/// * adding the [`SceneRoot`](crate::components::SceneRoot) component to an entity.
#[derive(Asset, TypePath, Debug)]
pub struct Scene {
/// The world of the scene, containing its entities and resources.

View file

@ -482,7 +482,7 @@ mod tests {
};
use bevy_reflect::Reflect;
use crate::{DynamicSceneBuilder, ScenePlugin};
use crate::{DynamicSceneBuilder, DynamicSceneRoot, ScenePlugin};
use super::*;
@ -725,7 +725,8 @@ mod tests {
// Spawn scene.
for _ in 0..count {
app.world_mut().spawn((ComponentA, scene.clone()));
app.world_mut()
.spawn((ComponentA, DynamicSceneRoot(scene.clone())));
}
app.update();

View file

@ -72,11 +72,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res
spawn_directional_light(&mut commands);
commands.spawn(SceneBundle {
scene: asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0"),
transform: Transform::from_xyz(0.0, 0.07, -0.13),
..default()
});
commands.spawn((
SceneRoot(asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0")),
Transform::from_xyz(0.0, 0.07, -0.13),
));
spawn_text(&mut commands, &app_status);
}

View file

@ -272,11 +272,9 @@ fn setup(
}
// Flight Helmet
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)));
// Light
commands.spawn((

View file

@ -69,11 +69,9 @@ fn setup_terrain_scene(
));
// Terrain
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/terrain/Mountains.gltf")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/terrain/Mountains.gltf"),
)));
// Sky
commands.spawn((

View file

@ -145,14 +145,13 @@ fn spawn_coated_glass_bubble_sphere(
/// This object is in glTF format, using the `KHR_materials_clearcoat`
/// extension.
fn spawn_golf_ball(commands: &mut Commands, asset_server: &AssetServer) {
commands
.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")),
transform: Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
..default()
})
.insert(ExampleSphere);
commands.spawn((
SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/GolfBall/GolfBall.glb")),
),
Transform::from_xyz(1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
ExampleSphere,
));
}
/// Spawns an object with only a clearcoat normal map (a scratch pattern) and no

View file

@ -381,21 +381,18 @@ fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading
fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) {
// Spawn the main scene.
commands.spawn(SceneBundle {
scene: asset_server.load(
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
),
..default()
});
)));
// Spawn the flight helmet.
commands.spawn(SceneBundle {
scene: asset_server
commands.spawn((
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default()
});
),
Transform::from_xyz(0.5, 0.0, -0.5).with_rotation(Quat::from_rotation_y(-0.15 * PI)),
));
// Spawn the light.
commands.spawn((

View file

@ -85,15 +85,11 @@ fn setup(
let helmet_scene = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
commands.spawn(SceneBundle {
scene: helmet_scene.clone(),
..default()
});
commands.spawn(SceneBundle {
scene: helmet_scene,
transform: Transform::from_xyz(-4.0, 0.0, -3.0),
..default()
});
commands.spawn(SceneRoot(helmet_scene.clone()));
commands.spawn((
SceneRoot(helmet_scene),
Transform::from_xyz(-4.0, 0.0, -3.0),
));
let mut forward_mat: StandardMaterial = Color::srgb(0.1, 0.2, 0.1).into();
forward_mat.opaque_render_method = OpaqueRendererMethod::Forward;

View file

@ -88,13 +88,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
}
// Spawn the scene.
commands.spawn(SceneBundle {
scene: asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("models/DepthOfFieldExample/DepthOfFieldExample.glb"),
),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/DepthOfFieldExample/DepthOfFieldExample.glb"),
)));
// Spawn the help text.
commands.spawn(

View file

@ -227,10 +227,7 @@ fn setup(mut commands: Commands, assets: Res<ExampleAssets>, app_status: Res<App
}
fn spawn_main_scene(commands: &mut Commands, assets: &ExampleAssets) {
commands.spawn(SceneBundle {
scene: assets.main_scene.clone(),
..SceneBundle::default()
});
commands.spawn(SceneRoot(assets.main_scene.clone()));
}
fn spawn_camera(commands: &mut Commands, assets: &ExampleAssets) {
@ -290,14 +287,12 @@ fn spawn_voxel_cube_parent(commands: &mut Commands) {
}
fn spawn_fox(commands: &mut Commands, assets: &ExampleAssets) {
commands
.spawn(SceneBundle {
scene: assets.fox.clone(),
visibility: Visibility::Hidden,
transform: Transform::from_scale(Vec3::splat(FOX_SCALE)),
..default()
})
.insert(MainObject);
commands.spawn((
SceneRoot(assets.fox.clone()),
Visibility::Hidden,
Transform::from_scale(Vec3::splat(FOX_SCALE)),
MainObject,
));
}
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
@ -386,11 +381,8 @@ fn rotate_camera(
fn change_main_object(
keyboard: Res<ButtonInput<KeyCode>>,
mut app_status: ResMut<AppStatus>,
mut sphere_query: Query<
&mut Visibility,
(With<MainObject>, With<Mesh3d>, Without<Handle<Scene>>),
>,
mut fox_query: Query<&mut Visibility, (With<MainObject>, With<Handle<Scene>>)>,
mut sphere_query: Query<&mut Visibility, (With<MainObject>, With<Mesh3d>, Without<SceneRoot>)>,
mut fox_query: Query<&mut Visibility, (With<MainObject>, With<SceneRoot>)>,
) {
if !keyboard.just_pressed(KeyCode::Tab) {
return;

View file

@ -12,11 +12,9 @@ fn main() {
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb"),
)));
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-278.0, 273.0, 800.0),

View file

@ -46,11 +46,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}
.build(),
));
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
)));
}
fn animate_light_direction(

View file

@ -28,11 +28,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
// a barebones scene containing one of each gltf_extra type
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/extras/gltf_extras.glb")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/extras/gltf_extras.glb"),
)));
// a place to display the extras on screen
commands.spawn((

View file

@ -205,10 +205,9 @@ fn spawn_light(commands: &mut Commands, app_status: &AppStatus) {
/// Loads and spawns the glTF palm tree scene.
fn spawn_gltf_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneBundle {
scene: asset_server.load("models/PalmTree/PalmTree.gltf#Scene0"),
..default()
});
commands.spawn(SceneRoot(
asset_server.load("models/PalmTree/PalmTree.gltf#Scene0"),
));
}
/// Spawns all the buttons at the bottom of the screen.

View file

@ -93,21 +93,18 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
/// variety of colors.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
// Spawn the main scene.
commands.spawn(SceneBundle {
scene: asset_server.load(
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
),
..default()
});
)));
// Spawn the flight helmet.
commands.spawn(SceneBundle {
scene: asset_server
commands.spawn((
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default()
});
),
Transform::from_xyz(0.5, 0.0, -0.5).with_rotation(Quat::from_rotation_y(-0.15 * PI)),
));
// Spawn the light.
commands.spawn((

View file

@ -83,13 +83,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}
.build(),
));
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/GltfPrimitives/gltf_primitives.glb")),
transform: Transform {
commands.spawn((
SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/GltfPrimitives/gltf_primitives.glb"),
)),
Transform {
rotation: Quat::from_rotation_y(-90.0 / 180.0 * PI),
..default()
},
..default()
});
));
}

View file

@ -96,10 +96,9 @@ fn setup(
// Spawns the cubes, light, and camera.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/cubes/Cubes.glb")),
..SceneBundle::default()
});
commands.spawn(SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/cubes/Cubes.glb")),
));
}
// Spawns the camera.

View file

@ -27,10 +27,9 @@ fn setup(
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
..default()
});
commands.spawn(SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
));
// Light
commands.spawn((

View file

@ -163,15 +163,15 @@ fn spawn_cube(
// Spawns the flight helmet.
fn spawn_flight_helmet(commands: &mut Commands, asset_server: &AssetServer) {
commands
.spawn(SceneBundle {
scene: asset_server
commands.spawn((
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
transform: Transform::from_scale(Vec3::splat(2.5)),
..default()
})
.insert(FlightHelmetModel)
.insert(Visibility::Hidden);
),
Transform::from_scale(Vec3::splat(2.5)),
FlightHelmetModel,
Visibility::Hidden,
));
}
// Spawns the water plane.

View file

@ -95,24 +95,20 @@ fn setup(
fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// Main scene
commands
.spawn(SceneBundle {
scene: asset_server.load(
commands.spawn((
SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/TonemappingTest/TonemappingTest.gltf"),
),
..default()
})
.insert(SceneNumber(1));
)),
SceneNumber(1),
));
// Flight Helmet
commands.spawn((
SceneBundle {
scene: asset_server
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default()
},
),
Transform::from_xyz(0.5, 0.0, -0.5).with_rotation(Quat::from_rotation_y(-0.15 * PI)),
SceneNumber(1),
));

View file

@ -38,20 +38,20 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
));
// Spawn the scene as a child of this entity at the given transform
commands.spawn(SceneBundle {
transform: Transform::from_xyz(-1.0, 0.0, 0.0),
scene: asset_server
commands.spawn((
Transform::from_xyz(-1.0, 0.0, 0.0),
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default()
});
),
));
// Spawn a second scene, and add a tag component to be able to target it later
commands.spawn((
SceneBundle {
scene: asset_server
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default()
},
),
MovedScene,
));
}

View file

@ -101,23 +101,23 @@ fn setup(
// Spawn the two HLODs.
commands
.spawn(SceneBundle {
scene: asset_server
commands.spawn((
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
..default()
})
.insert(MainModel::HighPoly);
),
MainModel::HighPoly,
));
commands
.spawn(SceneBundle {
scene: asset_server.load(
commands.spawn((
SceneRoot(
asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("models/FlightHelmetLowPoly/FlightHelmetLowPoly.gltf"),
),
..default()
})
.insert(MainModel::LowPoly);
),
MainModel::LowPoly,
));
// Spawn a light.
commands.spawn((

View file

@ -57,13 +57,9 @@ fn main() {
/// Initializes the scene.
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: Res<AppSettings>) {
// Spawn the glTF scene.
commands.spawn(SceneBundle {
scene: asset_server.load(
GltfAssetLabel::Scene(0)
.from_asset("models/VolumetricFogExample/VolumetricFogExample.glb"),
),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/VolumetricFogExample/VolumetricFogExample.glb"),
)));
// Spawn the camera.
commands

View file

@ -80,10 +80,9 @@ fn setup(
));
// Fox
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)),
..default()
});
commands.spawn(SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)),
));
println!("Animation controls:");
println!(" - spacebar: play / pause");

View file

@ -232,11 +232,12 @@ fn setup_scene(
Transform::from_xyz(-4.0, 8.0, 13.0),
));
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
transform: Transform::from_scale(Vec3::splat(0.07)),
..default()
});
commands.spawn((
SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
Transform::from_scale(Vec3::splat(0.07)),
));
// Ground

View file

@ -118,11 +118,12 @@ fn setup_scene(
));
// Spawn the fox.
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
transform: Transform::from_scale(Vec3::splat(0.07)),
..default()
});
commands.spawn((
SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
Transform::from_scale(Vec3::splat(0.07)),
));
// Spawn the ground.
commands.spawn((

View file

@ -26,11 +26,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
// Spawn the first scene in `models/SimpleSkin/SimpleSkin.gltf`
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/SimpleSkin/SimpleSkin.gltf")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/SimpleSkin/SimpleSkin.gltf"),
)));
}
/// The scene hierarchy currently looks somewhat like this:

View file

@ -46,11 +46,9 @@ fn setup(asset_server: Res<AssetServer>, mut commands: Commands) {
.from_asset("models/animated/MorphStressTest.gltf"),
),
});
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/animated/MorphStressTest.gltf")),
..default()
});
commands.spawn(SceneRoot(asset_server.load(
GltfAssetLabel::Scene(0).from_asset("models/animated/MorphStressTest.gltf"),
)));
commands.spawn((
DirectionalLight::default(),
Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),

View file

@ -23,10 +23,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// You should see the changes immediately show up in your app.
// mesh
commands.spawn(SceneBundle {
scene: scene_handle,
..default()
});
commands.spawn(SceneRoot(scene_handle));
// light
commands.spawn((
DirectionalLight::default(),

View file

@ -261,11 +261,7 @@ fn wait_on_load(
// All gltfs must exist because this is guarded by the `AssetBarrier`.
let gltf = gltfs.get(&foxes.0[index]).unwrap();
let scene = gltf.scenes.first().unwrap().clone();
commands.spawn(SceneBundle {
scene,
transform: Transform::from_translation(position),
..Default::default()
});
commands.spawn((SceneRoot(scene), Transform::from_translation(position)));
}
}
}

View file

@ -78,14 +78,12 @@ fn setup(
commands.spawn((
Name::new("Fox"),
SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
),
// Note: the scale adjustment is purely an accident of our fox model, which renders
// HUGE unless mitigated!
transform: Transform::from_translation(Vec3::splat(0.0)).with_scale(Vec3::splat(0.025)),
..default()
},
Transform::from_translation(Vec3::splat(0.0)).with_scale(Vec3::splat(0.025)),
));
commands.spawn((

View file

@ -139,11 +139,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
(0..BOARD_SIZE_I)
.map(|i| {
let height = rng.gen_range(-0.1..0.1);
commands.spawn(SceneBundle {
transform: Transform::from_xyz(i as f32, height - 0.2, j as f32),
scene: cell_scene.clone(),
..default()
});
commands.spawn((
Transform::from_xyz(i as f32, height - 0.2, j as f32),
SceneRoot(cell_scene.clone()),
));
Cell { height }
})
.collect()
@ -153,8 +152,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
// spawn the game character
game.player.entity = Some(
commands
.spawn(SceneBundle {
transform: Transform {
.spawn((
Transform {
translation: Vec3::new(
game.player.i as f32,
game.board[game.player.j][game.player.i].height,
@ -163,10 +162,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
rotation: Quat::from_rotation_y(-PI / 2.),
..default()
},
scene: asset_server
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/AlienCake/alien.glb")),
..default()
})
),
))
.id(),
);
@ -345,15 +345,14 @@ fn spawn_bonus(
}
game.bonus.entity = Some(
commands
.spawn(SceneBundle {
transform: Transform::from_xyz(
.spawn((
Transform::from_xyz(
game.bonus.i as f32,
game.board[game.bonus.j][game.bonus.i].height + 0.2,
game.bonus.j as f32,
),
scene: game.bonus.handle.clone(),
..default()
})
SceneRoot(game.bonus.handle.clone()),
))
.with_child((
PointLight {
color: Color::srgb(1.0, 1.0, 0.0),

View file

@ -154,11 +154,8 @@ fn load_level_1(
loading_data.loading_assets.push(fox.clone().into());
// Spawn the fox.
commands.spawn((
SceneBundle {
scene: fox.clone(),
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..default()
},
SceneRoot(fox.clone()),
Transform::from_xyz(0.0, 0.0, 0.0),
LevelComponents,
));
@ -194,13 +191,7 @@ fn load_level_2(
loading_data
.loading_assets
.push(helmet_scene.clone().into());
commands.spawn((
SceneBundle {
scene: helmet_scene.clone(),
..default()
},
LevelComponents,
));
commands.spawn((SceneRoot(helmet_scene.clone()), LevelComponents));
// Spawn the light.
commands.spawn((

View file

@ -65,13 +65,10 @@ const SCENE_FILE_PATH: &str = "scenes/load_scene_example.scn.ron";
const NEW_SCENE_FILE_PATH: &str = "scenes/load_scene_example-new.scn.ron";
fn load_scene_system(mut commands: Commands, asset_server: Res<AssetServer>) {
// "Spawning" a scene bundle creates a new entity and spawns new instances
// Spawning a DynamicSceneRoot creates a new entity and spawns new instances
// of the given scene's entities as children of that entity.
commands.spawn(DynamicSceneBundle {
// Scenes are loaded just like any other asset.
scene: asset_server.load(SCENE_FILE_PATH),
..default()
});
// Scenes can be loaded just like any other asset.
commands.spawn(DynamicSceneRoot(asset_server.load(SCENE_FILE_PATH)));
}
// This system logs all ComponentA components in our world. Try making a change to a ComponentA in

View file

@ -172,13 +172,12 @@ fn setup(
let (x, z) = (radius * c, radius * s);
commands.entity(ring_parent).with_children(|builder| {
builder.spawn(SceneBundle {
scene: fox_handle.clone(),
transform: Transform::from_xyz(x, 0.0, z)
builder.spawn((
SceneRoot(fox_handle.clone()),
Transform::from_xyz(x, 0.0, z)
.with_scale(Vec3::splat(0.01))
.with_rotation(base_rotation * Quat::from_rotation_y(-fox_angle)),
..default()
});
));
});
}

View file

@ -81,11 +81,10 @@ fn setup(
// Finally, our ship that is going to rotate
commands.spawn((
SceneBundle {
scene: asset_server
SceneRoot(
asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/ship/craft_speederD.gltf")),
..default()
},
),
Ship {
target_transform: random_axes_target_alignment(&RandomAxes(first, second)),
..default()

View file

@ -12,10 +12,9 @@ fn main() {
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// add entities to the world
commands.spawn(SceneBundle {
scene: asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf")),
..default()
});
commands.spawn(SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/torus/torus.gltf")),
));
// light
commands.spawn((
DirectionalLight::default(),