|
| 1 | +//! In the context of game development, an "asset" is a piece of content that is loaded from disk and displayed in the game. |
| 2 | +//! Typically, these are authored by artists and designers (in contrast to code), |
| 3 | +//! are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts. |
| 4 | +//! |
| 5 | +//! This presents two main challenges: |
| 6 | +//! - Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive. |
| 7 | +//! - Loading assets from disk is slow, and can cause long load times and delays. |
| 8 | +//! |
| 9 | +//! These problems play into each other, for if assets are expensive to store in memory, |
| 10 | +//! then larger game worlds will need to load them from disk as needed, ideally without a loading screen. |
| 11 | +//! |
| 12 | +//! 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. |
| 13 | +//! Bevy coordinates these tasks using the [`AssetServer`] resource, storing each loaded asset in a strongly-typed [`Assets<T>`] collection (also a resource). |
| 14 | +//! [`Handle`]s serve as an id-based reference to entries in the [`Assets`] collection, allowing them to be cheaply shared between systems, |
| 15 | +//! and providing a way to initialize objects (generally entities) before the required assets are loaded. |
| 16 | +//! In short: [`Handle`]s are not the assets themselves, they just tell how to look them up! |
| 17 | +//! |
| 18 | +//! ## Loading assets |
| 19 | +//! |
| 20 | +//! The [`AssetServer`] is the main entry point for loading assets. |
| 21 | +//! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`]. |
| 22 | +//! 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, |
| 23 | +//! calling [`AssetServer::load`] on the same path will return the same handle. |
| 24 | +//! The handle that's returned can be used to instantiate various [`Component`](bevy_ecs::prelude::Component)s that require asset data to function, |
| 25 | +//! which will then be spawned into the world as part of an entity. |
| 26 | +//! |
| 27 | +//! 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. |
| 28 | +//! This can be done by checking the [`LoadState`] of the asset handle using [`AssetServer::is_loaded_with_dependencies`], |
| 29 | +//! which will be `true` when the asset is ready to use. |
| 30 | +//! |
| 31 | +//! Keep track of what you're waiting on by using a [`HashSet`] of asset handles or similar data structure, |
| 32 | +//! which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded. |
| 33 | +//! Bevy's built-in states system can be very helpful for this! |
| 34 | +//! |
| 35 | +//! # Modifying entities that use assets |
| 36 | +//! |
| 37 | +//! If we later want to change the asset data a given component uses (such as changing an entity's material), we have three options: |
| 38 | +//! |
| 39 | +//! 1. Change the handle stored on the responsible component to the handle of a different asset |
| 40 | +//! 2. Despawn the entity and spawn a new one with the new asset data. |
| 41 | +//! 3. Use the [`Assets`] collection to directly modify the current handle's asset data |
| 42 | +//! |
| 43 | +//! The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset. |
| 44 | +//! 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, |
| 45 | +//! you'll need to find that component via a query and change the handle to the new mesh asset. |
| 46 | +//! This is so commonly done that you should think about strategies for how to store and swap handles in your game. |
| 47 | +//! |
| 48 | +//! The second option is the simplest, but can be slow if done frequently, |
| 49 | +//! 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. |
| 50 | +//! Generally, this isn't a great strategy. |
| 51 | +//! |
| 52 | +//! 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. |
| 53 | +//! While this might be what you want, it generally isn't! |
| 54 | +//! |
| 55 | +//! # Hot reloading assets |
| 56 | +//! |
| 57 | +//! Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting. |
| 58 | +//! When enabled, any changes to the underlying asset file will be detected by the [`AssetServer`], which will then reload the asset, |
| 59 | +//! mutating the asset data in the [`Assets`] collection and thus updating all entities that use the asset. |
| 60 | +//! While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly. |
| 61 | +//! |
| 62 | +//! To enable asset hot reloading on desktop platforms, enable `bevy`'s `file_watcher` cargo feature. |
| 63 | +//! To toggle it at runtime, you can use the `watch_for_changes_override` field in the [`AssetPlugin`] to enable or disable hot reloading. |
| 64 | +//! |
| 65 | +//! # Procedural asset creation |
| 66 | +//! |
| 67 | +//! Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels. |
| 68 | +//! After creating an item of a type that implements [`Asset`], you can add it to the [`Assets`] collection using [`Assets::add`]. |
| 69 | +//! Once in the asset collection, this data can be operated on like any other asset. |
| 70 | +//! |
| 71 | +//! Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets: |
| 72 | +//! calling [`Assets::add`] for every entity that needs the asset will create a new copy of the asset for each entity, |
| 73 | +//! quickly consuming memory. |
| 74 | +//! |
| 75 | +//! ## Handles and reference counting |
| 76 | +//! |
| 77 | +//! [`Handle`] (or their untyped counterpart [`UntypedHandle`]) are used to reference assets in the [`Assets`] collection, |
| 78 | +//! and are the primary way to interact with assets in Bevy. |
| 79 | +//! As a user, you'll be working with handles a lot! |
| 80 | +//! |
| 81 | +//! 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. |
| 82 | +//! When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented. |
| 83 | +//! When the reference count hits zero, the asset it references is removed from the [`Assets`] collection. |
| 84 | +//! |
| 85 | +//! This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use. |
| 86 | +//! However, it can lead to surprising behavior if you're not careful! |
| 87 | +//! |
| 88 | +//! There are two categories of problems to watch out for: |
| 89 | +//! - never dropping a handle, causing the asset to never be removed from memory |
| 90 | +//! - dropping a handle too early, causing the asset to be removed from memory while it's still in use |
| 91 | +//! |
| 92 | +//! 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, |
| 93 | +//! and loading them all at the start of the game. |
| 94 | +//! As your game grows, you'll need to be more careful about when you load and unload assets, |
| 95 | +//! segmenting them by level or area, and loading them on-demand. |
| 96 | +//! This problem generally arises when handles are stored in a persistent "collection" or "manifest" of possible objects (generally in a resource), |
| 97 | +//! which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage. |
| 98 | +//! |
| 99 | +//! The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game. |
| 100 | +//! Debugging reveals that the *entities* are still there, but nothing is rendering! |
| 101 | +//! This is because the assets were removed from memory while they were still in use. |
| 102 | +//! 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! |
| 103 | +//! 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. |
| 104 | +//! |
| 105 | +//! # Asset dependencies |
| 106 | +//! |
| 107 | +//! Some assets depend on other assets to be loaded before they can be loaded themselves. |
| 108 | +//! For example, a 3D model might require both textures and meshes to be loaded, |
| 109 | +//! or a 2D level might require a tileset to be loaded. |
| 110 | +//! |
| 111 | +//! The assets that are required to load another asset are called "dependencies". |
| 112 | +//! An asset is only considered fully loaded when it and all of its dependencies are loaded. |
| 113 | +//! Asset dependencies can be declared when implementing the [`Asset`] trait by implementing the [`VisitAssetDependencies`] trait, |
| 114 | +//! and the `#[dependency]` attribute can be used to automatically derive this implementation. |
| 115 | +//! |
| 116 | +//! # Custom asset types |
| 117 | +//! |
| 118 | +//! While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!), |
| 119 | +//! implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats. |
| 120 | +//! |
| 121 | +//! Defining a new asset type is as simple as implementing the [`Asset`] trait. |
| 122 | +//! This requires [`TypePath`] for metadata about the asset type, |
| 123 | +//! and [`VisitAssetDependencies`] to track asset dependencies. |
| 124 | +//! In simple cases, you can derive [`Asset`] and [`Reflect`] and be done with it: the required supertraits will be implemented for you. |
| 125 | +//! |
| 126 | +//! With a new asset type in place, we now need to figure out how to load it. |
| 127 | +//! While [`AssetReader`](io::AssetReader) describes strategies to read asset bytes from various sources, |
| 128 | +//! [`AssetLoader`] is the trait that actually turns those into your desired in-memory format. |
| 129 | +//! Generally, (only) [`AssetLoader`] needs to be implemented for custom assets, as the [`AssetReader`](io::AssetReader) implementations are provided by Bevy. |
| 130 | +//! |
| 131 | +//! However, [`AssetLoader`] shouldn't be implemented for your asset type directly: instead, this is implemented for a "loader" type |
| 132 | +//! 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. |
| 133 | +//! As the trait documentation explains, this allows various [`AssetLoader::Settings`] to be used to configure the loader. |
| 134 | +//! |
| 135 | +//! After the loader is implemented, it needs to be registered with the [`AssetServer`] using [`App::register_asset_loader`](AssetApp::register_asset_loader). |
| 136 | +//! Once your asset type is loaded, you can use it in your game like any other asset type! |
| 137 | +//! |
| 138 | +//! If you want to save your assets back to disk, you should implement [`AssetSaver`](saver::AssetSaver) as well. |
| 139 | +//! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader). |
| 140 | +
|
1 | 141 | // FIXME(3492): remove once docs are ready
|
2 | 142 | #![allow(missing_docs)]
|
3 | 143 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
@@ -240,13 +380,24 @@ impl Plugin for AssetPlugin {
|
240 | 380 | }
|
241 | 381 | }
|
242 | 382 |
|
| 383 | +/// Declares that this type is an asset, |
| 384 | +/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections. |
| 385 | +/// |
| 386 | +/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers. |
| 387 | +/// |
| 388 | +/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type. |
| 389 | +/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`]. |
243 | 390 | #[diagnostic::on_unimplemented(
|
244 | 391 | message = "`{Self}` is not an `Asset`",
|
245 | 392 | label = "invalid `Asset`",
|
246 | 393 | note = "consider annotating `{Self}` with `#[derive(Asset)]`"
|
247 | 394 | )]
|
248 | 395 | pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {}
|
249 | 396 |
|
| 397 | +/// This trait defines how to visit the dependencies of an asset. |
| 398 | +/// For example, a 3D model might require both textures and meshes to be loaded. |
| 399 | +/// |
| 400 | +/// Note that this trait is automatically implemented when deriving [`Asset`]. |
250 | 401 | pub trait VisitAssetDependencies {
|
251 | 402 | fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId));
|
252 | 403 | }
|
|
0 commit comments