Skip to content

Commit c6958b3

Browse files
mockersfcart
andcommitted
add a SceneBundle to spawn a scene (#2424)
# Objective - Spawning a scene is handled as a special case with a command `spawn_scene` that takes an handle but doesn't let you specify anything else. This is the only handle that works that way. - Workaround for this have been to add the `spawn_scene` on `ChildBuilder` to be able to specify transform of parent, or to make the `SceneSpawner` available to be able to select entities from a scene by their instance id ## Solution Add a bundle ```rust pub struct SceneBundle { pub scene: Handle<Scene>, pub transform: Transform, pub global_transform: GlobalTransform, pub instance_id: Option<InstanceId>, } ``` and instead of ```rust commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); ``` you can do ```rust commands.spawn_bundle(SceneBundle { scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), ..Default::default() }); ``` The scene will be spawned as a child of the entity with the `SceneBundle` ~I would like to remove the command `spawn_scene` in favor of this bundle but didn't do it yet to get feedback first~ Co-authored-by: François <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
1 parent cdb62af commit c6958b3

18 files changed

+262
-187
lines changed

crates/bevy_hierarchy/src/components/parent.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ impl FromWorld for Parent {
2525

2626
impl MapEntities for Parent {
2727
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
28-
self.0 = entity_map.get(self.0)?;
28+
// Parent of an entity in the new world can be in outside world, in which case it
29+
// should not be mapped.
30+
if let Ok(mapped_entity) = entity_map.get(self.0) {
31+
self.0 = mapped_entity;
32+
}
2933
Ok(())
3034
}
3135
}
@@ -51,7 +55,11 @@ pub struct PreviousParent(pub(crate) Entity);
5155

5256
impl MapEntities for PreviousParent {
5357
fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> {
54-
self.0 = entity_map.get(self.0)?;
58+
// PreviousParent of an entity in the new world can be in outside world, in which
59+
// case it should not be mapped.
60+
if let Ok(mapped_entity) = entity_map.get(self.0) {
61+
self.0 = mapped_entity;
62+
}
5563
Ok(())
5664
}
5765
}

crates/bevy_scene/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ keywords = ["bevy"]
1212
# bevy
1313
bevy_app = { path = "../bevy_app", version = "0.8.0-dev" }
1414
bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" }
15+
bevy_derive = { path = "../bevy_derive", version = "0.8.0-dev" }
1516
bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" }
1617
bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] }
1718
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.8.0-dev" }
19+
bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" }
1820
bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }
1921

2022
# other

crates/bevy_scene/src/bundle.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use bevy_asset::Handle;
2+
use bevy_derive::{Deref, DerefMut};
3+
use bevy_ecs::{
4+
bundle::Bundle,
5+
change_detection::ResMut,
6+
entity::Entity,
7+
prelude::{Changed, Component, Without},
8+
system::{Commands, Query},
9+
};
10+
use bevy_transform::components::{GlobalTransform, Transform};
11+
12+
use crate::{DynamicScene, InstanceId, Scene, SceneSpawner};
13+
14+
/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to
15+
/// interact with the spawned scene.
16+
#[derive(Component, Deref, DerefMut)]
17+
pub struct SceneInstance(InstanceId);
18+
19+
/// A component bundle for a [`Scene`] root.
20+
///
21+
/// The scene from `scene` will be spawn as a child of the entity with this component.
22+
/// Once it's spawned, the entity will have a [`SceneInstance`] component.
23+
#[derive(Default, Bundle)]
24+
pub struct SceneBundle {
25+
/// Handle to the scene to spawn
26+
pub scene: Handle<Scene>,
27+
pub transform: Transform,
28+
pub global_transform: GlobalTransform,
29+
}
30+
31+
/// A component bundle for a [`DynamicScene`] root.
32+
///
33+
/// The dynamic scene from `scene` will be spawn as a child of the entity with this component.
34+
/// Once it's spawned, the entity will have a [`SceneInstance`] component.
35+
#[derive(Default, Bundle)]
36+
pub struct DynamicSceneBundle {
37+
/// Handle to the scene to spawn
38+
pub scene: Handle<DynamicScene>,
39+
pub transform: Transform,
40+
pub global_transform: GlobalTransform,
41+
}
42+
43+
/// System that will spawn scenes from [`SceneBundle`].
44+
pub fn scene_spawner(
45+
mut commands: Commands,
46+
mut scene_to_spawn: Query<
47+
(Entity, &Handle<Scene>, Option<&mut SceneInstance>),
48+
(Changed<Handle<Scene>>, Without<Handle<DynamicScene>>),
49+
>,
50+
mut dynamic_scene_to_spawn: Query<
51+
(Entity, &Handle<DynamicScene>, Option<&mut SceneInstance>),
52+
(Changed<Handle<DynamicScene>>, Without<Handle<Scene>>),
53+
>,
54+
mut scene_spawner: ResMut<SceneSpawner>,
55+
) {
56+
for (entity, scene, instance) in scene_to_spawn.iter_mut() {
57+
let new_instance = scene_spawner.spawn_as_child(scene.clone(), entity);
58+
if let Some(mut old_instance) = instance {
59+
scene_spawner.despawn_instance(**old_instance);
60+
*old_instance = SceneInstance(new_instance);
61+
} else {
62+
commands.entity(entity).insert(SceneInstance(new_instance));
63+
}
64+
}
65+
for (entity, dynamic_scene, instance) in dynamic_scene_to_spawn.iter_mut() {
66+
let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.clone(), entity);
67+
if let Some(mut old_instance) = instance {
68+
scene_spawner.despawn_instance(**old_instance);
69+
*old_instance = SceneInstance(new_instance);
70+
} else {
71+
commands.entity(entity).insert(SceneInstance(new_instance));
72+
}
73+
}
74+
}

