Expand description
In the context of game development, an “asset” is a piece of content that is loaded from disk and displayed in the game. Typically, these are authored by artists and designers (in contrast to code), are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts.
This presents two main challenges:
- Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive.
- Loading assets from disk is slow, and can cause long load times and delays.
These problems play into each other, for if assets are expensive to store in memory, then larger game worlds will need to load them from disk as needed, ideally without a loading screen.
As is common in Rust, non-blocking asset loading is done using async
, with background tasks used to load assets while the game is running.
Bevy coordinates these tasks using the AssetServer
resource, storing each loaded asset in a strongly-typed Assets<T>
collection (also a resource).
Handle
s serve as an id-based reference to entries in the Assets
collection, allowing them to be cheaply shared between systems,
and providing a way to initialize objects (generally entities) before the required assets are loaded.
In short: Handle
s are not the assets themselves, they just tell how to look them up!
§Loading assets
The AssetServer
is the main entry point for loading assets.
Typically, you’ll use the AssetServer::load
method to load an asset from disk, which returns a Handle
.
Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped,
calling AssetServer::load
on the same path will return the same handle.
The handle that’s returned can be used to instantiate various Component
s that require asset data to function,
which will then be spawned into the world as part of an entity.
To avoid assets “popping” into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene.
This can be done by checking the LoadState
of the asset handle using AssetServer::is_loaded_with_dependencies
,
which will be true
when the asset is ready to use.
Keep track of what you’re waiting on by using a HashSet
of asset handles or similar data structure,
which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded.
Bevy’s built-in states system can be very helpful for this!
§Modifying entities that use assets
If we later want to change the asset data a given component uses (such as changing an entity’s material), we have three options:
- Change the handle stored on the responsible component to the handle of a different asset
- Despawn the entity and spawn a new one with the new asset data.
- Use the
Assets
collection to directly modify the current handle’s asset data
The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset. Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset, you’ll need to find that component via a query and change the handle to the new mesh asset. This is so commonly done that you should think about strategies for how to store and swap handles in your game.
The second option is the simplest, but can be slow if done frequently, and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost. Generally, this isn’t a great strategy.
The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for all entities using this handle. While this might be what you want, it generally isn’t!
§Hot reloading assets
Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting.
When enabled, any changes to the underlying asset file will be detected by the AssetServer
, which will then reload the asset,
mutating the asset data in the Assets
collection and thus updating all entities that use the asset.
While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly.
To enable asset hot reloading on desktop platforms, enable bevy
’s file_watcher
cargo feature.
To toggle it at runtime, you can use the watch_for_changes_override
field in the AssetPlugin
to enable or disable hot reloading.
§Procedural asset creation
Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels.
After creating an item of a type that implements Asset
, you can add it to the Assets
collection using Assets::add
.
Once in the asset collection, this data can be operated on like any other asset.
Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets:
calling Assets::add
for every entity that needs the asset will create a new copy of the asset for each entity,
quickly consuming memory.
§Handles and reference counting
Handle
(or their untyped counterpart UntypedHandle
) are used to reference assets in the Assets
collection,
and are the primary way to interact with assets in Bevy.
As a user, you’ll be working with handles a lot!
The most important thing to know about handles is that they are reference counted: when you clone a handle, you’re incrementing a reference count.
When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented.
When the reference count hits zero, the asset it references is removed from the Assets
collection.
This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use. However, it can lead to surprising behavior if you’re not careful!
There are two categories of problems to watch out for:
- never dropping a handle, causing the asset to never be removed from memory
- dropping a handle too early, causing the asset to be removed from memory while it’s still in use
The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once, and loading them all at the start of the game. As your game grows, you’ll need to be more careful about when you load and unload assets, segmenting them by level or area, and loading them on-demand. This problem generally arises when handles are stored in a persistent “collection” or “manifest” of possible objects (generally in a resource), which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage.
The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game. Debugging reveals that the entities are still there, but nothing is rendering! This is because the assets were removed from memory while they were still in use. You were probably too aggressive with the use of weak handles (which don’t increment the reference count of the asset): think through the lifecycle of your assets carefully! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player.
§Asset dependencies
Some assets depend on other assets to be loaded before they can be loaded themselves. For example, a 3D model might require both textures and meshes to be loaded, or a 2D level might require a tileset to be loaded.
The assets that are required to load another asset are called “dependencies”.
An asset is only considered fully loaded when it and all of its dependencies are loaded.
Asset dependencies can be declared when implementing the Asset
trait by implementing the VisitAssetDependencies
trait,
and the #[dependency]
attribute can be used to automatically derive this implementation.
§Custom asset types
While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!), implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats.
Defining a new asset type is as simple as implementing the Asset
trait.
This requires TypePath
for metadata about the asset type,
and VisitAssetDependencies
to track asset dependencies.
In simple cases, you can derive Asset
and Reflect
and be done with it: the required supertraits will be implemented for you.
With a new asset type in place, we now need to figure out how to load it.
While AssetReader
describes strategies to read asset bytes from various sources,
AssetLoader
is the trait that actually turns those into your desired in-memory format.
Generally, (only) AssetLoader
needs to be implemented for custom assets, as the AssetReader
implementations are provided by Bevy.
However, AssetLoader
shouldn’t be implemented for your asset type directly: instead, this is implemented for a “loader” type
that can store settings and any additional data required to load your asset, while your asset type is used as the AssetLoader::Asset
associated type.
As the trait documentation explains, this allows various AssetLoader::Settings
to be used to configure the loader.
After the loader is implemented, it needs to be registered with the AssetServer
using App::register_asset_loader
.
Once your asset type is loaded, you can use it in your game like any other asset type!
If you want to save your assets back to disk, you should implement AssetSaver
as well.
This trait mirrors AssetLoader
in structure, and works in tandem with AssetWriter
, which mirrors AssetReader
.
Re-exports§
pub use ron;
Modules§
- io
- meta
- prelude
- The asset prelude.
- processor
- Asset processing in Bevy is a framework for automatically transforming artist-authored assets into the format that best suits the needs of your particular game.
- saver
- transformer
Macros§
- embedded_
asset - Creates a new
embedded
asset by embedding the bytes of the given path into the current binary and registering those bytes with theembedded
AssetSource
. - embedded_
path - Returns the
Path
for a givenembedded
asset. This is used internally byembedded_asset
and can be used to get aPath
that matches theAssetPath
used by that asset. - load_
internal_ asset - Loads an “internal” asset by embedding the string stored in the given
path_str
and associates it with the given handle. - load_
internal_ binary_ asset - Loads an “internal” binary asset by embedding the bytes stored in the given
path_str
and associates it with the given handle.
Structs§
- AddAsync
Error - Asset
Events - A system set where events accumulated in
Assets
are applied to theAssetEvent
Events
resource. - Asset
Handle Provider - Provides
Handle
andUntypedHandle
for a specific asset type. This should only be used for one specific asset type. - Asset
Index - A generational runtime-only identifier for a specific
Asset
stored inAssets
. This is optimized for efficient runtime usage and is not suitable for identifying assets across app runs. - Asset
Load Failed Event - An event emitted when a specific
Asset
fails to load. - Asset
Loader Error - Asset
Path - Represents a path to an asset in a “virtual filesystem”.
- Asset
Plugin - Provides “asset” loading and processing functionality. An
Asset
is a “runtime value” that is loaded from anAssetSource
, which can be something like a filesystem, a network, etc. - Asset
Server - Loads and tracks the state of
Asset
values from a configuredAssetReader
. This can be used to kick off new asset loads and retrieve their current load states. - Assets
- Stores
Asset
values identified by theirAssetId
. - Assets
MutIterator - A mutable iterator over
Assets
. - Deferred
NestedLoader
will create and return asset handles immediately, but only actually load the asset later.- Dynamic
Typed NestedLoader
has been configured with info on what type of asset to load at runtime.- Erased
Loaded Asset - A “type erased / boxed” counterpart to
LoadedAsset
. This is used in places where the loaded type is not statically known. - Immediate
NestedLoader
will immediately load an asset when requested.- Invalid
Generation Error - Load
Context - A context that provides access to assets in
AssetLoader
s, tracks dependencies, and collects asset load state. - Load
Direct Error - An error that occurs when attempting to call
NestedLoader::load
which is configured to work immediately. - Loaded
Asset - The successful result of an
AssetLoader::load
call. This contains the loaded “root” asset and any other “labeled” assets produced by the loader. It also holds the inputAssetMeta
(if it exists) and tracks dependencies: - Loaded
Folder - A “loaded folder” containing handles for all assets stored in a given
AssetPath
. - Loaded
Untyped Asset - A “loaded asset” containing the untyped handle for an asset stored in a given
AssetPath
. - Missing
Asset Loader ForExtension Error - An error that occurs when an
AssetLoader
is not registered for a given extension. - Missing
Asset Loader ForType IdError - An error that occurs when an
AssetLoader
is not registered for a givenAsset
TypeId
. - Missing
Asset Loader ForType Name Error - An error that occurs when an
AssetLoader
is not registered for a givenstd::any::type_name
. - Nested
Loader - A builder for loading nested assets inside a
LoadContext
. - Reflect
Asset - Type data for the
TypeRegistry
used to operate on reflectedAsset
s. - Reflect
Handle - Reflect type data struct relating a
Handle<T>
back to theT
asset type. - Render
Asset Usages - Defines where the asset will be used.
- Static
Typed NestedLoader
will be provided the type of asset as a type parameter onload
.- Strong
Handle - The internal “strong”
Asset
handle storage forHandle::Strong
andUntypedHandle::Strong
. When this is dropped, theAsset
will be freed. It also stores some asset metadata for easy access from handles. - Track
Assets - A system set that holds all “track asset” operations.
- Unknown
Typed NestedLoader
does not know what type of asset it will be loading.- Untyped
Asset Load Failed Event - An untyped version of
AssetLoadFailedEvent
.
Enums§
- Asset
Event - Events that occur for a specific loaded
Asset
, such as “value changed” events and “dependency” events. - AssetId
- A unique runtime-only identifier for an
Asset
. This is cheap toCopy
/Clone
and is not directly tied to the lifetime of the Asset. This means it can point to anAsset
that no longer exists. - Asset
Load Error - An error that occurs during an
Asset
load. - Asset
Meta Check - Configures how / if meta files will be checked. If an asset’s meta file is not checked, the default meta for the asset will be used.
- Asset
Mode - Controls whether or not assets are pre-processed before being loaded.
- Asset
Server Mode - The “asset mode” the server is currently in.
- Dependency
Load State - The load state of an asset’s dependencies.
- Deserialize
Meta Error - An error that occurs while deserializing
AssetMeta
. - Handle
- A strong or weak handle to a specific
Asset
. If aHandle
isHandle::Strong
, theAsset
will be kept alive until theHandle
is dropped. If aHandle
isHandle::Weak
, it does not necessarily reference a liveAsset
, nor will it keep assets alive. - Load
State - The load state of an asset.
- Parse
Asset Path Error - An error that occurs when parsing a string type to create an
AssetPath
fails, such as duringAssetPath::parse
. - Read
Asset Bytes Error - An error produced when calling
LoadContext::read_asset_bytes
- Recursive
Dependency Load State - The recursive load state of an asset’s dependencies.
- Untyped
Asset Conversion Error - Errors preventing the conversion of to/from an
UntypedHandle
and aHandle
. - Untyped
Asset Id - An “untyped” / “generic-less”
Asset
identifier that behaves much likeAssetId
, but stores theAsset
type information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids across asset types together and enables comparisons between them. - Untyped
Asset IdConversion Error - Errors preventing the conversion of to/from an
UntypedAssetId
and anAssetId
. - Untyped
Handle - An untyped variant of
Handle
, which internally stores theAsset
type information at runtime as aTypeId
instead of encoding it in the compile-time type. This allows handles acrossAsset
types to be stored together and compared. - Wait
ForAsset Error - An error when attempting to wait asynchronously for an
Asset
to load.
Traits§
- Asset
- Declares that this type is an asset,
which can be loaded and managed by the
AssetServer
and stored inAssets
collections. - Asset
App - Adds asset-related builder methods to
App
. - Asset
Container - A type erased container for an
Asset
value that is capable of inserting theAsset
into aWorld
’sAssets
collection. - Asset
Loader - Loads an
Asset
from a given byteReader
. This can acceptAssetLoader::Settings
, which configure how theAsset
should be loaded. - Async
Read Ext - Extension trait for
AsyncRead
. - Async
Write Ext - Extension trait for
AsyncWrite
. - Direct
Asset Access Ext - Erased
Asset Loader - Provides type-erased access to an
AssetLoader
. - Visit
Asset Dependencies - This trait defines how to visit the dependencies of an asset. For example, a 3D model might require both textures and meshes to be loaded.
Functions§
- handle_
internal_ asset_ events - A system that manages internal
AssetServer
events, such as finalizing asset loads.