Skip to content

Commit

Permalink
Migrate scenes to required components (#15579)
Browse files Browse the repository at this point in the history
# 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),
));
```
  • Loading branch information
tim-blackbird authored Oct 1, 2024
1 parent ead84e0 commit eb51b4c
Show file tree
Hide file tree
Showing 43 changed files with 272 additions and 309 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_animation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
27 changes: 11 additions & 16 deletions crates/bevy_gltf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
//! ));
//! }
//! ```
//!
Expand Down
45 changes: 25 additions & 20 deletions crates/bevy_scene/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy_asset::Handle;
#![expect(deprecated)]

use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
bundle::Bundle,
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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::{
Expand All @@ -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)]
Expand Down Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions crates/bevy_scene/src/components.rs
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 1 addition & 4 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 13 additions & 8 deletions crates/bevy_scene/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
extern crate alloc;

mod bundle;
mod components;
mod dynamic_scene;
mod dynamic_scene_builder;
mod scene;
Expand All @@ -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::*;
Expand All @@ -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)]
Expand All @@ -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();
Expand All @@ -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 {
Expand Down
5 changes: 1 addition & 4 deletions crates/bevy_scene/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ mod tests {
};
use bevy_reflect::Reflect;

use crate::{DynamicSceneBuilder, ScenePlugin};
use crate::{DynamicSceneBuilder, DynamicSceneRoot, ScenePlugin};

use super::*;

Expand Down Expand Up @@ -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();
Expand Down
9 changes: 4 additions & 5 deletions examples/3d/anisotropy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
8 changes: 3 additions & 5 deletions examples/3d/anti_aliasing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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((
Expand Down
8 changes: 3 additions & 5 deletions examples/3d/atmospheric_fog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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((
Expand Down
15 changes: 7 additions & 8 deletions examples/3d/clearcoat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit eb51b4c

Please sign in to comment.