Assets, Resources and AssetBundles(5)翻译
5.AssetBundle usage patterns
The previous step in this series covered thefundamentals of AssetBundles, which included the low-level behavior of various loading APIs. This chapter discusses problems and potential solutions to various aspects of using AssetBundles in practice.
本系列的前一篇介绍了AssetBundles的基础知识,包括各种加载APIs的底层行为。本章讨论了在实践中使用AssetBundles 的各个方面的问题和可能的解决方案。
5.1 Managing loaded Assets
It is critical to carefully control the size and number of loaded Objects in memory-sensitive environments. Unity does not automatically unload Objects when they are removed from the active scene. Asset cleanup is triggered at specific times, and it can also be triggered manually.
在内存敏感的环境中,谨慎地控制加载的对象的大小和数量是非常重要的。Unity不会自动卸载从活动场景中移除的对象。Asset 清理是在特定的时间触发的,它也可以手动触发。
AssetBundles themselves must be carefully managed. An AssetBundle backed by a file on local storage (either in the Unity cache or one loaded via AssetBundle.LoadFromFile) has minimal memory overhead, rarely consuming more than a few dozen kilobytes. However, this overhead can still become problematic if a large number of AssetBundles are present.
AssetBundles自身必须谨慎管理。在本地存储(在Unity缓存中或者通过AssetBundle.LoadFromFile加载的)的文件支持的AssetBundle有最小的内存开销,很少消耗几十千字节(几十MB)。然而,如果存在大量的AssetBundles ,这种开销仍然会成为问题。
As most projects allow users to re-experience content (such as replaying a level), it's important to know when to load or unload an AssetBundle. If an AssetBundle is unloaded improperly, it can cause Object duplication in memory. Improperly unloading AssetBundles can also result in undesirable behavior in certain circumstances, such as causing textures to go missing. To understand why this can happen, refer to the Inter-Object references section of the Assets, Objects, and serialization step.
因为大多数项目都允许用户重新体验内容(比如重放一个关卡),所以知道什么时候加载或卸载一个AssetBundle是很重要的。如果不正确地卸载了一个AssetBundle,它可能会导致内存中的对象重复。在某些情况下,不正确地卸载AssetBundles也会导致不期望的行为,比如导致纹理丢失。要理解为什么会发生这种情况,请参考Inter-Object references section of the Assets, Objects, and serialization引用部分。
The most important thing to understand when managing assets and AssetBundles is the difference in behavior when calling AssetBundle.Unload with either true or false for the unloadAllLoadedObjects parameter.
在管理assets and AssetBundles时,需要理解的最重要的事情是调用AssetBundle.Unload时unloadAllLoadedObjects参数使用true或false时卸载行为上的差异。
This API will unload the header information of the AssetBundle being called. The unloadAllLoadedObjects parameter determines whether to also unload all Objects instantiated from this AssetBundle. If set to true, then all Objects originating from the AssetBundle will also be immediately unloaded – even if they are currently being used in the active scene.
这个API将卸载被调用的AssetBundle的头信息。unloadAllLoadedObjects参数决定是否也卸载从这个AssetBundle中实例化的所有对象。如果设置为true,那么来自AssetBundle的所有对象也会立即卸载——即使它们目前正在活动场景中使用。
For example, assume a material M was loaded from an AssetBundle AB, and assume M is currently in the active scene.
例如,假设material M是从AssetBundle AB加载的,并且假设M当前在活动场景中。
If AssetBundle.Unload(true) is called, then M will be removed from the scene, destroyed and unloaded. However, if AssetBundle.Unload(false) is called, then AB's header information will be unloaded but M will remain in the scene and will still be functional. Calling AssetBundle.Unload(false) breaks the link between M and AB. If AB is loaded again later, then fresh copies of the Objects contained in AB will be loaded into memory.
如果AssetBundle.Unload(true)被调用,那么M将从场景中删除、销毁和卸载。但是,如果调用了AssetBundle.Unload(false),那么AB的头信息将被卸载,但M将保留在场景中,并且仍然有效。调用AssetBundle.Unload(false)会中断M和AB之间的链接。如果稍后再次加载AB,那么AB中包含的对象的新副本将被加载到内存中。
If AB is loaded again later, then a new copy of the AssetBundle's header information will be reloaded. However, M was not loaded from this new copy of AB. Unity does not establish any link between the new copy of AB and M.
如果AB稍后被再次加载,那么AssetBundle头信息的一个新的副本将被重新加载。但是M并没有从这个新的AB拷贝中加载,Unity并没有在新的AB拷贝和M之间建立任何联系。
If AssetBundle.LoadAsset() were called to reload M, Unity would not interpret the old copy of M as being an instance of the data in AB. Therefore, Unity will load a new copy of M and there will be two identical copies of M in the scene.
如果调用AssetBundle.LoadAsset()来重新加载M, Unity不会将M的旧的副本解释为AB中数据的一个实例,因此Unity会加载一个M的新副本,在场景中会有两个相同的M副本。
For most projects, this behavior is undesirable. Most projects should use AssetBundle.Unload(true) and adopt a method to ensure that Objects are not duplicated. Two common methods are:
对于大多数项目来说,这种行为是不可取的。大多数项目应该使用AssetBundle.Unload(true)并采用一种方法来确保对象是不重复的。两种常见的方法是:
-
Having well-defined points during the application's lifetime at which transient AssetBundles are unloaded, such as between levels or during a loading screen. This is the simpler and most common option.
-
Maintaining reference-counts for individual Objects and unload AssetBundles only when all of their constituent Objects are unused. This permits an application to unload and reload individual Objects without duplicating memory.
- 在应用程序的生命周期中有明确定义的点,在此点上临时AssetBundles (transient AssetBundles)被卸载,例如在关卡之间或在加载场景期间。这是最简单和最常见的选择。
- 维护单个对象的引用计数,只有当组成AssetBundles的所有对象都未使用时才卸载AssetBundles。这允许应用程序在不复制内存的情况下卸载和重新加载单个对象。
If an application must use AssetBundle.Unload(false), then individual Objects can only be unloaded in two ways:
如果一个应用程序必须使用AssetBundle.Unload(false),那么单个对象只能通过两种方式卸载:
-
Eliminate all references to an unwanted Object, both in the scene and in code. After this is done, call Resources.UnloadUnusedAssets.
-
Load a scene non-additively. This will destroy all Objects in the current scene and invoke Resources.UnloadUnusedAssets automatically.
- 消除所有对不需要的对象的引用,无论是在场景中还是在代码中。完成此操作后,调用Resources.UnloadUnusedAssets。
- 非附加(non-additively)加载一个场景。这将破坏当前场景中的所有对象并自动调用Resources.UnloadUnusedAssets。
If a project has well-defined points where the user can be made to wait for Objects to load and unload, such as in between game modes or levels, these points should be used to unload as many Objects as necessary and to load new Objects.
如果一个项目有明确定义的点,用户可以等待对象加载和卸载,例如在游戏模式或关卡之间,这些点应该用来卸载尽可能多的对象和加载新对象。
The simplest way to do this is to package discrete chunks of a project into scenes, and then build those scenes into AssetBundles, along with all of their dependencies. The application can then enter a "loading" scene, fully unload the AssetBundle containing the old scene, and then load the AssetBundle containing the new scene.
要做到这一点,最简单的方法是将项目的离散块打包到场景中,然后将这些场景连同它们的所有依赖项一起构建到AssetBundles。然后,应用程序可以进入一个“loading”场景,完全卸载包含旧场景的AssetBundle ,然后加载包含新场景的AssetBundle。
While this is the simplest flow, some projects require more complex AssetBundle management. As every project is different, there is no universal AssetBundle design pattern.
虽然这是最简单的流程,但有些项目需要更复杂的AssetBundle 管理。由于每个项目的不同,没有通用的AssetBundle设计模式。
When deciding how to group Objects into AssetBundles, it is generally best to start by bundling Objects into AssetBundles if they must be loaded or updated at the same time. For example, consider a role-playing game. Individual maps and cutscenes can be grouped into AssetBundles by scene, but some Objects will be needed in most scenes. AssetBundles could be built to provide portraits, the in-game UI, and different character models and textures. These latter Objects and Assets could then be grouped into a second set of AssetBundles that are loaded at startup and remain loaded for the lifetime of the app.
在决定如何将对象分组进AssetBundles时,如果这些对象必须同时加载或更新,通常最好一开始就把这些对象打包进同一AssetBundles。例如,考虑一个角色扮演游戏。单个的地图和过场动画可以根据场景分组到AssetBundles中,但是在大多数场景中会需要一些对象。可以构建提供肖像,游戏内UI,以及不同的角色模型和纹理的AssetBundles。这些对象和资产(【注:指前一句提到的UI,角色模型,纹理等】)可以被分组到第二组AssetBundles中,在启动时就加载,并且在应用程序整个生命周期中保持加载。
Another problem can arise if Unity must reload an Object from its AssetBundle after the AssetBundle has been unloaded. In this case, the reload will fail and the Object will appear in the Unity Editor's hierarchy as a (Missing) Object.
如果Unity必须在AssetBundle 被卸载后重新从对象的AssetBundle加载它,则会出现另一个问题。在这种情况下,重新加载将失败,对象将作为一个(丢失的)对象出现在Unity编辑器的层次结构中。
This primarily occurs when Unity loses and regains control over its graphics context, such as when a mobile app is suspended or the user locks their PC. In this case, Unity must re-upload textures and shaders to the GPU. If the source AssetBundle for these assets is unavailable, the application will render Objects in the scene as magenta.
这主要发生在Unity失去或恢复对其图形上下文的控制时,比如当一个移动应用程序被挂起或用户锁定了他们的PC。在这种情况下,Unity必须重新上传纹理和着色器到GPU。如果这些资产的源资产包不可用,应用程序将在场景中以洋红色渲染对象。
5.2. Distribution
There are two basic ways to distribute a project's AssetBundles to clients: installing them simultaneously with the project or downloading them after installation.
将项目的AssetBundles 分发给客户端有两种基本方法:与项目同时安装它们,或者在安装后下载它们。
The decision whether to ship AssetBundles within or after installation is driven by the capabilities and restrictions of the platforms on which the project will run. Mobile projects usually opt for post-install downloads to reduce initial install size and remain below over-the-air download size limits. Console and PC projects generally ship AssetBundles with their initial install.
决定是在安装时还是在安装后发布AssetBundles是由运行项目的平台的能力和限制所驱动的。移动项目通常选择安装后下载,以减少初始安装大小,并保持在无线下载大小的限制之下。控制台和PC项目通常在初始安装时附带AssetBundles。
Proper architecture permits patching new or revised content into your project post-install regardless of how the AssetBundles are delivered initially. For more information on this, see the Patching with AssetBundles section of the Unity Manual.
适当的架构允许在安装后将新的或修改过的内容打补丁到项目中,而不管最初的资产包是如何交付的。有关这方面的更多信息,请参见Unity手册中的“Patching with AssetBundles”一节。
5.2.1. Shipped with project
Shipping AssetBundles with the project is the simplest way to distribute them as it does not require additional download-management code. There are two major reasons why a project might include AssetBundles with the install:
在项目中分发AssetBundles是分发它们的最简单方法,因为它不需要额外的下载管理代码。一个项目可以在安装时包含AssetBundles,有两个主要的原因,:
-
To reduce project build times and permit simpler iterative development. If these AssetBundles do not need to be updated separately from the application itself, then the AssetBundles can be included with the application by storing the AssetBundles in Streaming Assets. See the Streaming Assets section, below.
- 减少项目构建时间并允许更简单的迭代开发。如果这些AssetBundles不需要从应用程序本身单独更新,那么这些AssetBundles 可以通过在Streaming Assets中存储AssetBundles 来包含在应用程序中。请参阅下面的Streaming Assets section。
-
To ship an initial revision of updatable content. This is commonly done to save end-users time after their initial install or to serve as the basis for later patching. Streaming Assets is not ideal for this case. However, if writing a custom downloading and caching system is not an option, then an initial revision of updatable content can be loaded into the Unity cache from Streaming Assets (See the Cache Priming section, below).
- 发布可更新内容的初始版本。这样做通常是为了在初始安装后为最终用户节省时间,或者作为以后打补丁的基础。 Streaming Assets在这种情况下并不理想。然而,如果编写一个自定义下载和缓存系统不是一个选项,那么一个可更新内容的初始版本可以从Streaming Assets加载到Unity缓存中(参见下面的Cache Priming section)。
5.2.1.1. Streaming Assets
The easiest way to include any type of content, including AssetBundles, within a Unity application at install time is to build the content into the /Assets/StreamingAssets/ folder, prior to building the project. Anything contained in the StreamingAssets folder at build time will be copied into the final application.
在安装时在Unity应用程序中包含任何类型的内容,包括AssetBundles,最简单的方法是在构建项目之前将内容放入/Assets/StreamingAssets/文件夹中。在构建时StreamingAssets文件夹中包含的任何内容都将复制到最终的应用程序中。
The full path to the StreamingAssets folder on local storage is accessible via the property Application.streamingAssetsPath at runtime. The AssetBundles can then be loaded with via AssetBundle.LoadFromFile on most platforms.
可以通过Application.streamingAssetsPath属性在运行时本地存储上的StreamingAssets文件夹的完整路径。在大多数平台上,然后可以通过AssetBundle.LoadFromFile加载AssetBundles 。
Android Developers: On Android, assets in the StreamingAssets folders are stored into the APK and may take more time to load if they are compressed, as files stored in an APK can use different storage algorithms. The algorithm used may vary from one Unity version to another. You can use an archiver such as 7-zip to open the APK to determine if the files are compressed or not. If they are, you can expect AssetBundle.LoadFromFile() to perform more slowly. In this case, you can retrieve the cached version by using UnityWebRequest.GetAssetBundle as a workaround. By using UnityWebRequest, the AssetBundle will be uncompressed and cached during the first run, allowing for following executions to be faster. Note that this will take more storage space, as the AssetBundle will be copied to the cache. Alternatively, you can export your Gradle project and add an extension to your AssetBundles at build time. You can then edit the build.gradle file and add that extension to the noCompress section. Once done, you should be able to use AssetBundle.LoadFromFile() without having to pay the decompression performance cost.
Android开发者:在Android上,StreamingAssets文件夹中的assets被存储在APK中,如果它们被压缩,可能需要更多的时间来加载,因为存储在APK中的文件可以使用不同的存储算法。使用的算法可能因Unity版本的不同而有所不同。可以使用7-zip之类的archiver(压缩软件)打开APK,以确定文件是否被压缩。如果压缩过,那么AssetBundle.LoadFromFile()的执行速度会慢一些。在这种情况下,你可以通过使用UnityWebRequest.GetAssetBundle来检索缓存的版本。作为一个解决方案。通过使用UnityWebRequest,AssetBundle将在第一次运行时被解压并缓存,从而使后续执行速度更快。注意,这将占用更多的存储空间,因为AssetBundle将被复制到缓存中。或者,您可以导出您的Gradle项目,并在构建时向您的AssetBundles添加一个扩展。然后您可以编辑build.gradle文件并将扩展添加到未压缩部分。完成之后,您应该能够使用AssetBundle.LoadFromFile(),而不必为解压缩的性能付出代价。
Note: Streaming Assets is not a writable location on some platforms. If a project's AssetBundles need to be updated after installation, either use WWW.LoadFromCacheOrDownload or write a custom downloader.
注意:Streaming Assets在某些平台上不是可写的位置。如果一个项目的AssetBundles包需要安装后更新,使用WWW.LoadFromCacheOrDownload或编写一个自定义下载器。
5.2.2. Downloaded post-install
The favored method of delivering AssetBundles to mobile devices is to download them after app installation. This also allows the content to be updated after installation without forcing users to re-download the entire application. On many platforms, application binaries must undergo an expensive and lengthy recertification process. Therefore, developing a good system for post-install downloads is vital.
向移动设备分发AssetBundles的首选方法是在应用程序安装后下载它们。这也允许在安装后更新内容,而不强制用户重新下载整个应用程序。在许多平台上,应用程序二进制文件必须经历昂贵而漫长的重新认证过程。因此,为安装后的下载开发一个好的系统是至关重要的。
The simplest way to deliver AssetBundles is to place them on a web server and deliver them via UnityWebRequest. Unity will automatically cache downloaded AssetBundles on local storage. If the downloaded AssetBundle is LZMA compressed, the AssetBundle will be stored in the cache either as uncompressed or re-compressed as LZ4 (dependent on the Caching.compressionEnabled setting), for faster loading in the future. If the downloaded bundle is LZ4 compressed, the AssetBundle will be stored compressed. If the cache fills up, Unity will remove the least recently used AssetBundle from the cache. See the Built-in caching section for more details.
分发AssetBundles最简单的方式是将它们放在web服务器上,然后通过UnityWebRequest分发。Unity会自动将下载的AssetBundles缓存到本地存储中。如果下载的资源包是LZMA压缩的,那么资源包将以未压缩或以LZ4重新压缩的形式存储在缓存中(这取决于Caching.compressionEnabled设置),以便在将来更快的加载。如果下载的包是LZ4压缩的,那么AssetBundle将被压缩存储。如果缓存已满,Unity将从缓存中删除最近最少使用的AssetBundle。有关更多细节,请参阅Built-in caching。
It is generally recommended to start by using UnityWebRequest when possible, or WWW.LoadFromCacheOrDownload only if using Unity 5.2 or older. Only invest in a custom download system if the built-in APIs' memory consumption, caching behavior or performance are unacceptable for a specific project, or if a project must run platform-specific code to achieve its requirements.
一般建议在可能的情况下使用UnityWebRequest,或仅当使用Unity 5.2或更老版本时WWW.LoadFromCacheOrDownload。如果内置APIs的内存消耗、缓存行为或性能对于特定项目来说是不可接受的,或者项目必须运行特定于平台的代码来满足需求,那么就只能致力于定制的下载系统。
Examples of situations which may prevent the use of UnityWebRequest or WWW.LoadFromCacheOrDownload:
可能会阻止使用UnityWebRequest或WWW.LoadFromCacheOrDownload的情况:
-
When fine-grained control over the AssetBundle cache is required
- 当需要对AssetBundle缓存进行细粒度控制时
-
When a project needs to implement a custom compression strategy
- 当项目需要实现自定义压缩策略时
-
When a project wishes to use platform-specific APIs to satisfy certain requirements, such as the need to stream data while inactive.
- 当项目希望使用特定于平台的APIs来满足某些需求时,比如在非活动状态下需要流数据。
- Example: Using iOS' Background Tasks API to download data while in the background.
- 例如:在后台使用iOS的Background Tasks API下载数据。
-
When AssetBundles must be delivered over SSL on platforms where Unity does not have proper SSL support (such as PC).
- 当AssetBundles 必须在Unity没有适当SSL支持的平台(如PC)上通过SSL交付时。
5.2.3. Built-in caching 内置缓存
Unity has a built-in AssetBundle caching system that can be used to cache AssetBundles downloaded via the UnityWebRequest API, which has an overload accepting an AssetBundle version number as an argument. This number is not stored inside the AssetBundle, and is not generated by the AssetBundle system.
Unity有一个内置的资产包缓存系统,可以用来缓存通过UnityWebRequest API下载的AssetBundles,该API有一个重载接受一个AssetBundle版本号作为参数。这个数字不存储在资产包中,也不是由资产包系统生成的。
The caching system keeps track of the last version number passed to UnityWebRequest. When this API is called with a version number, the caching system checks to see if there is a cached AssetBundle by comparing version numbers. If these numbers match, the system will load the cached AssetBundle. If the numbers do not match, or there is no cached AssetBundle, then Unity will download a new copy. This new copy will be associated with the new version number.
缓存系统会记录上次传递给UnityWebRequest的版本号。当使用版本号调用此API时,缓存系统通过比较版本号来检查是否存在缓存的AssetBundle。如果这些数字匹配,系统将加载缓存的AssetBundle 。如果数字不匹配,或者没有缓存的AssetBundle,那么Unity将下载一个新的拷贝。这个新的副本将与新的版本号相关联。
AssetBundles in the caching system are identified only by their file names, and not by the full URL from which they are downloaded. This means that an AssetBundle with the same file name can be stored in multiple different locations, such as a Content Delivery Network. As long as the file names are identical, the caching system will recognize them as the same AssetBundle.
缓存系统中的AssetBundles只能通过它们的文件名来识别,而不能通过下载它们时的完整URL来识别。这意味着具有相同文件名的资产包可以存储在多个不同的位置,比如内容分发网络。只要文件名相同,缓存系统就会将它们识别为相同的资产包。
It is up to each individual application to determine an appropriate strategy for assigning version numbers to AssetBundles, and to pass these numbers to UnityWebRequest. The numbers may come from unique identifiers of sorts, such as a CRC value. Note that while AssetBundleManifest.GetAssetBundleHash() may also be used for this purpose, we don’t recommend this function for versioning, as it provides just an estimation, and not a true hash calculation).
每个单独的应用程序都应该决定一个适当的策略,为AssetBundles分配版本号,并将这些版本号传递给UnityWebRequest。数字可能来自各种唯一标识符,比如CRC值。注意,虽然AssetBundleManifest.GetAssetBundleHash()也可以用于此目的,但我们不推荐使用该函数进行版本控制,因为它仅提供估算,而不是真正的散列计算)。
See the Patching with AssetBundles section of the Unity Manual for more details.
更多细节,请参见Unity手册中的Patching with AssetBundles部分。
In Unity 2017.1 onward, the Caching API has been extended to provide more granular control, by allow developers to select an active cache from multiple caches. Prior versions of Unity may only modify Caching.expirationDelay and Caching.maximumAvailableDiskSpace to remove cached items (these properties remain in Unity 2017.1 in the Cache class).
在Unity 2017.1之后,Caching API已经扩展到提供更细粒度的控制,允许开发者从多个缓存中选择一个活动缓存。以前版本的Unity只能修改Caching.expirationDelay和Caching.maximumAvailableDiskSpace去移除缓存项(这些属性保留在Unity 2017.1的Cache类中)。
- expirationDelay is the minimum number of seconds that must elapse before an AssetBundle is automatically deleted. If an AssetBundle is not accessed during this time, it will be deleted automatically.
- expirationDelay是在一个资产包被自动删除之前必须经过的最小秒数。如果一个AssetBundle 在此期间没有访问,它将被自动删除。
- maximumAvailableDiskSpace specifies the amount of space on local storage, in bytes, that the cache may use before it begins deleting AssetBundles that have been used less recently than the expirationDelay. When the limit is reached, Unity will delete the AssetBundle in the cache which was least recently opened (or marked as used via Caching.MarkAsUsed). Unity will delete cached AssetBundles until there is sufficient space to complete the new download.
- maximumAvailableDiskSpace指定本地存储上的空间量,以字节为单位,缓存在开始删除最近使用的小于expirationDelay的AssetBundles之前可以使用的空间量。当达到这个限制时,Unity会删除缓存中最近最少打开的资源包(通过 Caching.MarkAsUsed 标识已使用的 AssetBundle)。Unity会删除缓存的资源包,直到有足够的空间来完成新的下载。
5.2.3.1. Cache Priming 缓存启动
Because AssetBundles are identified by their file names, it is possible to "prime" the cache with AssetBundles shipped with the application. To do this, store the initial or base version of each AssetBundle in /Assets/StreamingAssets/. The process is identical to the one detailed in theShipped with project section.
因为AssetBundles 是通过它们的文件名来标识的,所以可以用应用程序附带的AssetBundles来“prime(准备)”缓存。为此,将每个AssetBundle 的初始版本或基本版本存储在/Assets/StreamingAssets/中。该过程与Shipped with project中详细描述的过程相同。
The cache can be populated by loading AssetBundles from Application.streamingAssetsPath the first time the application is run. From then on, the application can call UnityWebRequest normally (UnityWebRequest can also be used to initially load AssetBundles from the StreamingAssets path as well).
应用程序第一次运行时,缓存可以通过从Application.streamingAssetsPath加载AssetBundles来填充。从那时起,应用程序可以正常调用UnityWebRequest (UnityWebRequest也可以用来初始加载StreamingAssets路径中的AssetBundles)。
5.2.4. Custom downloaders 自定义下载器
Writing a custom downloader gives an application full control over how AssetBundles are downloaded, decompressed and stored. As the engineering work involved is non-trivial, we recommend this approach only for larger teams. There are four major considerations when writing a custom downloader:
编写一个自定义下载程序可以让应用程序完全控制如何下载、解压和存储AssetBundles。由于所涉及的工程工作是很困难的,所以我们只推荐这种方法用于较大的团队。在编写自定义下载器时,有四个主要考虑:
-
Download mechanism 下载机制
-
Storage location
-
Compression type
-
Patching
For information on patching AssetBundles, see the Patching with AssetBundles section of the Unity Manual.
有关为资产包打补丁的信息,请参阅Unity手册中的Patching with AssetBundles一节。
5.2.4.1. Downloading
For most applications, HTTP is the simplest method to download AssetBundles. However, implementing an HTTP-based downloader is not the simplest task. Custom downloaders must avoid excessive memory allocations, excessive thread usage and excessive thread wakeups. Unity's WWW class is unsuitable for reasons exhaustively described in the WWW.LoadFromCacheOrDownload section of the AssetBundle fundamentals step.
对于大多数应用程序,HTTP是下载资产包最简单的方法。然而,实现基于HTTP的下载器并不是最简单的任务。自定义下载器必须避免过多的内存分配、过多的线程使用和过多的线程调用。Unity的WWW类是不合适的。AssetBundle fundamentals 中的WWW.LoadFromCacheOrDownload 部分已经有详尽描述。
When writing a custom downloader, there are three options:
当编写一个自定义下载器时,有三个选项:
-
C#'s HttpWebRequest and WebClient classes
-
Custom native plugins
-
Asset store packages
5.2.4.1.1. C# classes
If an application does not require HTTPS/SSL support, C#'s WebClient class provides the simplest possible mechanism for downloading AssetBundles. It is capable of asynchronously downloading any file directly to local storage without excessive managed memory allocation.
如果应用程序不需要HTTPS/SSL支持,C#的WebClient类为下载AssetBundles提供了最简单的可能机制。它能够异步下载任何文件直接到本地存储,而不需要过多的托管内存分配。
To download an AssetBundle with WebClient, allocate an instance of the class and pass it the URL of the AssetBundle to download and a destination path. If more control is required over the request's parameters, it is possible to write a downloader using C#'s HttpWebRequest class:
要用WebClient下载一个AssetBundle ,需要分配这个类的一个实例,并向它传递要下载的资产包的URL和一个目标路径。如果需要对请求的参数进行更多的控制,可以使用C#的HttpWebRequest类编写一个下载器:
-
Get a byte stream from HttpWebResponse.GetResponseStream.
-
Allocate a fixed-size byte buffer on the stack.
-
Read from the response stream into the buffer.
-
Write the buffer to disk using C#'s File.IO APIs, or any other streaming IO system.
- 从HttpWebResponse.GetResponseStream获得一个字节流。
- 在堆栈上分配一个固定大小的字节缓冲区。
- 从响应流读入缓冲区。
- 使用C#的File.IO APIs将缓冲区写入磁盘或任何其他流IO系统。
5.2.4.1.2. Asset Store Packages
Several asset store packages offer native-code implementations to download files via HTTP, HTTPS and other protocols. Before writing a custom native-code plugin for Unity, it is recommended to evaluate available Asset Store packages.
一些资asset store packages提供了原生代码实现,以通过HTTP、HTTPS和其他协议下载文件。在为Unity编写自定义本地代码插件之前,建议先评估可用的资产存储包。
5.2.4.1.3. Custom Native Plugins 自定义原生插件
Writing a custom native plugin is the most time-intensive, but most flexible method for downloading data in Unity. Due to the high programming time requirements and high technical risk, this method is only recommended if no other method is capable of satisfying an application's requirements. For example, a custom native plugin may be necessary if an application must use SSL communication on platforms without C# SSL support in Unity.
编写一个自定义原生插件在Unity中下载数据是最费时,但也是最灵活的方法。由于高编程时间要求和高技术风险,只有在没有其他方法能够满足应用程序的要求时,才推荐使用此方法。例如,如果应用程序必须在Unity中没有C# SSL支持的平台上使用SSL通信,那么定制本地插件可能是必要的。
A custom native plugin will generally wrap a target platform's native downloading APIs. Examples include NSURLConnection on iOS and java.net.HttpURLConnection on Android. Consult each platform's native documentation for further details on using these APIs.
定制的本地插件通常会包装目标平台的本地下载APIs。示例包括iOS上的NSURLConnection和Android上的java.net.HttpURLConnection。有关使用这些api的详细信息,请参阅每个平台的本机文档。
5.2.4.2. Storage 存储
On all platforms, Application.persistentDataPath points to a writable location that should be used for storing data that should persist between runs of an application. When writing a custom downloader, it is strongly recommended to use a subdirectory of Application.persistentDataPath to store downloaded data.
在所有平台上,Application.persistentDataPath指向一个可写的位置,该位置应用于存储应用程序运行之间应该持久存储的数据。在编写自定义下载程序时,强烈建议使用Application.persistentDataPath的子目录存储下载的数据。
Application.streamingAssetPath is not writable and is a poor choice for an AssetBundle cache. Example locations for streamingAssetsPath include:
-
OSX: Within .app package; not writable.
-
Windows: Within install directory (e.g. Program Files); usually not writable
-
iOS: Within .ipa package; not writable
-
Android: Within .apk file; not writable
5.3. Asset Assignment Strategies(Asset分配策略)
Deciding how to divide a project's assets into AssetBundles is not simple. It is tempting to adopt a simplistic strategy, such as placing all Objects in their own AssetBundle or using only a single AssetBundle, but these solutions have significant drawbacks:
决定如何将一个项目的assets划分进AssetBundles并不简单。采用一种简单的策略是很诱人的,比如将所有对象放在它们自己的AssetBundle中,或者只使用一个AssetBundle ,但是这些解决方案有明显的缺点:
-
Having too few AssetBundles...
-
拥有的资产太少了……
-
Increases runtime memory usage
-
增加运行时内存使用
-
Increases loading times
-
增加加载时间
-
Requires larger downloads
- 需要更大的下载
-
Having too many AssetBundles...
- 拥有太多的AssetBundles…
-
Increases build times
- 构建时间增加
-
Can complicate development
- 会使开发变得复杂
-
Increases total download time
- 增加总下载时间
The key decision is how to group Objects into AssetBundles. The primary strategies are:
关键决策是如何将对象分组到AssetBundles中。主要战略是:
-
Logical entities
- 逻辑实体
-
Object Types
- 对象类型
-
Concurrent content
- 并发内容
5.4. Common pitfalls 常见缺点
This section describes several problems that commonly appear in projects using AssetBundles.
本节描述在使用资产包的项目中经常出现的几个问题。
5.4.1. Asset duplication (Asset 重复)
Unity 5's AssetBundle system will discover all dependencies of an Object when the Object is built into an AssetBundle. This dependency information is used to determine the set of Objects that will be included in an AssetBundle.
Unity 5的AssetBundle系统将对象被构建到一个AssetBundle时发现对象的所有依赖当。此依赖项信息用于确定将包含在一个AssetBundle中的对象集合。
Objects that are explicitly assigned to an AssetBundle will only be built into that AssetBundle. An Object is "explicitly assigned" when that Object's AssetImporter has its assetBundleName property set to a non-empty string. This can be done in the Unity Editor by selecting an AssetBundle in the Object's Inspector, or from Editor scripts.
显式分配给一个AssetBundle 的对象将只被打包进于那个AssetBundle中。当对象的AssetImporter的assetBundleName属性设置为非空字符串时,该对象被“显式分配”。这可以在Unity编辑器中通过在对象的检查器中选择一个AssetBundle来完成,或者从编辑器脚本中完成。
Objects can also be assigned to an AssetBundle by defining them as part of an AssetBundle building map, which is to be used in conjunction with the overloaded BuildPipeline.BuildAssetBundles() function that takes in an array of AssetBundleBuild.
通过将对象定义为AssetBundle building map的一部分,也可以将对象分配给资产束,该映射将与重载的BuildPipeline.BuildAssetBundles()函数一起使用,该函数接受一个AssetBundleBuild数组。
Any Object that is not explicitly assigned in an AssetBundle will be included in all AssetBundles that contain 1 or more Objects that reference the untagged Object.
任何没有在AssetBundle中显式分配的对象都将包含在所有包含1个或多个引用未标记对象的AssetBundles中。
For example, if two different Objects are assigned to two different AssetBundles, but both have references to a common dependency Object, then that dependency Object will be copied into both AssetBundles. The duplicated dependency will also be instanced, meaning that the two copies of the dependency Object will be considered different Objects with different identifiers. This will increase the total size of the application's AssetBundles. This will also cause two different copies of the Object to be loaded into memory if the application loads both of its parents.
例如,如果两个不同的对象被分配给两个不同的资产包,但都引用了一个公共依赖对象,那么这个依赖对象将被复制到两个AssetBundles中。重复的依赖项也将被实例化,这意味着依赖项对象的两个副本将被视为具有不同标识符的不同对象。这将增加应用程序AssetBundles的总大小。如果应用程序同时加载它的父物体,这也会导致对象的两个不同副本被加载到内存中。
There are several ways to address this problem:
有几种方法可以解决这个问题:
-
Ensure that Objects built into different AssetBundles do not share dependencies. Any Objects which do share dependencies can be placed into the same AssetBundle without duplicating their dependencies.
- 确保内置于不同AssetBundles中的对象不共享依赖关系。任何共享依赖关系的对象都可以放在同一个AssetBundle中,而不不是复制它们的依赖关系。
-
This method usually is not viable for projects with many shared dependencies. It produces monolithic AssetBundles that must be rebuilt and re-downloaded too frequently to be convenient or efficient.
- 这种方法通常不适用于有许多共享依赖项的项目。它产生的巨大的AssetBundles必须频繁的重新构建和重新下载,以致不方便,效率低。
-
Segment AssetBundles so that no two AssetBundles that share a dependency will be loaded at the same time.
分段资产包,这样就不会同时加载两个共享依赖关系的资产包。
-
This method may work for certain types of projects, such as level-based games. However, it still unnecessarily increases the size of the project's AssetBundles, and increases both build times and loading times.
- 这种方法可能适用于某些类型的项目,比如基于关卡的游戏。然而,它仍然不必要地增加了项目AssetBundles的大小,增加了构建时间和加载时间。
-
Ensure that all dependency assets are built into their own AssetBundles. This entirely eliminates the risk of duplicated assets, but also introduces complexity. The application must track dependencies between AssetBundles, and ensure that the right AssetBundles are loaded before calling any AssetBundle.LoadAsset APIs.
- 确保所有依赖资产都内置于它们自己的AssetBundles中。这完全消除了重复资产的风险,但也引入了复杂性。应用程序必须跟踪AssetBundles之间的依赖关系,并确保在调用LoadAsset APIs之前加载了正确的AssetBundles 。
Object dependencies are tracked via the AssetDatabase API, located in the UnityEditor namespace. As the namespace implies, this API is only available in the Unity Editor and not at runtime. AssetDatabase.GetDependencies can be used to locate all of the immediate dependencies of a specific Object or Asset. Note that these dependencies may have their own dependencies. Additionally, the AssetImporter API can be used to query the AssetBundle to which any specific Object is assigned.
对象依赖关系通过位于UnityEditor命名空间中的AssetDatabase API来跟踪。正如名称空间所蕴含的,这个API只能在Unity编辑器中使用,而不能在运行时使用。AssetDatabase.GetDependencies可用于定位特定对象或Asset的所有直接依赖项。注意,这些依赖项可能有它们自己的依赖项。此外,AssetImporter API可以用来查询分配给AssetBundle的任何特定对象。
By combining the AssetDatabase and AssetImporter APIs, it is possible to write an Editor script that ensures that all of an AssetBundle's direct or indirect dependencies are assigned to AssetBundles, or that no two AssetBundles share dependencies that have not been assigned to an AssetBundle. Due to the memory cost of duplicating assets, it is recommended that all projects have such a script.
通过结合AssetDatabase和AssetImporter APIs,有可能编写一个编辑器脚本,以确保一个资产包的所有直接或间接依赖被分配给AssetBundles,或者没有两个资产包共享依赖,但没有将依赖指定给一个AssetBundle的。由于重复assets的内存成本,建议所有项目都有这样的脚本。
5.4.2. Sprite atlas duplication
Any automatically-generated sprite atlas will be assigned to the AssetBundle containing the Sprite Objects from which the sprite atlas was generated. If the sprite Objects are assigned to multiple AssetBundles, then the sprite atlas will not be assigned to an AssetBundle and will be duplicated. If the Sprite Objects are not assigned to an AssetBundle, then the sprite atlas will also not be assigned to an AssetBundle.
任何自动生成的精灵图集都将被分配到包含精灵对象的AssetBundle 中,精灵图集就是从这些对象中生成的。如果精灵对象被分配给多个资源包,那么精灵图集将不会被分配给一个资源包,而会被复制。如果精灵对象没有被分配给一个资产束,那么精灵图集也不会被分配给一个资产束。
To ensure that sprite atlases are not duplicated, check that all sprites tagged into the same sprite atlas are assigned to the same AssetBundle.
为了确保精灵图集不重复,检查标记为同一个精灵图集中的所有精灵都被分配到同一个AssetBundle中。
Note that in Unity 5.2.2p3 and older, automatically-generated sprite atlases will never be assigned to an AssetBundle. Because of this, they will be included in any AssetBundles containing their constituent sprites and also any AssetBundles referencing their constituent sprites. Because of this problem, it is strongly recommended that all Unity 5 projects using Unity's sprite packer upgrade to Unity 5.2.2p4, 5.3 or any newer version of Unity.
注意在Unity 5.2.2p3和更早的版本中,自动生成的精灵图集永远不会被分配给一个AssetBundle。因此,它们将被包含在任何包含其组成子元素的AssetBundles中,以及任何引用其组成子元素的AssetBundles中。因为这个问题,我们强烈建议所有使用Unity的sprite packer软件的Unity 5项目升级到Unity 5.2.2p4, 5.3或者任何更新的Unity版本。
5.4.3. Android textures
Due to heavy device fragmentation in the Android ecosystem, it is often necessary to compress textures into several different formats. While all Android devices support ETC1, ETC1 does not support textures with alpha channels. Should an application not require OpenGL ES 2 support, the cleanest way to solve the problem is to use ETC2, which is supported by all Android OpenGL ES 3 devices.
Most applications need to ship on older devices where ETC2 support is unavailable. One way to solve this problem is with Unity 5's AssetBundle Variants (refer to Unity's Android optimization guide for details on other options).
To use AssetBundle Variants, all textures that cannot be cleanly compressed using ETC1 must be isolated into texture-only AssetBundles. Next, create sufficient variants of these AssetBundles to support the non-ETC2-capable slices of the Android ecosystem, using vendor-specific texture compression formats such as DXT5, PVRTC and ATITC. For each AssetBundle Variant, change the included textures' TextureImporter settings to the compression format appropriate to the Variant.
At runtime, support for the different texture compression formats can be detected using the SystemInfo.SupportsTextureFormat API. This information should be used to select and load the AssetBundle Variant containing textures compressed in a supported format.
More information on Android texture compression formats can be found here.
5.4.4. iOS file handle overuse
Current versions of Unity are not affected by this issue.
In versions prior to Unity 5.3.2p2, Unity would hold an open file handle to an AssetBundle the entire time that the AssetBundle is loaded. This is not a problem on most platforms. However, iOS limits the number of file handles a process may simultaneously have open to 255. If loading an AssetBundle causes this limit to be exceeded, the loading call will fail with a "Too Many Open File Handles" error.
This was a common problem for projects trying to divide their content across many hundreds or thousands of AssetBundles.
For projects unable to upgrade to a patched version of Unity, temporary solutions are:
-
Reducing the number of AssetBundles in use by merging related AssetBundles
-
Using AssetBundle.Unload(false) to close an AssetBundle's file handle, and managing the loaded Objects' lifecycles manually
5.5. AssetBundle Variants
A key feature of the AssetBundle system is the introduction of AssetBundle Variants. The purpose of Variants is to allow an application to adjust its content to better suit its runtime environment. Variants permit different UnityEngine.Objects in different AssetBundle files to appear as being the "same" Object when loading Objects and resolving Instance ID references. Conceptually, it permits two UnityEngine.Objects to appear to share the same File GUID & Local ID, and identifies the actual UnityEngine.Object to load by a string Variant ID.
AssetBundle系统的一个关键特性是引入了AssetBundle Variants。变体的目的是允许应用程序调整其内容以更好地适应其运行时环境。变体允许不同的AssetBundle文件中中不同的UnityEngine.Object在加载对象和解析实例ID引用时看起来是“相同的”对象。从概念上讲,它允许两个UnityEngine.Object共享相同的File GUID和Local ID,并通过字符串变量ID标识实际加载的UnityEngine.Object。
There are two primary use cases for this system:
这个系统有两个主要用例:
-
Variants simplify the loading of AssetBundles appropriate for a given platform.
-
Example: A build system might create an AssetBundle containing high-resolution textures and complex shaders suitable for a standalone DirectX11 Windows build, and a second AssetBundle with lower-fidelity content intended for Android. At runtime, the project's resource loading code can then load the appropriate AssetBundle Variant for its platform, and the Object names passed into the AssetBundle.Load API do not need to change.
-
Variants allow an application to load different content on the same platform, but with different hardware.
-
This is key for supporting a wide range of mobile devices. An iPhone 4 is incapable of displaying the same fidelity of content as the latest iPhone in any real-world application.
-
On Android, AssetBundle Variants can be used to tackle the immense fragmentation of screen aspect ratios and DPIs between devices.
- 变体简化了适合于给定平台的AssetBundles的加载。
- 例如:一个构建系统可能会创建一个包含高分辨率纹理和复杂着色器的AssetBundle ,适用于一个独立的DirectX11 Windows构建,和含有低保真内容的第二个AssetBundles,用于Android。在运行时,项目的资源加载代码可以为其平台加载合适的AssetBundle变体,传递到AssetBundle.Load API中的对象名称不需要改变。
- 变体允许应用程序在使用不同的硬件的同一平台上加载不同的内容。
- 这是支持各种移动设备的关键。在任何现实世界的应用程序中,iPhone 4都无法显示与最新款iPhone相同的内容保真度。
- 在Android上,AssetBundle变体可以用来解决设备之间屏幕长宽比和DPIs的巨大差异。
5.5.1. Limitations
A key limitation of the AssetBundle Variant system is that it requires Variants to be built from distinct Assets. This limitation applies even if the only variations between those Assets is their import settings. If the only distinction between a texture built into Variant A and Variant B is the specific texture compression algorithm selected in the Unity texture importer, Variant A and Variant B must still be entirely different Assets. This means that Variant A and Variant B must be separate files on disk.
AssetBundle变体系统的一个关键限制是,它要求从不同的Assets构建变体。即使这些Assets之间的唯一差异是它们的导入设置,也适用于此限制。如果放入变体A和变体B中的纹理的唯一区别是在Unity纹理导入器中选择的特定纹理压缩算法,那么变体A和变体B肯定还是完全不同的资产。这意味着变体A和变体B必须是磁盘上的独立文件。
This limitation complicates the management of large projects as multiple copies of a specific Asset must be kept in source control. All copies of an Asset must be updated when developers wish to change the content of the Asset. There are no built-in workarounds for this problem.
这种限制使大型项目的管理复杂化,因为必须在源代码控制中保存特定Asset的多个副本。当开发人员希望更改资产的内容时,必须更新资产的所有副本。对于这个问题没有内置的解决方案。
Most teams implement their own form of AssetBundle Variants. This is done by building AssetBundles with well-defined suffixes appended to their filenames, in order to identify the specific variant a given AssetBundle represents. Custom code programmatically alters the importer settings of the included Assets when building these AssetBundles. Some developers have extended their custom systems to also be able to alter parameters on components attached to prefabs.
大多数团队实现他们自己形式的AssetBundle变体。这是通过在文件名中添加定义良好的后缀来构建AssetBundles 来实现的,以便识别给定AssetBundle所表示的特定变体。自定义代码以编程方式更改了创建这些AssetBundles所包含资产的导入器设置。一些开发人员已经扩展了他们的定制系统,使其能够更改附加在预制件上的组件的参数。
5.6. Compressed or uncompressed?
Whether to compress AssetBundles requires several important considerations, which include:
是否压缩AssetBundles需要考虑几个重要因素,其中包括:
-
Loading time: Uncompressed AssetBundles are much faster to load than compressed AssetBundles when loading from local storage or a local cache.
- 加载时间:当从本地存储或本地缓存加载时,未压缩的资源包比压缩资源包加载要快得多。
-
Build time: LZMA and LZ4 are very slow when compressing files, and the Unity Editor processes AssetBundles serially. Projects with a large number of AssetBundles will spend a lot of time compressing them.
- 构建时间:LZMA和LZ4在压缩文件时非常慢,并且Unity编辑器串行处理AssetBundles。拥有大量AssetBundles的项目将花费大量时间压缩它们。
-
Application size: If the AssetBundles are shipped in the application, compressing them will reduce the application's total size. Alternatively, the AssetBundles can be downloaded post-install.
- 应用程序大小:如果资产包是在应用程序中提供的,压缩它们将减少应用程序的总大小。或者,资产包可以在安装后下载。
-
Memory usage: Prior to Unity 5.3, all of Unity's decompression mechanisms required the entire compressed AssetBundle to be loaded into memory prior to decompression. If memory usage is important, use either uncompressed or LZ4 compressed AssetBundles.
- 内存使用:在Unity 5.3之前,所有Unity的解压机制都要求在解压之前将整个压缩包加载到内存中。如果内存使用是重要的,使用未压缩或LZ4压缩AssetBundles。
-
Download time: Compression may only be necessary if the AssetBundles are large, or if users are in a bandwidth-constrained environment, such as downloading on low-speed or metered connections. If only a few tens of megabytes of data are being delivered to PCs on high-speed connections, it may be possible to omit compression.
- 下载时间:只有当资源包很大,或者用户在带宽受限的环境下,例如在低速或计量连接上下载时,压缩才有必要。在PC高速连接时,如果只有几十兆字节的数据被分发,它可能会省略压缩。
5.6.1. Crunch Compression
Bundles which consist primarily of DXT-compressed textures which use the Crunch compression algorithm should be built uncompressed.
主要由使用了Crunch压缩算法的dxt压缩纹理组成的Bundles在创建时不应被压缩。
5.7. AssetBundles and WebGL
All AssetBundle decompression and loading in a WebGL project must occur on the main thread, due to Unity’s WebGL export option not currently supporting worker threads. The downloading of AssetBundles is delegated to the browser using XMLHttpRequest. Once downloaded, compressed AssetBundles will be decompressed on Unity’s main thread, therefore stalling execution of the Unity content depending on the size of the bundle.
Unity recommends that developers prefer small asset bundles to avoid incurring performance issues. This approach will also be more memory efficient than using large asset bundles. Unity WebGL only supports LZ4-compressed and uncompressed asset bundles, however, it is possible to apply gzip/brotli compression on the bundles generated by Unity. In that case you will need to configure your web server accordingly so that the files are decompressed on download by the browser. See here for more details.
If you are using Unity 5.5 or older, consider avoiding LZMA for your AssetBundles and compress using LZ4 instead, which is decompressed very efficiently on-demand. Unity 5.6 removes LZMA as a compression option for the WebGL platform.