Bevy plugin helping with asset loading and organisation

Overview

Bevy asset loader

crates.io docs license crates.io

This Bevy plugin reduces boilerplate when loading game assets. The crate offers the AssetCollection trait and can automatically load structs that implement it. The trait can be derived.

How to use

The AssetLoader is constructed with two States. During the first state it will load the assets and check up on the loading status in every frame. When the assets are done loading, the collections will be inserted as resources, and the plugin switches to the second state.

You can add as many AssetCollections to the loader as you want. This is done 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 AssetCollections as resources in your systems.

} #[derive(AssetCollection)] struct TextureAssets { #[asset(path = "textures/player.png")] player: Handle, #[asset(path = "textures/tree.png")] tree: Handle, } fn use_my_assets(_texture_assets: Res, _audio_assets: Res) { // do something using the asset handles from the resources } #[derive(Clone, Eq, PartialEq, Debug, Hash)] enum GameState { AssetLoading, Next, } ">
use bevy::prelude::*;
use bevy_asset_loader::{AssetLoader, AssetCollection};

fn main() {
  let mut app = App::build();
  AssetLoader::new(GameState::AssetLoading, GameState::Next)
          .with_collection::()
          .with_collection::()
          .build(&mut app);
  app.add_state(GameState::AssetLoading)
          .add_plugins(DefaultPlugins)
          .add_system_set(SystemSet::on_enter(GameState::Next).with_system(use_my_assets.system()))
          .run();
}

#[derive(AssetCollection)]
struct AudioAssets {
  #[asset(path = "walking.ogg")]
  walking: Handle<AudioSource>
}

#[derive(AssetCollection)]
struct TextureAssets {
  #[asset(path = "textures/player.png")]
  player: Handle<Texture>,
  #[asset(path = "textures/tree.png")]
  tree: Handle<Texture>,
}

fn use_my_assets(_texture_assets: Res, _audio_assets: Res) {
  // do something using the asset handles from the resources
}

#[derive(Clone, Eq, PartialEq, Debug, Hash)]
enum GameState {
  AssetLoading,
  Next,
}

See the example for a complete setup.

