Bevy Scene hook

A one-file 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
[dependencies]
bevy-scene-hook = "2.0"
The following snippet of code is extracted from Warlock's Gambit source
code.
It loads the scene.glb file when the game state becomes GameState::Loading.
It then runs the Scene::hook method once using the Scene::when_not_spawned run
criteria, Scene::hook runs the Scene::hook_named_node method for each named
entity in the scene and adds specific components to them. Most notably
the animation ones. It then leaves the GameState::Loading state using the
Scene::when_spawned run criteria.
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.
use bevy::{
ecs::system::EntityCommands,
prelude::{Plugin as BevyPlugin, *},
};
use bevy_scene_hook::{SceneHook, SceneInstance};
use crate::{
animate::Animated,
camera::PlayerCam,
common_components::{OppoCardSpawner, OppoHand, PlayerCardSpawner, PlayerHand, PlayerSleeve},
pile::{Pile, PileType},
state::GameState,
};
pub enum Scene {}
impl SceneHook for Scene {
fn hook_named_node(name: &Name, cmds: &mut EntityCommands) {
match name.as_str() {
"PlayerPerspective_Orientation" => cmds.insert(PlayerCam),
"PlayerCardSpawn" => cmds.insert(PlayerCardSpawner),
"OppoCardSpawn" => cmds.insert(OppoCardSpawner),
"OppoHand" => cmds.insert_bundle((OppoHand, Animated::bob(1.0, 0.3, 6.0))),
"PlayerHand" => cmds.insert_bundle((PlayerHand, Animated::bob(2.0, 0.05, 7.0))),
"Pile" => cmds.insert(Pile::new(PileType::War)),
"OppoPile" => cmds.insert(Pile::new(PileType::Oppo)),
"PlayerPile" => cmds.insert(Pile::new(PileType::Player)),
"ManBody" => cmds.insert(Animated::breath(0.0, 0.03, 6.0)),
"ManHead" => cmds.insert(Animated::bob(6. / 4., 0.1, 6.0)),
"Bird" => cmds.insert(Animated::breath(0.0, 0.075, 5.0)),
"BirdEyePupilla" => cmds.insert(Animated::bob(5. / 4., 0.02, 5.0)),
"PlayerSleeveStash" => cmds.insert(PlayerSleeve),
_ => cmds,
};
}
}
fn setup_scene(
mut cmds: Commands,
mut scene_spawner: ResMut<SceneSpawner>,
asset_server: Res<AssetServer>,
) {
let gltf = scene_spawner.spawn(asset_server.load("scene.glb#Scene0"));
cmds.spawn().insert(SceneInstance::<Scene>::new(gltf));
}
fn exit_load_state(mut state: ResMut<State<GameState>>) {
if state.current() == &GameState::LoadScene {
state.set(GameState::Playing).unwrap();
}
}
pub struct Plugin;
impl BevyPlugin for Plugin {
fn build(&self, app: &mut App) {
app.add_system_set(SystemSet::on_enter(GameState::Loading).with_system(setup_scene))
.add_system(exit_load_state.with_run_criteria(Scene::when_spawned))
.add_system(Scene::hook.with_run_criteria(Scene::when_not_spawned));
}
}
If SceneHook is too weak for you and you need access to queries or world
resources (mutable or not), you can use world::SceneHook instead.
Limitations
- You will need to keep track of what you name what.
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)
Version matrix
| bevy |
latest supporting version |
| 0.7 |
2.0.0 |
| 0.6 |
1.2.0 |
License
This library is licensed under Apache 2.0.