chriba / bevy_asset_loader

Bevy plugin helping with asset loading and organization

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bevy asset loader

crates.io docs license crates.io

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)

How to use

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.

Dynamic assets

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.

Loading a folder as asset

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).

Loading a list of paths

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"],
    ),
})

Loading standard materials

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",
    ),
})

Loading texture atlases

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..

Initialize FromWorld resources

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.

Progress tracking

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.

A note on system ordering

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.

Usage without a loading state

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>,
}

Stageless

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.

Compatible Bevy versions

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

License

Licensed under either of

at your option.

Assets in the examples might be distributed under different terms. See the readme in the bevy_asset_loader/examples directory.

Contribution

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.

About

Bevy plugin helping with asset loading and organization

License:Other


Languages

Language:Rust 100.0%