Skip to content

Commit

Permalink
add a SceneBundle to spawn a scene (#2424)
Browse files Browse the repository at this point in the history
# 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]>
  • Loading branch information
3 people committed Jun 9, 2022
1 parent cdb62af commit c6958b3
Show file tree
Hide file tree
Showing 18 changed files with 262 additions and 187 deletions.
12 changes: 10 additions & 2 deletions crates/bevy_hierarchy/src/components/parent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
Expand All @@ -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(())
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_scene/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions crates/bevy_scene/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -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<Scene>,
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<DynamicScene>,
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<Scene>, Option<&mut SceneInstance>),
(Changed<Handle<Scene>>, Without<Handle<DynamicScene>>),
>,
mut dynamic_scene_to_spawn: Query<
(Entity, &Handle<DynamicScene>, Option<&mut SceneInstance>),
(Changed<Handle<DynamicScene>>, Without<Handle<Scene>>),
>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
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));
}
}
}
56 changes: 0 additions & 56 deletions crates/bevy_scene/src/command.rs

This file was deleted.

6 changes: 6 additions & 0 deletions crates/bevy_scene/src/dynamic_scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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)
#[derive(Default, TypeUuid)]
#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"]
pub struct DynamicScene {
Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_scene/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
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::*;
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::*;
Expand All @@ -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);
}
}
6 changes: 6 additions & 0 deletions crates/bevy_scene/src/scene.rs
Original file line number Diff line number Diff line change
@@ -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<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)
#[derive(Debug, TypeUuid)]
#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"]
pub struct Scene {
Expand Down
70 changes: 55 additions & 15 deletions crates/bevy_scene/src/scene_spawner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ pub struct SceneSpawner {
spawned_dynamic_scenes: HashMap<Handle<DynamicScene>, Vec<InstanceId>>,
spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<Handle<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
scenes_to_despawn: Vec<Handle<DynamicScene>>,
instances_to_despawn: Vec<InstanceId>,
scenes_with_parent: Vec<(InstanceId, Entity)>,
}

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

impl SceneSpawner {
pub fn spawn_dynamic(&mut self, scene_handle: Handle<DynamicScene>) {
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<DynamicScene>,
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<Scene>) -> InstanceId {
Expand All @@ -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<DynamicScene>,
) -> 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,
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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));
Expand Down
5 changes: 4 additions & 1 deletion examples/3d/load_gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ fn main() {
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
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()
Expand All @@ -37,6 +36,10 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
});
commands.spawn_bundle(SceneBundle {
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
..default()
});
}

fn animate_light_direction(
Expand Down
5 changes: 4 additions & 1 deletion examples/3d/split_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit c6958b3

Please sign in to comment.