diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index ddc0e2a634..525e56bdec 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -25,7 +25,11 @@ impl FromWorld for Parent { impl MapEntities for Parent { fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { - self.0 = entity_map.get(self.0)?; + // Parent of an entity in the new world can be in outside world, in which case it + // should not be mapped. + if let Ok(mapped_entity) = entity_map.get(self.0) { + self.0 = mapped_entity; + } Ok(()) } } @@ -51,7 +55,11 @@ pub struct PreviousParent(pub(crate) Entity); impl MapEntities for PreviousParent { fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { - self.0 = entity_map.get(self.0)?; + // PreviousParent of an entity in the new world can be in outside world, in which + // case it should not be mapped. + if let Ok(mapped_entity) = entity_map.get(self.0) { + self.0 = mapped_entity; + } Ok(()) } } diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 360d30bdc0..49313b486b 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -12,9 +12,11 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.8.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.8.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.8.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } # other diff --git a/crates/bevy_scene/src/bundle.rs b/crates/bevy_scene/src/bundle.rs new file mode 100644 index 0000000000..931de37556 --- /dev/null +++ b/crates/bevy_scene/src/bundle.rs @@ -0,0 +1,74 @@ +use bevy_asset::Handle; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{ + bundle::Bundle, + change_detection::ResMut, + entity::Entity, + prelude::{Changed, Component, Without}, + system::{Commands, Query}, +}; +use bevy_transform::components::{GlobalTransform, Transform}; + +use crate::{DynamicScene, InstanceId, Scene, 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(InstanceId); + +/// A component bundle for a [`Scene`] root. +/// +/// The 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)] +pub struct SceneBundle { + /// Handle to the scene to spawn + pub scene: Handle, + pub transform: Transform, + pub global_transform: GlobalTransform, +} + +/// A component bundle for a [`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)] +pub struct DynamicSceneBundle { + /// Handle to the scene to spawn + pub scene: Handle, + pub transform: Transform, + pub global_transform: GlobalTransform, +} + +/// System that will spawn scenes from [`SceneBundle`]. +pub fn scene_spawner( + mut commands: Commands, + mut scene_to_spawn: Query< + (Entity, &Handle, Option<&mut SceneInstance>), + (Changed>, Without>), + >, + mut dynamic_scene_to_spawn: Query< + (Entity, &Handle, Option<&mut SceneInstance>), + (Changed>, Without>), + >, + mut scene_spawner: ResMut, +) { + for (entity, scene, instance) in scene_to_spawn.iter_mut() { + let new_instance = scene_spawner.spawn_as_child(scene.clone(), entity); + if let Some(mut old_instance) = instance { + scene_spawner.despawn_instance(**old_instance); + *old_instance = SceneInstance(new_instance); + } else { + commands.entity(entity).insert(SceneInstance(new_instance)); + } + } + for (entity, dynamic_scene, instance) in dynamic_scene_to_spawn.iter_mut() { + let new_instance = scene_spawner.spawn_dynamic_as_child(dynamic_scene.clone(), entity); + if let Some(mut old_instance) = instance { + scene_spawner.despawn_instance(**old_instance); + *old_instance = SceneInstance(new_instance); + } else { + commands.entity(entity).insert(SceneInstance(new_instance)); + } + } +} diff --git a/crates/bevy_scene/src/command.rs b/crates/bevy_scene/src/command.rs deleted file mode 100644 index 55b8c4ec85..0000000000 --- a/crates/bevy_scene/src/command.rs +++ /dev/null @@ -1,56 +0,0 @@ -use bevy_asset::Handle; -use bevy_ecs::{ - entity::Entity, - system::{Command, Commands}, - world::World, -}; -use bevy_hierarchy::ChildBuilder; - -use crate::{Scene, SceneSpawner}; - -pub struct SpawnScene { - scene_handle: Handle, -} - -impl Command for SpawnScene { - fn write(self, world: &mut World) { - let mut spawner = world.resource_mut::(); - spawner.spawn(self.scene_handle); - } -} - -pub trait SpawnSceneCommands { - fn spawn_scene(&mut self, scene: Handle); -} - -impl<'w, 's> SpawnSceneCommands for Commands<'w, 's> { - fn spawn_scene(&mut self, scene_handle: Handle) { - self.add(SpawnScene { scene_handle }); - } -} - -pub struct SpawnSceneAsChild { - scene_handle: Handle, - parent: Entity, -} - -impl Command for SpawnSceneAsChild { - fn write(self, world: &mut World) { - let mut spawner = world.resource_mut::(); - spawner.spawn_as_child(self.scene_handle, self.parent); - } -} - -pub trait SpawnSceneAsChildCommands { - fn spawn_scene(&mut self, scene: Handle) -> &mut Self; -} - -impl<'w, 's, 'a> SpawnSceneAsChildCommands for ChildBuilder<'w, 's, 'a> { - fn spawn_scene(&mut self, scene_handle: Handle) -> &mut Self { - self.add_command(SpawnSceneAsChild { - scene_handle, - parent: self.parent_entity(), - }); - self - } -} diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 3f2402850f..2c46875f06 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -9,6 +9,12 @@ use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid}; use serde::Serialize; /// A collection of serializable dynamic entities, each with 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`](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) #[derive(Default, TypeUuid)] #[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"] pub struct DynamicScene { diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 7c35b9ba11..bf51a8fc7b 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,11 +1,11 @@ -mod command; +mod bundle; mod dynamic_scene; mod scene; mod scene_loader; mod scene_spawner; pub mod serde; -pub use command::*; +pub use bundle::*; pub use dynamic_scene::*; pub use scene::*; pub use scene_loader::*; @@ -13,9 +13,7 @@ pub use scene_spawner::*; pub mod prelude { #[doc(hidden)] - pub use crate::{ - DynamicScene, Scene, SceneSpawner, SpawnSceneAsChildCommands, SpawnSceneCommands, - }; + pub use crate::{DynamicScene, DynamicSceneBundle, Scene, SceneBundle, SceneSpawner}; } use bevy_app::prelude::*; @@ -34,6 +32,8 @@ impl Plugin for ScenePlugin { .add_system_to_stage( CoreStage::PreUpdate, scene_spawner_system.exclusive_system().at_end(), - ); + ) + // Systems `*_bundle_spawner` must run before `scene_spawner_system` + .add_system_to_stage(CoreStage::PreUpdate, scene_spawner); } } diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 0a8bd50b82..4cd871fa72 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,6 +1,12 @@ use bevy_ecs::world::World; use bevy_reflect::TypeUuid; +/// To spawn a scene, you can use either: +/// * [`SceneSpawner::spawn`](crate::SceneSpawner::spawn) +/// * adding the [`SceneBundle`](crate::SceneBundle) to an entity +/// * adding the [`Handle`](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) #[derive(Debug, TypeUuid)] #[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"] pub struct Scene { diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 142d299743..dd6cf489f1 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -33,9 +33,10 @@ pub struct SceneSpawner { spawned_dynamic_scenes: HashMap, Vec>, spawned_instances: HashMap, scene_asset_event_reader: ManualEventReader>, - dynamic_scenes_to_spawn: Vec>, + dynamic_scenes_to_spawn: Vec<(Handle, InstanceId)>, scenes_to_spawn: Vec<(Handle, InstanceId)>, scenes_to_despawn: Vec>, + instances_to_despawn: Vec, scenes_with_parent: Vec<(InstanceId, Entity)>, } @@ -53,7 +54,21 @@ pub enum SceneSpawnError { impl SceneSpawner { pub fn spawn_dynamic(&mut self, scene_handle: Handle) { - self.dynamic_scenes_to_spawn.push(scene_handle); + let instance_id = InstanceId::new(); + self.dynamic_scenes_to_spawn + .push((scene_handle, instance_id)); + } + + pub fn spawn_dynamic_as_child( + &mut self, + scene_handle: Handle, + parent: Entity, + ) -> InstanceId { + let instance_id = InstanceId::new(); + self.dynamic_scenes_to_spawn + .push((scene_handle, instance_id)); + self.scenes_with_parent.push((instance_id, parent)); + instance_id } pub fn spawn(&mut self, scene_handle: Handle) -> InstanceId { @@ -73,26 +88,31 @@ impl SceneSpawner { self.scenes_to_despawn.push(scene_handle); } + pub fn despawn_instance(&mut self, instance_id: InstanceId) { + self.instances_to_despawn.push(instance_id); + } + pub fn despawn_sync( &mut self, world: &mut World, scene_handle: Handle, ) -> Result<(), SceneSpawnError> { - if let Some(instance_ids) = self.spawned_dynamic_scenes.get(&scene_handle) { + if let Some(instance_ids) = self.spawned_dynamic_scenes.remove(&scene_handle) { for instance_id in instance_ids { - if let Some(instance) = self.spawned_instances.get(instance_id) { - for entity in instance.entity_map.values() { - let _ = world.despawn(entity); // Ignore the result, despawn only cares if - // it exists. - } - } + self.despawn_instance_sync(world, &instance_id); } - - self.spawned_dynamic_scenes.remove(&scene_handle); } Ok(()) } + pub fn despawn_instance_sync(&mut self, world: &mut World, instance_id: &InstanceId) { + if let Some(instance) = self.spawned_instances.remove(instance_id) { + for entity in instance.entity_map.values() { + let _ = world.despawn(entity); + } + } + } + pub fn spawn_dynamic_sync( &mut self, world: &mut World, @@ -235,14 +255,33 @@ impl SceneSpawner { Ok(()) } + pub fn despawn_queued_instances(&mut self, world: &mut World) { + let instances_to_despawn = std::mem::take(&mut self.instances_to_despawn); + + for instance_id in instances_to_despawn { + self.despawn_instance_sync(world, &instance_id); + } + } + pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> { let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn); - for scene_handle in scenes_to_spawn { - match self.spawn_dynamic_sync(world, &scene_handle) { - Ok(_) => {} + for (scene_handle, instance_id) in scenes_to_spawn { + let mut entity_map = EntityMap::default(); + + match Self::spawn_dynamic_internal(world, &scene_handle, &mut entity_map) { + Ok(_) => { + self.spawned_instances + .insert(instance_id, InstanceInfo { entity_map }); + let spawned = self + .spawned_dynamic_scenes + .entry(scene_handle.clone()) + .or_insert_with(Vec::new); + spawned.push(instance_id); + } Err(SceneSpawnError::NonExistentScene { .. }) => { - self.dynamic_scenes_to_spawn.push(scene_handle); + self.dynamic_scenes_to_spawn + .push((scene_handle, instance_id)); } Err(err) => return Err(err), } @@ -327,6 +366,7 @@ pub fn scene_spawner_system(world: &mut World) { } scene_spawner.despawn_queued_scenes(world).unwrap(); + scene_spawner.despawn_queued_instances(world); scene_spawner .spawn_queued_scenes(world) .unwrap_or_else(|err| panic!("{}", err)); diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs index ef4eed5739..6d29461808 100644 --- a/examples/3d/load_gltf.rs +++ b/examples/3d/load_gltf.rs @@ -15,7 +15,6 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), ..default() @@ -37,6 +36,10 @@ fn setup(mut commands: Commands, asset_server: Res) { }, ..default() }); + commands.spawn_bundle(SceneBundle { + scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + ..default() + }); } fn animate_light_direction( diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 1b0992c84b..70cc464ea2 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -29,7 +29,10 @@ fn setup( ..default() }); - commands.spawn_scene(asset_server.load("models/animated/Fox.glb#Scene0")); + commands.spawn_bundle(SceneBundle { + scene: asset_server.load("models/animated/Fox.glb#Scene0"), + ..default() + }); // Light commands.spawn_bundle(DirectionalLightBundle { diff --git a/examples/3d/update_gltf_scene.rs b/examples/3d/update_gltf_scene.rs index 230375c72a..69d2672c37 100644 --- a/examples/3d/update_gltf_scene.rs +++ b/examples/3d/update_gltf_scene.rs @@ -1,32 +1,20 @@ //! Update a scene from a glTF file, either by spawning the scene as a child of another entity, //! or by accessing the entities of the scene. -use bevy::{prelude::*, scene::InstanceId}; +use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .init_resource::() .add_startup_system(setup) - .add_system(scene_update) .add_system(move_scene_entities) .run(); } -// Resource to hold the scene `instance_id` until it is loaded -#[derive(Default)] -struct SceneInstance(Option); - -// Component that will be used to tag entities in the scene #[derive(Component)] -struct EntityInMyScene; +struct MovedScene; -fn setup( - mut commands: Commands, - asset_server: Res, - mut scene_spawner: ResMut, - mut scene_instance: ResMut, -) { +fn setup(mut commands: Commands, asset_server: Res) { commands.spawn_bundle(PointLightBundle { transform: Transform::from_xyz(4.0, 5.0, 4.0), ..default() @@ -37,56 +25,49 @@ fn setup( ..default() }); - // Spawn the scene as a child of another entity. This first scene will be translated backward - // with its parent - commands - .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, -1.0))) - .with_children(|parent| { - parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); - }); + // Spawn the scene as a child of this entity at the given transform + commands.spawn_bundle(SceneBundle { + transform: Transform::from_xyz(0.0, 0.0, -1.0), + scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + ..default() + }); - // Spawn a second scene, and keep its `instance_id` - let instance_id = - scene_spawner.spawn(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); - scene_instance.0 = Some(instance_id); + // Spawn a second scene, and add a tag component to be able to target it later + commands + .spawn_bundle(SceneBundle { + scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"), + ..default() + }) + .insert(MovedScene); } -// This system will wait for the scene to be ready, and then tag entities from -// the scene with `EntityInMyScene`. All entities from the second scene will be -// tagged -fn scene_update( - mut commands: Commands, - scene_spawner: Res, - scene_instance: Res, - mut done: Local, +// This system will move all entities that are descendants of MovedScene (which will be all entities spawned in the scene) +fn move_scene_entities( + time: Res