Comments
  • Support TextureAtlas handles when deriving AssetCollection

    Support TextureAtlas handles when deriving AssetCollection

    #[derive(AssetCollection)]
    struct MyAssets {
        #[asset(texture_atlas(cell_width = 100., cell_height = 96., columns = 8, rows = 1))]
        #[asset(path = "textures/female_adventurer.png")]
        female_adventurer: Handle<TextureAtlas>,
    }
    
    opened by NiklasEi 10
  • Stageless

    Stageless

    Did a stab at implementing stageless. There is some cursed hairiness with ExclusiveSystem traits in bevy/iyes_loopless, so sorry for hairy code at places.

    Note that this requires your state to be pre-initialized before AssetLoader, I think it's limitation of how iyes_loopless does State.

    opened by freiksenet 9
  • Error when using `progress_tracking` and `progress_tracking_stageless` features

    Error when using `progress_tracking` and `progress_tracking_stageless` features

    When trying to delay the end of loading assets using iyes_progress, an error occurs:

    thread 'main' panicked at 'State Transiton Stage not found (assuming auto-added label)', C:\Users\Anton\.cargo\registry\src\github.com-1ecc6299db9ec823\iyes_loopless-0.5.1\src\state.rs:239:25
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    error: process didn't exit successfully: `target\debug\bevy_template.exe` (exit code: 101)
    

    bevy: 0.7 bevy_asset_loader: branch main

    Cargo.toml:

    [features]
    default = [
        # https://github.com/bevyengine/bevy/blob/main/docs/cargo_features.md
        "bevy/bevy_winit",
        "bevy/render",
        "bevy/png",
    
        "bevy_asset_loader/2d",
        "bevy_asset_loader/dynamic_assets",
        "bevy_asset_loader/stageless",
        "bevy_asset_loader/progress_tracking",
        "bevy_asset_loader/progress_tracking_stageless",
    ]
    
    dev = [
        "bevy/dynamic",
    ]
    
    [dependencies]
    bevy = { version = "0.7", default-features = false }
    iyes_loopless = { git = "https://github.com/NiklasEi/iyes_loopless", branch = "loopless-schedule-ext-trait" }
    bevy-inspector-egui = "0.11.0"
    bevy_asset_loader = { git = "https://github.com/NiklasEi/bevy_asset_loader", branch = "main" }
    iyes_progress = "0.3.0"
    

    lib.rs:

    mod loading;
    
    use bevy::prelude::*;
    use iyes_loopless::prelude::*;
    
    pub struct GamePlugin;
    
    impl Plugin for GamePlugin {
        fn build(&self, app: &mut App) {
            app.add_loopless_state(GameState::Loading)
                .add_plugin(loading::LoadingPlugin);
        }
    }
    
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub enum GameState {
        Loading,
        MainMenu,
        Gameplay,
    }
    

    loading.rs:

    use bevy::prelude::*;
    use bevy_asset_loader::prelude::*;
    use iyes_loopless::prelude::*;
    use iyes_progress::{ProgressCounter, ProgressPlugin};
    
    use crate::GameState;
    
    const TIME_FOR_SPLASH_SCREEN: f64 = 5.0;
    
    pub struct LoadingPlugin;
    
    impl Plugin for LoadingPlugin {
        fn build(&self, app: &mut App) {
            app.add_loading_state(
                LoadingState::new(GameState::Loading)
                    .continue_to_state(GameState::MainMenu)
                    .with_dynamic_collections::<StandardDynamicAssetCollection>(vec!["dynamic.assets"])
                    .with_collection::<ImageAssets>()
                    .with_collection::<AudioAssets>()
                    .with_collection::<FontAssets>(),
            )
            .add_plugin(ProgressPlugin::new(GameState::Loading))
            .add_system(make_loading_delay.run_in_state(GameState::Loading));
        }
    }
    
    #[derive(AssetCollection)]
    struct ImageAssets {}
    
    #[derive(AssetCollection)]
    struct AudioAssets {}
    
    #[derive(AssetCollection)]
    struct FontAssets {}
    
    fn make_loading_delay(time: Res<Time>, progress: Res<ProgressCounter>) {
        let condition = time.seconds_since_startup() > TIME_FOR_SPLASH_SCREEN;
        progress.manually_track(condition.into());
    }
    
    
    opened by arewerage 7
  • Folder as asset in web deployment

    Folder as asset in web deployment

    I am having trouble getting a folder loaded when running on the web. I am using your template for building and deploying my stuff to github pages, but a folder that is loading fine on windows is panicking on the web.

    You should see the problem on this deployment: (https://troels51.github.io/hex_map_bevy/) And it is panicking like this in the log panicked at 'called `Result::unwrap()` on an `Err` value: AssetFolderNotADirectory("hexes")', src\loading\hex.rs:20:10

    opened by Troels51 6
  • Allow adding assets to states from plugins

    Allow adding assets to states from plugins

    Currently the only (official) way to describe the loading transitions is from fn main function. For bigger projects, from what I understand, plugins are often used.

    I wrote very quick'n'dirty proof-of-concept:

    use bevy::{prelude::*, utils::*, ecs::schedule::StateData, asset::Asset};
    use bevy_asset_loader::prelude::*;
    
    enum AssetLoader<T> {
        BeingBuilt {
            hash: HashMap<T, Option<LoadingState<T>>>,
        },
        Completed
    }
    
    impl<T> Default for AssetLoader<T> {
        fn default() -> Self {
            Self::BeingBuilt {
                hash: Default::default()
            }
        }
    }
    
    pub trait AssetLoaderExt {
        fn continue_to_state<T: StateData>(&mut self, in_state: T, next: T) -> &mut Self;
        fn init_resource_in_state<T: StateData, R: FromWorld + Send + Sync + 'static>(&mut self, in_state: T) -> &mut Self;
        fn with_collection<T: StateData, A: AssetCollection>(&mut self, in_state: T) -> &mut Self;
        fn with_dynamic_collection<T: StateData, C: DynamicAssetCollection + Asset>(&mut self, in_state: T, files: Vec<&str>) -> &mut Self;
        fn with_loading_state<T: StateData>(&mut self, in_state: T, run: impl FnOnce(LoadingState<T>) -> LoadingState<T>) -> &mut Self;
        fn complete_loading<T: StateData>(&mut self) -> &mut Self;
    }
    
    impl AssetLoaderExt for App {
    
        fn continue_to_state<T: StateData>(&mut self, in_state: T, next: T) -> &mut Self {
            self.with_loading_state(in_state, |ls| {
                ls.continue_to_state(next)
            })
        }
    
        fn init_resource_in_state<T: StateData, R: FromWorld + Send + Sync + 'static>(&mut self, in_state: T) -> &mut Self {
            self.with_loading_state(in_state, |ls| {
                ls.init_resource::<R>()
            })
        }
    
        fn with_collection<T: StateData, A: AssetCollection>(&mut self, in_state: T) -> &mut Self {
            self.with_loading_state(in_state, |ls| {
                ls.with_collection::<A>()
            })
        }
    
        fn with_dynamic_collection<T: StateData, C: DynamicAssetCollection + Asset>(&mut self, in_state: T, files: Vec<&str>) -> &mut Self {
            self.with_loading_state(in_state, |ls| {
                ls.with_dynamic_collections::<C>(files)
            })
        }
    
        fn with_loading_state<T: StateData>(&mut self, in_state: T, run: impl FnOnce(LoadingState<T>) -> LoadingState<T>) -> &mut Self {
            let mut al = self.world.get_resource_or_insert_with(|| AssetLoader::<T>::default());
            match &mut *al {
                AssetLoader::BeingBuilt { hash } => {
                    let ols = hash.entry(in_state).or_insert(Some(LoadingState::new(in_state)));
                    *ols = Some(run(ols.take().unwrap()));
                },
                AssetLoader::Completed => unreachable!()
            }
            self
        }
    
        fn complete_loading<T: StateData>(&mut self) -> &mut Self {
            if let Some(al) = self.world.remove_resource::<AssetLoader<T>>() {
                match al {
                    AssetLoader::BeingBuilt { mut hash } => {
                        for (_state, ls) in hash.drain() {
                            self.add_loading_state(ls.unwrap());
                        }
                    },
                    AssetLoader::Completed => unreachable!()
                }
            }
            self.world.insert_resource(AssetLoader::<T>::Completed);
            self
        }
    }
    
    

    (It would probably be able to expose simpler interface if LoadingState changed from fn foo(self) -> Self to fn foo(&mut self) -> &mut Self).

    This way the loading can be constructed as:

    // ... 
    impl Plugin for PlayerPlugin {
        fn build(&self, app: &mut App) {
            // ...
            app.with_collection::<_, PlayerImages>(&State::Game);
        }
    }
    
    // ...
    fn main() {
        let mut app = App::new();
        // ...
        app.continue_to_state(State::Init, State::Game);
        // ...
        app.complete_loading::<State>();
        app.run();
    }
    

    (I suspect that complete_loading can be avoided with 'native' loading)

    opened by uzytkownik 5
  • Support for nested folders

    Support for nested folders

    I would love to see some support for nested folder structures, for example:

    - assets
        - scripts 
             - init.lua
             - hello.lua
             - more_scripts
                 - script.lua
    

    perhaps in the form of an argument to the asset attribute, maybe recursive ?

    struct LuaAssets {
        #[asset(path = "scripts", folder(typed), recursive(true))]
        folder: Vec<Handle<LuaFile>>
    }
    

    alternatively the folder could be modelled as a dictionary, with keys representing the relative/absolute paths, in case these have some meaning to the developer.

    In the case that the folder contains mixed assets, these could simply be ignored to accommodate use-cases where different asset types must be stored alongside each other.

    Happy to help if necessary!

    opened by makspll 5
  • Calling `add_loading_state` panics `iyes_loopless`

    Calling `add_loading_state` panics `iyes_loopless`

    backtrace
    thread 'main' panicked at 'State Transition Stage not found (assuming auto-added label)', /Users/landow/.cargo/registry/s
    rc/github.com-1ecc6299db9ec823/iyes_loopless-0.7.1/src/state.rs:328:25
    stack backtrace:
       0: _rust_begin_unwind
       1: core::panicking::panic_fmt
       2: core::panicking::panic_display
       3: core::panicking::panic_str
       4: core::option::expect_failed
       5: core::option::Option::expect
                 at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs:738:21
       6: ::add_enter_system
                 at /Users/landow/.cargo/registry/src/github.com-1ecc6299db9ec823/iyes_loopless-0.7.1/src/state.rs:328:25
       7: bevy_asset_loader::loading_state::LoadingState<S>::build
                 at /Users/landow/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_asset_loader-0.12.1/src/loading_state.
    rs:975:9
       8: ::add_loading_state
                 at /Users/landow/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_asset_loader-0.12.1/src/loading_state.
    rs:1078:9
       9: ::build
                 at ./src/level_builder/mod.rs:20:9
      10: bevy_app::app::App::add_plugin
                 at /Users/landow/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_app-0.8.1/src/app.rs:786:9
      11: bevy_experiment::game
                 at ./src/main.rs:25:5
      12: bevy_experiment::main
                 at ./src/main.rs:20:5
    

    I've commented out pretty much every other plugin/system add other than these (main just adds this plugin then runs):

            app
                .add_loopless_state(GameState::AssetLoading)
                .add_plugins(DefaultPlugins)
                .add_loading_state(
                    LoadingState::new(GameState::AssetLoading)
                        .continue_to_state(GameState::Playing)
                        .with_collection::<LevelBuilderAssets>(),
                )
                .add_enter_system(GameState::Playing, setup_lights)
                .add_enter_system(GameState::Playing, spawn_grid)
                .add_system_set(
                    ConditionSet::new()
                        .run_in_state(GameState::Playing)
                        .with_system(hover_and_click)
                        .into(),
                );
    
    

    I have the stageless feature enabled

    bevy = { version = "0.8.1", features = ["dynamic"] }
    bevy_asset_loader = { version = "0.12.1", features= ["stageless", "serde"] }
    iyes_loopless = { version = "0.8.0"}
    

    I hope I'm missing something obvious.

    opened by stevenctl 4
  • load-bearing `track_fake_long_task` system in `stageless_progress` example

    load-bearing `track_fake_long_task` system in `stageless_progress` example

    In the new iyes_loopless stageless_progress example, the track_fake_long_task system is load-bearing. If removed, the assets fail to be available.

    If you remove the system in the current example, the current progress is logged out as done:0, total: 0.

    2022-06-01T01:44:05.670996Z  INFO stageless_progress: Current progress: Progress { done: 0, total: 0 }
    

    and if you add a requirement for the TextureAssets in quit, you get a crash because Res<TextureAssets> is not available.

    fn quit(
        mut quit: EventWriter<AppExit>,
        // textures: Res<TextureAssets>,
    ) {
        info!("quitting");
        quit.send(AppExit);
    }
    
    2022-06-01T01:40:13.660762Z  WARN bevy_ecs::schedule::graph_utils: bevy_asset_loader::asset_loader::stageless::systems::run_loading_state<stageless_progress::MyStates> wants to be after unknown label: Preparation
    2022-06-01T01:40:13.662065Z  INFO stageless_progress: Current progress: Progress { done: 0, total: 0 }
    thread 'Compute Task Pool (3)' panicked at 'Resource requested by stageless_progress::quit does not exist: stageless_progress::TextureAssets', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_ecs-0.7.0/src/system/system_param.rs:319:17
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    thread 'main' panicked at 'task has failed', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/async-task-4.2.0/src/task.rs:425:45
    thread 'main' panicked at 'Task thread panicked while executing.: Any { .. }', /Users/chris/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_tasks-0.7.0/src/task_pool.rs:77:21
    

    There's a possibly unrelated warning as well relating to the Preparation label.

    2022-06-01T01:40:13.660762Z  WARN bevy_ecs::schedule::graph_utils: bevy_asset_loader::asset_loader::stageless::systems::run_loading_state<stageless_progress::MyStates> wants to be after unknown label: Preparation
    

    I think this is because of a race condition relating to time.seconds_since_startup(). With the delay, it's fine, without the delay it fails.

    opened by ChristopherBiscardi 4
  • WIP: attempt upgrade to unreleased 0.9.0

    WIP: attempt upgrade to unreleased 0.9.0

    • [ ] is no longer broken

    I attempted to update bevy ahead of the upcoming release but was left stuck with a strange error about not being able to derive Resource on AssetBuilder.

    opened by HugoPeters1024 3
  • Partial upgrade to Bevy 0.9-dev.

    Partial upgrade to Bevy 0.9-dev.

    Dropping this here in case it's useful. Please feel free to close or do as you like with any of the code.

    Draft because this is incomplete. Missing features that use iyes_* crates.

    I'm also not totally confident in the strategy I used with the AssetCollection and Resource traits.

    opened by rparrett 3
  • Need to use handles from collection during loading state

    Need to use handles from collection during loading state

    I'd like to set up a loading screen with a progress indicator that shows the percentage ready.

    However, to spawn the UI with the Text for the percentage indicator, I need a Handle to my Font.

    bevy_asset_loader only inserts the AssetCollection when those assets have actually been loaded, meaning it is not available for me to access when entering the loading state, to set up my UI.

    Is it possible to make an AssetCollection available from the start? The only way I see is from the example without using a loading state. However, I want to use a loading state. I just want to be able to use the handles when entering it.

    It's okay if the handles point to assets that aren't loaded yet. I just need to setup my loading/splash screen. The UI will just "pop in" when those specific assets become available.

    opened by inodentry 3
  • Array syntax in RON dynamic collections?

    Array syntax in RON dynamic collections?

    Would it be possible to do something like this?

    #[derive(AssetCollection)]
    struct SpriteAnimationSequences {
        // maybe this needs a new attribute?
        // `collection(array)` or something,
        // to distinguish from `Files`
        #[asset(key = "anim.jumps", collection(typed)]
        jumps: Vec<Handle<TextureAtlas>>,
    }
    
    ({
        // just use RON "array syntax"
        // to list as many items as we need
        "anim.jumps": [
            TextureAtlas (
                path: "jump_long.png",
                tile_size_x: 24.,
                tile_size_y: 32.,
                columns: 16,
                rows: 1,
            ),
            TextureAtlas (
                path: "jump_med.png",
                tile_size_x: 24.,
                tile_size_y: 32.,
                columns: 12,
                rows: 1,
            ),
            TextureAtlas (
                path: "jump_short.png",
                tile_size_x: 24.,
                tile_size_y: 32.,
                columns: 8,
                rows: 1,
            ),
        ],
    })
    
    opened by inodentry 1
  • Add dependencies of assets to the list of watched assets for transition out of loading state

    Add dependencies of assets to the list of watched assets for transition out of loading state

    Here's an example: I use a custom loader for ldtk map files which loads and parses the ldtk file, and provides it as an asset to Bevy. I also load all of the images used for sprites/backgrounds/tiles/etc when I do this, and use the LoadedAsset::with_dependencies(...) member to add them as dependencies to my ldtk asset.

    When I register this asset using an AssetCollection, as described in your readme, it seems to be correctly waiting on the ldtk file itself to load. However, it does not wait on all of the referenced images.

    I will look at creating a dynamic asset description when constructing the asset, or simply adding the images separately. It would be a nice feature if your crate could detect this, however, due to the fact that the ldtk user could change what images they utilize at any time.

    I don't know of other examples of assets with dependencies, but I imagine they could take advantage of this feature as well.

    opened by stinkytoe 3
