#component #gltf #bevy #assets #gamedev #game-engine

blenvy

Allows you to define Bevy components direclty inside gltf files and instanciate the components on the Bevy side

1 unstable release

0.1.0-alpha.1 Aug 14, 2024

#439 in Game dev

Download history 30/week @ 2024-08-19 40/week @ 2024-08-26 60/week @ 2024-09-02 18/week @ 2024-09-09 60/week @ 2024-09-16 52/week @ 2024-09-23 40/week @ 2024-09-30 50/week @ 2024-10-07 60/week @ 2024-10-14 32/week @ 2024-10-21 23/week @ 2024-10-28 36/week @ 2024-11-04 13/week @ 2024-11-11 74/week @ 2024-11-18 125/week @ 2024-11-25 47/week @ 2024-12-02

261 downloads per month
Used in 3 crates

MIT/Apache

155KB
3K SLoC

Crates.io Docs License Bevy tracking

blenvy

This crate allows you to

  • define Bevy components direclty inside gltf files and instanciate the components on the Bevy side.

  • define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy.

    • Allows you to create lightweight levels, where all assets are different gltf files and loaded after the main level is loaded
    • Allows you to spawn different entities from gtlf files at runtime in a clean manner, including simplified animation support !

    A blueprint is a set of overrideable components + a hierarchy: ie

      * just a Gltf file with Gltf_extras specifying components 
      * a component called BlueprintInfo
    

    Particularly useful when using Blender as an editor for the Bevy game engine, combined with the Blender add-on that do a lot of the work for you

  • allows you to create a Json export of all your components/ registered types. Its main use case is as a backbone for the blenvy Blender add-on, that allows you to add & edit components directly in Blender, using the actual type definitions from Bevy (and any of your custom types & components that you register in Bevy).

  • adds the ability to easilly save and load your game worlds for Bevy .

  • leverages blueprints & seperation between
    • dynamic entities : entities that can change during the lifetime of your app/game
    • static entities : entities that do NOT change (typically, a part of your levels/ environements)
  • and allows allow for :
    • a simple save/load workflow thanks to the above
    • ability to specify which entities to save or to exclude
    • ability to specify which components to save or to exclude
    • ability to specify which resources to save or to exclude
    • small(er) save files (only a portion of the entities is saved)

Particularly useful when using Blender as an editor for the Bevy game engine, combined with the Blender plugin that does a lot of the work for you (including spliting generating seperate gltf files for your static vs dynamic assets)

Usage

Here's a minimal usage example:

# Cargo.toml
[dependencies]
bevy="0.14"
blenvy = { version = "0.1.0-alpha.1"} 

use bevy::prelude::*;
use blenvy::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(BlenvyPlugin::default())

        .add_systems(Startup, setup_game)
        .add_systems(Update, spawn_blueprint_instance)
        .run();
}


// this is how you setup & spawn a level from a blueprint
fn setup_game(
    mut commands: Commands,
) {

    // here we spawn our game world/level, which is also a blueprint !
    commands.spawn((
        BlueprintInfo::from_path("levels/World.glb"), // all we need is a Blueprint info...
        SpawnBlueprint, // and spawnblueprint to tell blenvy to spawn the blueprint now
        HideUntilReady, // only reveal the level once it is ready
        GameWorldTag,
    ));
}

fn spawn_blueprint_instance(
    mut commands: Commands,
    keycode: Res<ButtonInput<KeyCode>>,
){
    if keycode.just_pressed(KeyCode::KeyS) {
        let new_entity = commands.spawn((
            BlueprintInfo::from_path("spawnable.glb"), // mandatory !!
            SpawnBlueprint, // mandatory !!
            TransformBundle::from_transform(Transform::from_xyz(0.0, 2.0, 0.2)), // VERY important !!
            // any other component you want to insert
        ));
    }
}

Installation

Add the following to your [dependencies] section in Cargo.toml:

blenvy = "0.1.0-alpha.1"

Or use cargo add:

cargo add blenvy

Setup

use bevy::prelude::*;
use blenvy::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugin(BlenvyPlugin::default())
        .run();
}

you may want to configure your settings:

use bevy::prelude::*;
use blenvy::*;

fn main() {
    App::new()
        .add_plugins((
             BlenvyPlugin{
                aabbs: true, // defaults to false, enable this to automatically calculate aabb for the scene/blueprint
                ..Default::default()
            }
        ))
        .run();
}

Spawning entities from blueprints

You can spawn entities from blueprints like this:

commands.spawn((
    BlueprintInfo::from_path("Health_Pickup.glb"), // mandatory !!
    // or the alterive: BlueprintInfo{name:"health pickup1".into(), path:"Health_Pickup.glb".into()}
    SpawnBlueprint, // mandatory !!
    
    TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // optional
    // any other component you want to insert
))

Once spawning of the actual entity is done, the contents (components, children etc) of the Blueprint will have been merged with those of the entity itself.

Important : you can add or override components present inside your Blueprint when spawning the BluePrint itself: ie

Adding components not specified inside the blueprint

you can just add any additional components you need when spawning :