crates/bevy_scene/src/command.rs

Lines changed: 0 additions & 56 deletions
This file was deleted.

crates/bevy_scene/src/dynamic_scene.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid};
99
use serde::Serialize;
1010

1111
/// A collection of serializable dynamic entities, each with its own run-time defined set of components.
12+
/// To spawn a dynamic scene, you can use either:
13+
/// * [`SceneSpawner::spawn_dynamic`](crate::SceneSpawner::spawn_dynamic)
14+
/// * adding the [`DynamicSceneBundle`](crate::DynamicSceneBundle) to an entity
15+
/// * adding the [`Handle<DynamicScene>`](bevy_asset::Handle) to an entity (the scene will only be
16+
/// visible if the entity already has [`Transform`](bevy_transform::components::Transform) and
17+
/// [`GlobalTransform`](bevy_transform::components::GlobalTransform) components)
1218
#[derive(Default, TypeUuid)]
1319
#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"]
1420
pub struct DynamicScene {

crates/bevy_scene/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
mod command;
1+
mod bundle;
22
mod dynamic_scene;
33
mod scene;
44
mod scene_loader;
55
mod scene_spawner;
66
pub mod serde;
77

8-
pub use command::*;
8+
pub use bundle::*;
99
pub use dynamic_scene::*;
1010
pub use scene::*;
1111
pub use scene_loader::*;
1212
pub use scene_spawner::*;
1313

1414
pub mod prelude {
1515
#[doc(hidden)]
16-
pub use crate::{
17-
DynamicScene, Scene, SceneSpawner, SpawnSceneAsChildCommands, SpawnSceneCommands,
18-
};
16+
pub use crate::{DynamicScene, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner};
1917
}
2018

2119
use bevy_app::prelude::*;
@@ -34,6 +32,8 @@ impl Plugin for ScenePlugin {
3432
.add_system_to_stage(
3533
CoreStage::PreUpdate,
3634
scene_spawner_system.exclusive_system().at_end(),
37-
);
35+
)
36+
// Systems `*_bundle_spawner` must run before `scene_spawner_system`
37+
.add_system_to_stage(CoreStage::PreUpdate, scene_spawner);
3838
}
3939
}

crates/bevy_scene/src/scene.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
use bevy_ecs::world::World;
22
use bevy_reflect::TypeUuid;
33