Owner
Niklas Eicker
Niklas Eicker
An asset that provides 2d collision detector and kinematics, build from scratch in bevy

Busturi A physics engine An asset that provides 2d collision detector and kinematics, build from scratch in bevy How to use Add PhysicsPlugin to the p

NemuiSen 2 Jun 22, 2022
A framework for saving and loading game state in Bevy.

Bevy_save A framework for saving and loading game state in Bevy. Features bevy_save is primarily built around extension traits to Bevy's World. Serial

Hank Jordan 10 Jan 24, 2023
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Sélène Amanita 11 May 28, 2023
A Rust library for reading asset files and resource packs for any version of Minecraft

minecraft-assets A Rust library for reading asset files and resource packs for any version of Minecraft. Example use minecraft_assets::api::AssetPack;

Ben Reeves 7 Aug 14, 2022
Simple rust asset handling derive macro for enums, and a proc-macro learning resource!

asset-derive Summary • Todos • Docs Summary Simple Rust asset loading derive macro for Enums, and a resource for learning proc-macros! Please feel fre

Shadorain 5 Feb 9, 2023
Bevy plugin to simulate and preview different types of Color Blindness.

Bevy Color Blindness Simulation Bevy plugin to simulate and preview different types of Color Blindness. This lets you ensure that your game is accessi

