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}