bevy_asset_loader

repository·main·Indexed 20 days ago

https://github.com/niklasei/bevy_asset_loader

A Bevy plugin designed to reduce boilerplate when managing game assets. It provides a derivable AssetCollection trait to automatically load groups of assets as resources during specific loading states. Features include support for compile-time and dynamic assets (via .ron files), texture atlas layout generation, image sampler configuration, and the ability to load folders or lists of assets into collections and maps.

Tokens
13.5K
Snippets
45
Records
59
Agent score
71%

What's inside bevy_asset_loader

  1. Explore bevy_asset_loader example capabilities

    main

    The repository includes several examples covering different use cases for asset loading in Bevy:

    Asset Types & Configurations

    • Texture Atlases: atlas_from_grid.rs (Loading a texture atlas from a sprite sheet)
    • Images: image_asset.rs (Setting different samplers for image assets)
    • Materials: standard_material.rs (Loading a standard material from a png file)
    • Dynamic Assets:
      • dynamic_asset.rs (Loading dynamic assets from a .ron file)
      • custom_dynamic_assets.rs (Defining and using your own dynamic assets)
      • manual_dynamic_asset.rs (Loading an image asset from a path resolved at run time)
      • dynamic_asset_arrays.rs (Defining dynamic assets in arrays)

    Collection & State Management

    • Collections:
      • full_collection.rs (Complete collection with all supported non-dynamic field types)
      • full_dynamic_collection.rs (Complete collection with all supported dynamic asset field types)
      • two_collections.rs (Loading multiple asset collections)
      • asset_maps.rs (Using different types as keys in asset maps)
    • States & Loading:
      • failure_state.rs (Setting up a failure state)
      • no_loading_state.rs (Using asset collections without a loading state)
      • sub_state.rs (Using a sub state)
      • finally_init_resource.rs (Inserting a FromWorld resource when all asset collections are loaded)
      • progress_tracking.rs (Setting up progress tracking using iyes_progress)
  2. Unload assets held in an AssetCollection

    main

    Bevy unloads an asset when there are no strong asset handles left pointing to it. Because an AssetCollection stores strong handles, it prevents the assets it contains from being removed from memory.

    To unload assets held by a collection, you must remove the AssetCollection resource itself from the Bevy World. A common pattern is to remove the resource when leaving the specific state that required that collection.

  3. How loading states work in bevy_asset_loader

    main

    A LoadingState manages the asset loading process during a specific Bevy State. It observes the loading progress of all assets in the provided collections. Once all assets in the collections are fully loaded, the collections are inserted into Bevy's ECS as Resources, and the application transitions to the configured 'next state'. This ensures that when your game logic starts in the next state, all asset handles are ready for use.

    To use a loading state:

    1. Define a State enum.
    2. Create a struct that implements AssetCollection and Resource.
    3. Use .add_loading_state() on your App to configure the loading state, the target state to transition to, and the collections to load.
    use bevy::prelude::*;
    use bevy_asset_loader::prelude::*;
    
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .init_state::<MyStates>()
            .add_loading_state(
                LoadingState::new(MyStates::AssetLoading)
                    .continue_to_state(MyStates::Next)
                    .load_collection::<AudioAssets>(),
            )
            .add_systems(OnEnter(MyStates::Next), start_background_audio)
            .run();
    }
    
    #[derive(AssetCollection, Resource)]
    struct AudioAssets {
        #[asset(path = "audio/background.ogg")]
        background: Handle<AudioSource>,
    }
    
    #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
    enum MyStates {
        #[default]
        AssetLoading,
        Next,
    }
    
    fn start_background_audio(mut commands: Commands, audio_assets: Res<AudioAssets>) {
        // AudioAssets is available as a resource and handles are fully loaded
        commands.spawn((AudioPlayer(audio_assets.background.clone()), PlaybackSettings::LOOP));
    }
  4. Initialize resources using FromWorld after loading

    main

    If you need to create a resource that depends on your loaded assets (e.g., combining two images into a third), implement FromWorld for that resource.

    To ensure the resource is initialized only after the assets are ready, use LoadingState::finally_init_resource or LoadingStateConfig::finally_init_resource. This behaves like Bevy's App::init_resource but executes at the correct point in the loading lifecycle, allowing you to access the loaded AssetCollection within the FromWorld implementation.

  5. Compile time vs. Dynamic assets

    main

    The AssetCollection derive macro supports two ways to configure assets:

    Compile time assets

    Configuration (like file paths) is provided directly in the code using #[asset(path = "...")] attributes. Changing these requires recompiling the application.

    Dynamic assets

    Configuration is decoupled from the code. Instead of path, you use the key attribute. Assets are looked up at runtime using these keys from a DynamicAssets resource or a dynamic assets file (e.g., a .assets.ron file).

    Benefits of Dynamic Assets:

    • Cleaner split of code and data.
    • No recompilation needed when changing asset paths or properties.
    • Easier for non-coders to contribute assets.
  6. Use dynamic assets files (.ron)

    main

    You can define your dynamic asset mappings in a .ron file. By default, the expected file extension is .assets.ron.

    To use File types and .ron files, ensure the standard_dynamic_assets feature is enabled.

    Example .assets.ron content:

    ({
        "player": File (
            path: "images/player.png",
        ),
        "tree": File (
            path: "images/tree.png",
        ),
    })

    You can customize the expected file extension using LoadingState::set_standard_dynamic_asset_collection_file_endings.

  7. Use AssetCollection without a loading state

    main

    If you do not want to use Bevy states for loading, you can still use bevy_asset_loader to reduce boilerplate by deriving AssetCollection on a resource. This allows you to initialize asset collections directly on the Bevy App or World using .init_collection::<T>().

    Limitations of no-loading-state collections:

    • They do not support folders.
    • They do not support dynamic assets.
    • They do not support the image annotation.

    These features require a waiting mechanism (loading states) to ensure assets are fully loaded before they are accessed.

    use bevy::prelude::*;
    use bevy_asset_loader::prelude::*;
    
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .init_collection::<MyAssets>()
            .run();
    }
    
    #[derive(AssetCollection, Resource)]
    struct MyAssets {
        #[asset(texture_atlas_layout(tile_size_x = 64, tile_size_y = 64, columns = 8, rows = 1, padding_x = 12, padding_y = 12, offset_x = 6, offset_y = 6))]
        layout: Handle<TextureAtlasLayout>,
        #[asset(path = "images/sprite_sheet.png")]
        sprite: Handle<Image>,
    }
  8. Use AssetFileStem as a MapKey

    main

    The AssetFileStem type creates a key using the file_stem (the filename without the extension). This is useful when you want to ignore file extensions, but it is prone to collisions if different file types share the same name.

    Example of a collision with AssetFileStem:

    folder
        file.png
        file.jpg

    Both assets will resolve to the same key: file.

  9. How bevy_asset_loader works

    main

    The bevy_asset_loader crate simplifies asset management in Bevy by automating the loading process during a specific State.

    The Workflow:

    1. Define Collections: You create structs representing groups of assets and derive the AssetCollection trait for them. Each field in the struct represents an asset, annotated with #[asset(path = "...")] to specify its location.
    2. Configure Loading State: You use LoadingState to define which state the game starts in (e.g., GameState::Loading), which state to transition to once assets are ready (e.g., GameState::Next), and which AssetCollection structs to load.
    3. Automatic Resource Insertion: Once all assets in the specified collections are loaded, the plugin automatically inserts the collections as Bevy Resources into the ECS.
    4. State Transition: The plugin automatically switches the app's state to the configured 'next' state.
    5. Usage: In your game systems, you can now access the loaded handles directly via Res<YourAssetCollectionStruct>.
    use bevy_asset_loader::prelude easily;
    use bevy::prelude easily;
    
    #[derive(AssetCollection, Resource)]
    struct MyAssets {
        #[asset(path = "textures/player.png")]
        player: Handle<Image>,
    }
    
    #[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default)]
    enum GameState {
        #[default]
        Loading,
        Playing,
    }
    
    fn main() {
        App::new()
            .init_state::<GameState>()
            .add_loading_state(
                LoadingState::new(GameState::Loading)
                    .continue_to_state(GameState::Playing)
                    .load_collection::<MyAssets>()
            )
            .run();
    }