bevy_scene_hook/
hook.rs

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