This Bevy plugin reduces boilerplate for handling game assets. The crate offers the derivable AssetCollection
trait and can automatically load structs that implement it. These structs contain handles to your game assets. Each asset collection is available to your systems as a Resource
after loading.
The plugin supports different paths for asset collections to be loaded. The most common one is a loading state (think loading screen). During this state, all assets are loaded. Only when all asset collections can be build with fully loaded asset handles, the collections are inserted as resources. If you do not want to use a loading state, asset collections can still result in cleaner code and improved maintainability for projects with a lot of assets (see "Usage without a loading state").
Asset configurations, like their file path or tile dimensions for sprite sheets, can be resolved at compile time (through derive macro attributes), or at run time (see "Dynamic assets"). The second allows managing asset configurations as assets. This means you can keep a list of your asset files and their properties in asset files (at the moment only ron
files are supported). The main benefit of dynamic asset collections is a clean split of code and asset configuration leading to less recompiles while working on your assets.
Asset loader also supports iyes_loopless
states via stageless
feature.
The main
branch and the latest release support Bevy version 0.7
(see version table)
An AssetLoader
is responsible for managing the loading process during a configurable loading state (see the cheatbook on states). A second state can be configured to move on to, when all assets are loaded and the collections were inserted as resources.
For structs with named fields that are either asset handles, implement FromWorld
, or are of another supported type, AssetCollection
can be derived. You can add as many asset collections to the loader as you want by chaining with_collection
calls. To finish the setup, call the build
function with your AppBuilder
.
Now you can start your game logic from the second configured state and use the asset collections as resources in your systems. The AssetLoader
guarantees that all handles in your collections are fully loaded at the time the second state starts.
use bevy::prelude::*;
use bevy_asset_loader::{AssetLoader, AssetCollection};
fn main() {
let mut app = App::new();
AssetLoader::new(GameState::AssetLoading)
.continue_to_state(GameState::Next)
.with_collection::<ImageAssets>()
.with_collection::<AudioAssets>()
.build(&mut app);
app.add_state(GameState::AssetLoading)
.add_plugins(DefaultPlugins)
.add_system_set(SystemSet::on_enter(GameState::Next).with_system(use_my_assets))
.run();
}
#[derive(AssetCollection)]
struct AudioAssets {
#[asset(path = "walking.ogg")]
walking: Handle<AudioSource>
}
#[derive(AssetCollection)]
struct ImageAssets {
#[asset(path = "images/player.png")]
player: Handle<Image>,
#[asset(path = "images/tree.png")]
tree: Handle<Image>,
}
fn use_my_assets(_image_assets: Res<ImageAssets>, _audio_assets: Res<AudioAssets>) {
// do something using the asset handles from the resources
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
AssetLoading,
Next,
}
See full_collection.rs for a complete example.
It is possible to decide asset configurations at run-time. This is done via the resource DynamicAssets
which is a map of asset keys to their configurations. The AssetLoader
initializes the resource and reads it during the loading state.
To create a dynamic asset collection, give your asset fields keys
.
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct ImageAssets {
#[asset(key = "player")]
player: Handle<Image>,
#[asset(key = "tree")]
tree: Handle<Image>,
}
The key player
in the example above should be either set manually in the DynamicAssets
resource before the loading state (see the dynamic_asset example), or should be part of a .assets
file in ron format (this requires the feature dynamic_assets
):
({
"player": File (
path: "images/player.png",
),
"tree": File (
path: "images/tree.png",
),
})
Loading dynamic assets from such a ron file requires little setup. Take a look at the dynamic_asset_ron example to see what it can look like in your game.
The file ending is .assets
by default, but can be configured via AssetLoader::set_dynamic_asset_collection_file_endings
.
The following sections describe more types of asset fields that you can add to your collections. All of them can be used as dynamic assets. The example full_dynamic_collection displays all supported field types.
You can load all assets in a folder and keep them in an AssetCollection
as a vector of untyped handles.
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(path = "images", collection)]
folder: Vec<HandleUntyped>,
}
Just like Bevy's load_folder
, this will also recursively load sub folders.
If all assets in the folder have the same type you can load the folder as Vec<Handle<T>>
. Just set typed
in the folder
attribute and adapt the type of the asset collection field.
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(path = "images", collection(typed))]
folder: Vec<Handle<Image>>,
}
Folders are also supported as a dynamic asset:
#[derive(AssetCollection)]
struct MyAssets {
#[asset(key = "my.images", collection(typed))]
images: Vec<Handle<Image>>,
}
({
"my.images": Folder (
path: "images",
),
})
Loading folders is not supported for web builds. If you need Wasm support, load you handles from a list of paths (see next section).
If you want load a list of asset files with the same type into a vector of Handle<T>
, you can list their paths in an attribute:
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(paths("images/player.png", "images/tree.png"), collection(typed))]
files_typed: Vec<Handle<Image>>,
}
In case you do not know their types, or they might have different types, the handles can also be untyped:
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(paths("images/player.png", "sound/background.ogg"), collection)]
files_untyped: Vec<HandleUntyped>,
}
As dynamic assets, these two asset collection fields will look like this:
#[derive(AssetCollection)]
struct MyAssets {
#[asset(key = "files_untyped", collection)]
files_untyped: Vec<HandleUntyped>,
#[asset(key = "files_typed", collection(typed))]
files_typed: Vec<Handle<Image>>,
}
({
"files_untyped": Files (
paths: ["images/tree.png", "images/player.png"],
),
"files_typed": Files (
paths: ["images/tree.png", "images/player.png"],
),
})
You can directly load standard materials if you enable the feature render
. For a complete example please take a look at standard_material.rs.
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(standard_material)]
#[asset(path = "images/player.png")]
player: Handle<StandardMaterial>,
}
This is also supported as a dynamic asset:
#[derive(AssetCollection)]
struct MyAssets {
#[asset(key = "image.player")]
player: Handle<StandardMaterial>,
}
({
"image.player": StandardMaterial (
path: "images/player.png",
),
})
You can directly load texture atlases from sprite sheets if you enable the feature render
. For a complete example please take a look at atlas_from_grid.rs.
use bevy::prelude::*;
use bevy_asset_loader::AssetCollection;
#[derive(AssetCollection)]
struct MyAssets {
#[asset(texture_atlas(tile_size_x = 100., tile_size_y = 64., columns = 8, rows = 1, padding_x = 12., padding_y = 12.))]
#[asset(path = "images/sprite_sheet.png")]
sprite: Handle<TextureAtlas>,
}
This is also supported as a dynamic asset:
#[derive(AssetCollection)]
struct MyAssets {
#[asset(key = "image.player")]
sprite: Handle<TextureAtlas>,
}
({
"image.player": TextureAtlas (
path: "images/sprite_sheet.png",
tile_size_x: 100.,
tile_size_y: 64.,
columns: 8,
rows: 1,
padding_x: 12.,
padding_y: 12.,
),
})
The two padding fields/attributes are optional and default to 0.
.
In situations where you would like to prepare other resources based on your loaded assets you can use AssetLoader::init_resource
to initialize FromWorld
resources. See init_resource.rs for an example that loads two images and then combines their pixel data into a third image.
AssetLoader::init_resource
does the same as Bevy's App::init_resource
, but at a different point in time. While Bevy inserts your resources at the very beginning, the AssetLoader will do so after having inserted your loaded asset collections. That means that you can use your asset collections in the FromWorld
implementations.
With the feature progress_tracking
, you can integrate with iyes_progress
to track asset loading during a loading state. This, for example, enables progress bars.
See progress_tracking
for a complete example.
When using stageless
feature, you need to add progress_tracking_stageless
feature in addition to progress_tracking
.
The loading state runs in a single exclusive system at_start
. This means that any parallel system in the loading state will always run after all asset handles have been checked for their status. You can thus read the current progress in each frame in a parallel system without worrying about frame lag.
Although the pattern of a loading state is quite nice, you might have reasons not to use it. In this case bevy_asset_loader
can still be helpful. Deriving AssetCollection
on a resource can significantly reduce the boilerplate for managing assets.
You can directly initialise asset collections on the bevy App
or World
. See no_loading_state.rs for a complete example.
use bevy::prelude::*;
use bevy_asset_loader::{AssetCollection, AssetCollectionApp};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_collection::<MyAssets>()
.run();
}
#[derive(AssetCollection)]
struct MyAssets {
#[asset(texture_atlas(tile_size_x = 100., tile_size_y = 96., columns = 8, rows = 1, padding_x = 12., padding_y = 12.))]
#[asset(path = "images/sprite_sheet.png")]
sprite: Handle<TextureAtlas>,
}
Asset loader can integrate with iyes_loopless
, which implements ideas from Bevy's Stageless RFC. The integration can be enabled with the stageless
feature.
Currently, you must initialize the loopless state before you initialize your AssetLoader
. This is a limitation due to the way iyes_loopless
works. The following is a minimal example of integrating bevy_asset_loader
with iyes_loopless
:
use bevy::prelude::*;
use bevy_asset_loader::{AssetCollection, AssetLoader};
use iyes_loopless::prelude::*;
fn main() {
let mut app = App::new();
app.add_loopless_state(MyStates::AssetLoading);
AssetLoader::new(MyStates::AssetLoading)
.continue_to_state(MyStates::Next)
.with_collection::<AudioAssets>()
.build(&mut app);
app
.add_plugins(DefaultPlugins)
.add_enter_system(MyStates::Next, use_my_assets)
.run();
}
#[derive(AssetCollection)]
struct AudioAssets {
#[asset(path = "audio/background.ogg")]
background: Handle<AudioSource>,
}
fn use_my_assets(_audio_assets: Res<AudioAssets>) {
// do something using the asset handles from the resources
}
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
enum MyStates {
AssetLoading,
Next,
}
When using with progress_tracking
, remember to enable progress_tracking_stageless
feature too.
See the stageless examples for more code.
The main branch is compatible with the latest Bevy release, while the branch bevy_main
tracks the main
branch of Bevy.
Compatibility of bevy_asset_loader
versions:
bevy_asset_loader |
bevy |
---|---|
0.10 - 0.11 |
0.7 |
0.8 - 0.9 |
0.6 |
0.1 - 0.7 |
0.5 |
main |
0.7 |
bevy_main |
main |
Licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Assets in the examples might be distributed under different terms. See the readme in the bevy_asset_loader/examples
directory.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.