commands.spawn((
    BlueprintInfo::from_path("Health_Pickup.glb"),
    SpawnBlueprint,
    TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
    // from Rapier/bevy_xpbd: this means the entity will also have a velocity component when inserted into the world
    Velocity {
        linvel: Vec3::new(vel_x, vel_y, vel_z),
        angvel: Vec3::new(0.0, 0.0, 0.0),
      },
))

Overriding components specified inside the blueprint

any component you specify when spawning the Blueprint that is also specified within the Blueprint will override that component in the final spawned entity

for example

commands.spawn((
    BlueprintInfo::from_path("Health_Pickup.glb"),
    SpawnBlueprint,
    TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)),
    HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning
))

BluePrintBundle

There is also a BluePrintBundle for convenience , which just has

  • a BlueprintInfo component
  • a SpawnBlueprint component

Additional information

  • When a blueprint is spawned, an FromBlueprint component is inserted into all its children entities (and nested children etc)
  • this crate also provides a special optional GameWorldTag component: this is useful when you want to keep all your spawned entities inside a root entity

You can use it in your queries to add your entities as children of this "world" This way all your levels, your dynamic entities etc, are kept seperated from UI nodes & other entities that are not relevant to the game world

Note: you should only have a SINGLE entity tagged with that component !

   commands.spawn((
        BlueprintInfo::from_path("levels/World.glb"), 
        SpawnBlueprint,
        HideUntilReady,
        GameWorldTag, // here it is
    ));

Registry

Blenvy automatically exports a Json file containing of all your registered components/ types, in order to create UIs that allows you to add & edit your components directly in Blender in the Blenvy Blender add-on

  • The output file will be generated in the Startup schedule whenever you run your app.
  • Every time you compile & run your app, the output json file will be updated.

Materials

Ff you enable it on the blender side, Blenvy will be using "material libraries" to share common textures/materials between blueprints, in order to avoid asset & memory bloat: Ie for example without this option, 56 different blueprints using the same material with a large texture would lead to the material/texture being embeded 56 times !!

Generating optimised blueprints and material libraries can be automated using the Blender plugin

Animation

blenvy provides some lightweight helpers to deal with animations stored in gltf files

It has both support for blueprint level animations (shared by all blueprint instance of the same blueprint)

  • an BlueprintAnimations component that gets inserted into spawned (root) entities that contains a hashmap of all animations contained inside that entity/gltf file .
  • an BlueprintAnimationPlayerLink component that gets inserted into spawned (root) entities, to make it easier to find Bevy's AnimationPlayer and AnimationTransitions components

And instance level animations (specific to one instance)

  • an InstanceAnimations component that gets inserted into spawned (root) entities that contains a hashmap of all animations specific to that instance .
  • an InstanceAnimationPlayerLink component that gets inserted into spawned (root) entities, to make it easier to find Bevy's AnimationPlayer and AnimationTransitions components for the animations above

The workflow for animations is as follows:

  • create a gltf file with animations (using Blender & co) as you would normally do
  • inside Bevy, use the blenvy boilerplate (see sections above), no specific setup beyond that is required
  • to control the animation of an entity, you need to query for entities that have both BlueprintAnimationPlayerLink and BlueprintAnimations components (added by blenvy) AND entities with the AnimationPlayer component

For example (blueprint animations):

pub fn trigger_blueprint_animations(
    animated_foxes: Query<(&BlueprintAnimationPlayerLink, &BlueprintAnimations), With<Fox>>,
    mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
    keycode: Res<ButtonInput<KeyCode>>,
){
    if keycode.just_pressed(KeyCode::KeyW) {
        for (link, animations) in animated_foxes.iter() {
            let (mut animation_player, mut animation_transitions) =
            animation_players.get_mut(link.0).unwrap();

            let anim_name = "Walk";
            animation_transitions
                .play(
                    &mut animation_player,
                    animations
                        .named_indices
                        .get(anim_name)
                        .expect("animation name should be in the list")
                        .clone(),
                    Duration::from_secs(5),
                )
                .repeat();
        }
    }
}

see https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation for how to set it up correctly

Additional features

this crate also includes automatic handling of lights in gltf files, to attempt to match Blender's eevee rendering as close as possible:

  • BlenderLightShadows (automatically generated by the gltf_auto_export Blender add-on) allows you to toggle light's shadows on/off in Blender and have matching behaviour in Bevy
  • BlenderBackgroundShader aka background color is also automatically set on the Bevy side
  • BlenderShadowSettings sets the cascade_size on the bevy side to match the one configured in Blender

Examples

https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/components

https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/blueprints

https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/animation

https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/save_load

https://github.com/kaosat-dev/Blenvy/tree/main/examples/blenvy/demo (a full fledged demo)

Compatible Bevy versions

The main branch is compatible with the latest Bevy release, while the branch bevy_main tries to track the main branch of Bevy (PRs updating the tracked commit are welcome).

Compatibility of blenvy versions:

blenvy bevy
0.1.0-alpha.1 0.14
branch main 0.14
branch bevy_main main

License

This crate, all its code, contents & assets is Dual-licensed under either of

Dependencies

~41–78MB
~1.5M SLoC