17 releases (9 major breaking)

10.0.0 Feb 19, 2024
9.0.0 Nov 4, 2023
8.0.0 Aug 30, 2023
7.0.0 Jul 9, 2023
1.2.0 Mar 22, 2022

#23 in Game dev

Download history 83/week @ 2023-12-22 43/week @ 2023-12-29 109/week @ 2024-01-05 51/week @ 2024-01-12 63/week @ 2024-01-19 55/week @ 2024-01-26 48/week @ 2024-02-02 95/week @ 2024-02-09 377/week @ 2024-02-16 346/week @ 2024-02-23 292/week @ 2024-03-01 171/week @ 2024-03-08 157/week @ 2024-03-15 263/week @ 2024-03-22 365/week @ 2024-03-29 336/week @ 2024-04-05

1,142 downloads per month
Used in 7 crates (3 directly)

Apache-2.0

39KB
197 lines

Bevy Scene hook

Bevy tracking Latest version Apache 2.0 Documentation

A proof of concept for adding components ad-hoc within code to entities spawned through scenes (such as gltf files) in the bevy game engine.

If you don't mind adding such a small dependency to your code rather than copy/pasting the code as a module, you can get it from crates.io.

Usage

  1. Add the crate to your dependencies
[dependencies]
bevy-scene-hook = "10.0.0"
  1. Add the plugin
.add_plugins(HookPlugin)

Example

use bevy::prelude::*;
use bevy_scene_hook::{SceneHook, HookedSceneBundle};

enum PileType { Drawing }

#[derive(Component)]
struct Pile(PileType);

#[derive(Component)]
struct Card;

fn load_scene(mut cmds: Commands, asset_server: Res<AssetServer>) {
   cmds.spawn(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,
           };
       }),
   });
}

It loads the scene.glb file when the game starts. When the scene is fully loaded, the closure passed to SceneHook::new is ran for each entity present in the scene. We add a Pile component to entities with a Name component of value "Pile".

It is possible to name object in glb scenes in blender using the Outliner dock (the tree view at the top right) and double-clicking object names.

Note that SceneHook hooks have access solely to one EntityRef and EntityCommands at a time, and ignore asset reloading.

Consider using this crate's reload::Hook for more advanced use-cases.

Implementation

bevy-scene-hook is a tinny crate, here is copy/pastable code you can directly vendor in your project:

use bevy::{
    prelude::*,
    scene::SceneInstance,
    ecs::{world::EntityRef, system::EntityCommands},
};
#[derive(Component, Debug)]
pub struct SceneHooked;

#[derive(Component)]
pub struct SceneHook {
    hook: Box<dyn Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>,
}
impl SceneHook {
    pub fn new<F: Fn(&EntityRef, &mut EntityCommands) + Send + Sync + 'static>(hook: F) -> Self {
        Self { hook: Box::new(hook) }
    }
}

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)
            .chain(std::iter::once(entity));
        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);
        }
    }
}

pub struct HookPlugin;
impl Plugin for HookPlugin {
    fn build(&self, app: &mut App) { app.add_systems(Update, run_hooks); }
}

Note that bevy-scene-hook also has a few items defined for user convinience:

  • HookedSceneBundle
  • HookedSceneState
  • is_scene_hooked

Those extra items are all defined in lib.rs.

Change log

  • 1.1.0: Add is_loaded method to SceneInstance
  • 1.2.0: Add the world module containing a SceneHook trait that has exclusive world access. Useful if you want access to assets for example.
  • 2.0.0: Breaking: bump bevy version to 0.7 (you should be able to upgrade from 1.2.0 without changing your code)
  • 3.0.0: Breaking: completely rework the crate.
    • Remove the world module, as the base hook method has become much more powerful.
    • Rename SceneHook to Hook, now Hook has a unique method to implement.
    • You don't have to add any system yourself, now you have to add the HookPlugin plugin to your app.
    • Move the API exclusively to a new SystemParam: HookingSceneSpawner. Please use that parameter to add and remove scenes that contain hooks. (please tell if you you accidentally spell it HonkingSceneSpawner more than once 🦆)
    • Moved the when_spawned run criteria to the is_scene_hooked function exposed at the root of the crate, the HookedSceneState system parameter or the SceneLoaded component. Please use any of those three instead of when_spawned.
    • Now supports passing closures as hooks, instead of having to define a trait each time.
    • Now supports adding multiple of the same scene! Doesn't handle hot-reloading, but that's alright since bevy's scene hot-reloading is currently broken anyway :D
  • 3.1.0: make run_hooks system public so that it's possible to add it to any stage you want in relation to any other system you want.
  • 4.0.0: Breaking: bump bevy version to 0.8
    • Uses the new scene bundle system
    • Rename SceneLoaded to SceneHooked.
    • Removed the Hook trait, now SceneHook::new accepts a closure.
    • Instead of using HookingSceneSpawner, uses HookedSceneBundle and spawn it into an entity.
  • 4.1.0: Add HookedDynamicSceneBundle to use with DynamicScenes. Thanks Shatur (https://github.com/nicopap/bevy-scene-hook/pull/3)
  • 5.0.0: Breaking: bump bevy version to 0.9
  • 5.1.1: My bad, I accidentally published to version 5.1.0 instead of 5.0.0
  • 5.1.2: Fix scenes never triggering hooks due to a missing check. Thanks sdfgeoff (#5) If you depend on bevy-scene-hook as a cargo dependency, you must run cargo update to get this fix.
  • 5.2.0: Add the reload module, defining reload::Hook, a variant of SceneHook that handles gracefully reloads and unloads.
  • 6.0.0: Breaking: bump bevy version to 0.10.
  • 7.0.0: Breaking: bump bevy version to 0.11.
  • 8.0.0: Add the root entity of the scene to the list of traversed scene entities.
    • May be breaking if you were blanket-adding components to entities & were relying on the root not being part of them.
    • Also if somehow the root has a property you were testing when adding components.
    • Thanks ickk (https://github.com/nicopap/bevy-scene-hook/pull/7)
  • 9.0.0: Breaking: bump bevy version to 0.12.
    • Move hook functions to the SpawnScene schedule
  • 10.0.0: Breaking: bump bevy version to 0.13.
    • Remove the file_path reload::Hook field in favor of the Handle::path method.
    • Add an example and test the Readme.

Version matrix

bevy latest supporting version
0.13 10.0.0
0.12 9.0.0
0.11 8.0.0
0.10 6.0.0
0.9 5.2.0
0.8 4.1.0
0.7 3.1.0
0.6 1.2.0

License

This library is licensed under Apache 2.0.

Dependencies

~20–58MB
~1M SLoC