A Bevy plugin for loading GLTF models at runtime using JSON configuration files. This crate allows you to define collections of 3D model parts in JSON and load them dynamically in your Bevy applications.
- Runtime GLTF Loading: Load GLTF models from JSON configuration files
- Configurable Parts System: Define reusable model parts with custom data
- Material Extensions: Support for custom material extensions and shaders
- State-Based Loading: Integrate with Bevy's state system for controlled loading
- Flexible Configuration: Support for custom data extensions in your JSON configs
Add this to your Cargo.toml:
[dependencies]
bevy_runtime_gltf_loader = "0.1.0"
bevy = "0.16.0"- Create a JSON configuration file (
config.json):
{
"DebugPart": {
"path": "models/my_model.gltf"
}
}- Set up your Bevy app:
use bevy::prelude::*;
use bevy_runtime_gltf_loader::{SimpleRuntimeGltfLoaderPlugin, SimplePartsMap};
#[derive(States, Default, Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum GameState {
#[default]
Loading,
Playing,
Done,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<GameState>()
.add_plugins(
SimpleRuntimeGltfLoaderPlugin::default().load_single(
"./config.json",
GameState::Loading,
GameState::Playing,
)
)
.add_systems(OnEnter(GameState::Playing), setup_scene)
.run();
}
fn setup_scene(
mut commands: Commands,
asset_server: Res<AssetServer>,
parts: Res<SimplePartsMap>,
) {
// Load and spawn a part from your JSON config
parts["DebugPart"]
.load()
.build(&mut commands, &asset_server);
// Add lighting and camera
commands.spawn((
PointLight::default(),
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}You can extend your JSON configuration with custom data:
use bevy::prelude::*;
use bevy_runtime_gltf_loader::{RuntimeGltfLoaderPlugin, PartsMap};
use serde::Deserialize;
#[derive(Asset, Reflect, Deserialize)]
struct CustomData {
hello: String,
scale: f32,
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_state::<GameState>()
.add_plugins(
RuntimeGltfLoaderPlugin::<CustomData>::default().load_single(
"./extended_config.json",
GameState::Loading,
GameState::Playing,
)
)
.add_systems(OnEnter(GameState::Playing), setup_scene_with_data)
.run();
}
fn setup_scene_with_data(
mut commands: Commands,
asset_server: Res<AssetServer>,
parts: Res<PartsMap<CustomData>>,
) {
let part = &parts["DebugPart"];
if let Some(data) = &part.data {
println!("Hello {}", data.hello);
println!("Scale: {}", data.scale);
}
part.load().build(&mut commands, &asset_server);
}With corresponding JSON (extended_config.json):
{
"DebugPart": {
"path": "models/my_model.gltf",
"data": {
"hello": "World",
"scale": 2.0
}
}
}Apply custom materials to your loaded models:
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
struct MyMaterialExtension {
#[uniform(100)]
quantize_steps: u32,
}
impl MaterialExtension for MyMaterialExtension {
fn fragment_shader() -> ShaderRef {
"shaders/my_shader.wgsl".into()
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(MaterialPlugin::<
ExtendedMaterial<StandardMaterial, MyMaterialExtension>
>::default())
.init_state::<GameState>()
.add_plugins(
SimpleRuntimeGltfLoaderPlugin::default()
.load_single("./config.json", GameState::Loading, GameState::Playing)
.register_material_extension::<MyMaterialExtension>()
)
.add_systems(OnEnter(GameState::Playing), setup_scene_with_materials)
.run();
}
fn setup_scene_with_materials(
mut commands: Commands,
asset_server: Res<AssetServer>,
parts: Res<SimplePartsMap>,
) {
parts["DebugPart"]
.load()
.extend_material(MyMaterialExtension { quantize_steps: 6 })
.build(&mut commands, &asset_server);
}SimpleRuntimeGltfLoaderPlugin: Basic plugin for simple usage without custom dataRuntimeGltfLoaderPlugin<T>: Generic plugin that supports custom data typesSimplePartsMap: Resource containing loaded parts (for simple usage)PartsMap<T>: Generic resource containing loaded parts with custom dataPartPath<T>: Represents a single loadable part with optional custom dataPartLoader<T>: Builder for configuring how parts are loaded
SimpleRuntimeGltfLoaderPlugin::default ()
.load_single(file_path, loading_state, target_state)
.register_material_extension::<MaterialType>()// Basic loading
parts["PartName"].load().build( & mut commands, & asset_server);
// Loading on specific entity
parts["PartName"].load()
.on(entity)
.build( & mut commands, & asset_server);
// Loading with material extension
parts["PartName"].load()
.extend_material(my_material)
.build( & mut commands, & asset_server);
// Loading specific asset label from GLTF
parts["PartName"].load()
.asset_label(GltfAssetLabel::Scene(1))
.build( & mut commands, & asset_server);{
"PartName": {
"path": "path/to/model.gltf",
"data": {
// Optional custom data (must match your Rust type)
}
}
}The crate includes several examples:
load_part.rs: Basic part loadingconfig_extension.rs: Using custom data extensionsreplace_mesh.rs: Applying material extensions
Run examples with:
cargo run --example load_part
cargo run --example config_extension
cargo run --example replace_mesh- Bevy 0.16.0
- Rust Edition 2024
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.