1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
//! Systems to insert components on loaded scenes.
//!
//! Please see the [`SceneHook`] documentation for detailed examples.
use bevy::ecs::{
component::Component,
entity::Entity,
prelude::{Without, World},
system::{Commands, EntityCommands, Query, Res},
world::EntityRef,
};
use bevy::scene::{SceneInstance, SceneSpawner};
/// Marker Component for scenes that were hooked.
#[derive(Component, Debug)]
#[non_exhaustive]
pub struct SceneHooked;
/// Add this as a component to any entity to run `hook`
/// when the scene is loaded.
///
/// You can use it to add your own non-serializable components to entites
/// present in a scene file.
///
/// A typical usage is adding animation, physics collision data or marker
/// components to a scene spawned from a file format that do not support it.
///
/// # Example
///
/// ```rust
/// # use bevy::ecs::{system::Res, component::Component, system::Commands};
/// # use bevy::asset::AssetServer;
/// # use bevy::utils::default;
/// # use bevy::scene::SceneBundle;
/// use bevy_scene_hook::{SceneHook, HookedSceneBundle};
/// # #[derive(Component)]
/// # struct Name; impl Name { fn as_str(&self) -> &str { todo!() } }
/// enum PileType { Drawing }
///
/// #[derive(Component)]
/// struct Pile(PileType);
///
/// #[derive(Component)]
/// struct Card;
///
/// fn load_scene(mut cmds: Commands, asset_server: Res<AssetServer>) {
/// cmds.spawn_bundle(HookedSceneBundle {
/// scene: SceneBundle { scene: asset_server.load("scene.glb#Scene0"), ..default() },
/// hook: SceneHook::new(|entity, cmds| {
/// match entity.get::<Name>().map(|t|t.as_str()) {
/// Some("Pile") => cmds.insert(Pile(PileType::Drawing)),
/// Some("Card") => cmds.insert(Card),
/// _ => cmds,
/// };
/// }),
/// });
/// }
/// ```
#[derive(Component)]
pub struct SceneHook {
hook: Box<dyn Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>,
}
impl SceneHook {
/// Add a hook to a scene, to run for each entities when the scene is
/// loaded.
///
/// The hook adds [`Component`]s or do anything with entity in the spawned
/// scene refered by `EntityRef`.
///
/// # Example
///
/// ```rust
/// # use bevy::ecs::{
/// # world::EntityRef, component::Component,
/// # system::{Commands, Res, Resource, EntityCommands}
/// # };
/// # use bevy::asset::{AssetServer, Handle};
/// # use bevy::utils::default;
/// # use bevy::scene::{Scene, SceneBundle};
/// use bevy_scene_hook::{SceneHook, HookedSceneBundle};
/// # #[derive(Component)] struct Name;
/// # type DeckData = Scene;
/// #[derive(Clone, Resource)]
/// struct DeckAssets { player: Handle<DeckData>, oppo: Handle<DeckData> }
///
/// fn hook(decks: &DeckAssets, entity: &EntityRef, cmds: &mut EntityCommands) {}
/// fn load_scene(mut cmds: Commands, decks: Res<DeckAssets>, assets: Res<AssetServer>) {
/// let decks = decks.clone();
/// cmds.spawn_bundle(HookedSceneBundle {
/// scene: SceneBundle { scene: assets.load("scene.glb#Scene0"), ..default() },
/// hook: SceneHook::new(move |entity, cmds| hook(&decks, entity, cmds)),
/// });
/// }
/// ```
pub fn new<F: Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>(hook: F) -> Self {
Self {
hook: Box::new(hook),
}
}
}
/// Run once [`SceneHook`]s added to [`SceneBundle`](crate::SceneBundle) or
/// [`DynamicSceneBundle`](crate::DynamicSceneBundle) when the scenes are loaded.
pub fn run_hooks(
unloaded_instances: Query<(Entity, &SceneInstance, &SceneHook), Without<SceneHooked>>,
scene_manager: Res<SceneSpawner>,
world: &World,
mut cmds: Commands,
) {
for (entity, instance, hooked) in unloaded_instances.iter() {
if scene_manager.instance_is_ready(**instance) {
cmds.entity(entity).insert(SceneHooked);
}
let entities = scene_manager.iter_instance_entities(**instance);
for entity_ref in entities.filter_map(|e| world.get_entity(e)) {
let mut cmd = cmds.entity(entity_ref.id());
(hooked.hook)(&entity_ref, &mut cmd);
}
}
}