annie 29 Nov 22, 2022
Bevy plugin that does navmesh generation, pathfinding, and navigation for tilemaps

Bevy plugin that does navmesh generation, pathfinding, and navigation for tilemaps. Navmesh generation is available without Bevy dependency.

Seldom 13 Jan 31, 2023
A Bevy plugin to easily create and manage windows that remember where they were.

bevy-persistent-windows A Bevy plugin to easily create and manage windows that remember where they were. Background When you're developing a game, thu

Umut 4 Aug 12, 2023
Inspector plugin for the bevy game engine

bevy-inspector-egui This crate provides the ability to annotate structs with a #[derive(Inspectable)], which opens a debug interface using egui where

Jakob Hellermann 517 Dec 31, 2022
A plugin for Egui integration into Bevy

bevy_egui This crate provides a Egui integration for the Bevy game engine. Features: Desktop and web (bevy_webgl2) platforms support Clipboard (web su

Vladyslav Batyrenko 453 Jan 3, 2023
Crossterm plugin for the bevy game engine

What is bevy_crossterm? bevy_crossterm is a Bevy plugin that uses crossterm as a renderer. It provides custom components and events which allow users

null 79 Nov 2, 2022
A Bevy plugin to use Kira for game audio

Bevy Kira audio This bevy plugin is intended to try integrating Kira into Bevy. The end goal would be to replace or update bevy_audio, if Kira turns o

Niklas Eicker 172 Jan 5, 2023
A prototype plugin providing a simple line drawing api for bevy.

bevy_debug_lines A prototype plugin providing a simple line drawing api for bevy. See docs.rs for documentation. Expect breakage on master. Click on t

Michael Palmos 92 Dec 31, 2022
A sprite-sheet animation plugin for bevy

Benimator A sprite sheet animation plugin for bevy Features A SpriteSheetAnimation component to automatically update the indices of the TextureAtlasSp

Jonathan Cornaz 140 Dec 27, 2022
A Bevy Engine plugin for making 2D paths, smooth animations with Bezier curves

bevy_pen_tool A Bevy Engine plugin for making 2D paths and smooth animations with Bezier curves TODO: Mesh-making functionality for building 2D shapes

Eli 36 Dec 22, 2022
A procedural sky plugin for bevy

bevy_atmosphere A procedural sky plugin for bevy Example use bevy::prelude::*; use bevy_atmosphere::*; fn main() { App::build() .insert_re

Jonah Henriksson 137 Dec 23, 2022
Generic cellular automaton plugin for bevy.

Bevy Cellular Automaton bevy_life is a generic plugin for cellular automaton. From the classic 2D Conway's game of life to WireWorld and 3D rules, the

Félix Lescaudey de Maneville 34 Nov 23, 2022
Verlet physics plugin for bevy.

bevy_verlet Simple Verlet points and sticks implementation for bevy. Features You can simply add a VerletPoint component on any entity with a Transfor

Félix Lescaudey de Maneville 34 Dec 9, 2022
Bevy plugin for an AssetServer that can load embedded resources, or use other AssetServers based on the path.

Bevy-Embasset Embed your asset folder inside your binary. bevy-embasset adds support for loading assets embedded into the binary. Furthermore, it can

Johnny Tidemand Vestergaard 9 Aug 4, 2022