4+
/// To spawn a scene, you can use either:
5+
/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn)
6+
/// * adding the [`SceneBundle`](crate::SceneBundle) to an entity
7+
/// * adding the [`Handle<Scene>`](bevy_asset::Handle) to an entity (the scene will only be
8+
/// visible if the entity already has [`Transform`](bevy_transform::components::Transform) and
9+
/// [`GlobalTransform`](bevy_transform::components::GlobalTransform) components)
410
#[derive(Debug, TypeUuid)]
511
#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"]
612
pub struct Scene {

crates/bevy_scene/src/scene_spawner.rs

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ pub struct SceneSpawner {
3333
spawned_dynamic_scenes: HashMap<Handle<DynamicScene>, Vec<InstanceId>>,
3434
spawned_instances: HashMap<InstanceId, InstanceInfo>,
3535
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
36-
dynamic_scenes_to_spawn: Vec<Handle<DynamicScene>>,
36+
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
3737
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
3838
scenes_to_despawn: Vec<Handle<DynamicScene>>,
39+
instances_to_despawn: Vec<InstanceId>,
3940
scenes_with_parent: Vec<(InstanceId, Entity)>,
4041
}
4142

@@ -53,7 +54,21 @@ pub enum SceneSpawnError {
5354

5455
impl SceneSpawner {
5556
pub fn spawn_dynamic(&mut self, scene_handle: Handle<DynamicScene>) {
56-
self.dynamic_scenes_to_spawn.push(scene_handle);
57+
let instance_id = InstanceId::new();
58+
self.dynamic_scenes_to_spawn
59+
.push((scene_handle, instance_id));
60+
}
61+
62+
pub fn spawn_dynamic_as_child(
63+
&mut self,
64+
scene_handle: Handle<DynamicScene>,
65+
parent: Entity,
66+
) -> InstanceId {
67+
let instance_id = InstanceId::new();
68+
self.dynamic_scenes_to_spawn
69+
.push((scene_handle, instance_id));
70+
self.scenes_with_parent.push((instance_id, parent));
71+
instance_id
5772
}
5873

5974
pub fn spawn(&mut self, scene_handle: Handle<Scene>) -> InstanceId {
@@ -73,26 +88,31 @@ impl SceneSpawner {
7388
self.scenes_to_despawn.push(scene_handle);
7489
}
7590

91+
pub fn despawn_instance(&mut self, instance_id: InstanceId) {
92+
self.instances_to_despawn.push(instance_id);
93+
}
94+
7695
pub fn despawn_sync(
7796
&mut self,
7897
world: &mut World,
7998
scene_handle: Handle<DynamicScene>,
8099
) -> Result<(), SceneSpawnError> {
81-
if let Some(instance_ids) = self.spawned_dynamic_scenes.get(&scene_handle) {
100+
if let Some(instance_ids) = self.spawned_dynamic_scenes.remove(&scene_handle) {
82101
for instance_id in instance_ids {
83-
if let Some(instance) = self.spawned_instances.get(instance_id) {
84-
for entity in instance.entity_map.values() {
85-
let _ = world.despawn(entity); // Ignore the result, despawn only cares if
86-
// it exists.
87-
}
88-
}
102+
self.despawn_instance_sync(world, &instance_id);
89103
}
90-
91-
self.spawned_dynamic_scenes.remove(&scene_handle);
92104
}
93105
Ok(())
94106
}
95107

108+
pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) {
109+
if let Some(instance) = self.spawned_instances.remove(instance_id) {
110+
for entity in instance.entity_map.values() {
111+
let _ = world.despawn(entity);
112+
}
113+
}
114+
}
115+
96116
pub fn spawn_dynamic_sync(
97117
&mut self,
98118
world: &mut World,
@@ -235,14 +255,33 @@ impl SceneSpawner {
235255
Ok(())
236256
}
237257

258+
pub fn despawn_queued_instances(&mut self, world: &mut World) {
259+
let instances_to_despawn = std::mem::take(&mut self.instances_to_despawn);
260+
261+
for instance_id in instances_to_despawn {
262+
self.despawn_instance_sync(world, &instance_id);
263+
}
264+
}
265+
238266
pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
239267
let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn);
240268

241-
for scene_handle in scenes_to_spawn {
242-
match self.spawn_dynamic_sync(world, &scene_handle) {
243-
Ok(_) => {}
269+
for (scene_handle, instance_id) in scenes_to_spawn {
270+
let mut entity_map = EntityMap::default();
271+
272+
match Self::spawn_dynamic_internal(world, &scene_handle, &mut entity_map) {
273+
Ok(_) => {
274+
self.spawned_instances
275+
.insert(instance_id, InstanceInfo { entity_map });
276+
let spawned = self
277+
.spawned_dynamic_scenes
278+
.entry(scene_handle.clone())
279+
.or_insert_with(Vec::new);
280+
spawned.push(instance_id);
281+
}
244282
Err(SceneSpawnError::NonExistentScene { .. }) => {
245-
self.dynamic_scenes_to_spawn.push(scene_handle);
283+
self.dynamic_scenes_to_spawn
284+
.push((scene_handle, instance_id));
246285
}
247286
Err(err) => return Err(err),
248287
}
@@ -327,6 +366,7 @@ pub fn scene_spawner_system(world: &mut World) {
327366
}
328367

329368
scene_spawner.despawn_queued_scenes(world).unwrap();
369+
scene_spawner.despawn_queued_instances(world);
330370
scene_spawner
331371
.spawn_queued_scenes(world)
332372
.unwrap_or_else(|err| panic!("{}", err));

examples/3d/load_gltf.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ fn main() {
1515
}
1616

1717
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
18-
commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
1918
commands.spawn_bundle(Camera3dBundle {
2019
transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
2120
..default()
@@ -37,6 +36,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
3736
},
3837
..default()
3938
});
39+
commands.spawn_bundle(SceneBundle {
40+
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
41+
..default()
42+
});
4043
}
4144

4245
fn animate_light_direction(

examples/3d/split_screen.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ fn setup(
2929
..default()
3030
});
3131

32-
commands.spawn_scene(asset_server.load("models/animated/Fox.glb#Scene0"));
32+
commands.spawn_bundle(SceneBundle {
33+
scene: asset_server.load("models/animated/Fox.glb#Scene0"),
34+
..default()
35+
});
3336

3437
// Light
3538
commands.spawn_bundle(DirectionalLightBundle {

0 commit comments

Comments
 (0)