Unity7.AssetBundle官方文档
参考:http://www.manew.com/thread-94644-1-1.html
AssetBundles官方文档:
https://docs.unity3d.com/Manual/AssetBundlesIntro.html
从HTTP服务器下载AssetBundle(GET):
https://docs.unity3d.com/2018.1/Documentation/Manual/UnityWebRequest-DownloadingAssetBundle.html
AssetBundle 资源加载-IOS流程:http://forum.china.unity3d.com/thread-22824-1-1.html
目录
AssetBundle.LoadFromMemoryAsync
WWW.LoadFromCacheOrDownload(弃用)
AssetBundleManager.Initialize()
概览
一、什么是AssetBundle
估计很多人只知道Unity的模型之类的东西可以导出成一种叫做AssetBundle的文件,然后打包后可以在Unity程序运行的时候再加载回来用。
那么AssetBundle是一个什么样的东西呢?其实AssetBundle只是一种使用LZMA压缩方式压缩的资源文件。
AssetBundle打包的时候,你可以指定一个mainAsset,那么加载完之后就可以通过AssetBundle.mainAsset来获取到了。你也可以不指定mainAsset,直接打包一堆内容进去,然后加载后通过AssetBundle.LoadAsset指定名字的读取出来。
AssetBundle的链式结构和增量打包
一个小的部分改变了,它将会改变的地方只有总的AssetBundle.manifest,还有直接依赖于它本身的manifest。其他不依赖的部分是不需要重新打包的。
manifest里面可以用本地的绝对路径,那是针对你本地打包时用的,和加载无关。
三、导出AssetBundle和自动设置名称
刚才我们都是直接的输入AssetBundleName来导出AssetBundle的,其实这一步可以使用代码自动完成
在Unity项目内部,每一个小的资源都会有一个唯一的哈希Id。我们可以通过AssetDatabase.AssetPathToGUID来获得这个ID。
那么自动设置就变得简单了,通过代码,我们可以设置一个总的prefab的AssetBundleName,然后自动获得它身上的所有依赖,然后获得每个依赖资源的唯一Id,再赋予AssetBundleName就行了
四、加载AssetBundle的步骤
资源的依赖关系组装是unity本身会自动完成的。比如一个资源A,它是依赖于资源B和资源C的。当资源A加载进来之后,发现内存里面已经有资源B和资源C了,它会自动的组装起来。
加载的步骤:
1、获得总的依赖配置
刚才已经说明了,真的有用的依赖配置文件是没有后缀名称的AssetBundle文件,所以我们需要加载的就是这个文件了。
string mUrl = Cdn + "AssetBundle";
然后www加载。
Cdn就是你的资源服务器路径,“AssetBundle”就是文件名,它没有后缀。
加载后,通过AssetBundle.LoadAsset("AssetBundleManifest"),就可以把刚才那个没有后缀名的文件转成AssetBundleManifest对象mainfest。
2、根据名称找到目标加载资源的所有依赖
获得了AssetBundleManifest对象mainfest之后,比如我们实际上是需要加载obj1.ab的,这在刚才的AssetBundle.manifest里面可以知道,它的Info里面就有obj1.ab。然后我们通过
string[] dps = mainfest.GetAllDependencies("obj1.ab");
就可以获取到obj1.ab的所有依赖了,包括了子依赖
3、加载所有依赖的资源
获取到obj1.ab的所有依赖之后,就应该逐个的去加载他们了。分别www加载他们,然后保存他们的AssetBundle。
4、加载目标加载资源
当加载了所有的依赖资源之后,就可以光明正大 的去加载目标资源了,这里我们的目标资源就是obj1.ab。
5、实例化显示
obj1.ab加载完之后,你爱怎样用都可以,直接实例化出来吧。
由于AssetBundle是不能重复加载的,如果你需要多次加载一个资源,你有2个选择,要么加载了就Unload(false)卸载了它。要么你可以把它存起来,当需要相同名字的AssetBundle的时候,直接取出来。
五、建议
1、Unity5的新版AssetBundle好像是一套全新的系统,其实和旧系统的差别并没有很大,只是自动生成了依赖配置文件而已。这一个步骤实际上完全可以自己实现的,那些配置文件可以用自己喜欢的格式生成,然后加载的时候再自己想办法把依赖关系找回来就行了。
2、把AssetBundle拆得太碎并不是一件好事情。如果加载一个模型,需要分别加载十几次依赖资源,才能显示,这个过程中发送这么多的www或者http请求,过程有点危险。至于说冗余文件的问题,自己考虑一下分布策略吧。
2、 一般来说,这种东西都需要配合着一套资源管理的系统来用的
官方文档
AssetBundles
一个AssetBundle是包含特定于平台的档案文件的资产(模型,纹理,预制,音频剪辑,甚至整个场景)可以在运行时加载。AssetBundles可以表达彼此之间的依赖关系; 例如,AssetBundle A中的材料可以引用AssetBundle B中的纹理。为了通过网络进行有效传递,可以根据用例要求(LZMA和LZ4)使用内置算法选择压缩AssetBundle。
AssetBundles对于可下载内容(DLC)非常有用,可减少初始安装大小,加载针对最终用户平台优化的资产,并降低运行时内存压力。
AssetBundle中有什么?
好问题,实际上“AssetBundle”可以指两种不同但相关的东西。
首先是磁盘上的实际文件。我们称之为AssetBundle存档,或者在本文档中简称存档。归档文件可以被视为一个容器,就像文件夹一样,可以容纳其中的其他文件。
这些附加文件包含两种类型; 序列化文件和资源文件。
序列化文件包含分解为各个对象的资产,并写入此单个文件。
资源文件只是为某些资产(纹理和音频)单独存储的二进制数据块,以允许我们有效地从另一个线程上的磁盘加载它们。
第二个是您通过代码与特定存档加载资产进行交互的实际AssetBundle对象。此对象包含您添加到此存档的资产的所有文件路径的映射,以及在您请求时需要加载的属于该资产的对象。
AssetBundle工作流程
要开始使用AssetBundles,请按照以下步骤操作。有关每个工作流程的更多详细信息,请参阅本文档的其他页面。
将资产分配给AssetBundles
分配给定资产 AssetBundle,请按照下列步骤操作:
- 从项目视图中选择要分配给捆绑包的资产
- 检查检查器中的对象
- 在检查器的底部,您应该看到一个分配AssetBundles和Variants的部分:
- 左侧下拉分配AssetBundle,而右侧下拉分配变量
- 单击左侧下拉列表,显示“无”,以显示当前已注册的AssetBundle名称
- 单击“新建...”以创建新的AssetBundle
- 输入所需的AssetBundle名称。请注意,AssetBundle名称确实支持一种文件夹结构,具体取决于您键入的内容。要添加子文件夹,请用“/”分隔文件夹名称。例如:AssetBundle名称“environment / forest”将在环境子文件夹下创建名为forest的包
- 一旦选择或创建了AssetBundle名称,您可以重复此过程以右手下拉以分配或创建变体名称(如果需要)。构建AssetBundle不需要变体名称
要阅读有关AssetBundle分配和相关策略的更多信息,请参阅有关为AssetBundle准备资产的文档 。
构建AssetBundles
在Assets文件夹中创建一个名为Editor的文件夹,并将包含以下内容的脚本放在该文件夹中:
using UnityEditor; using System.IO;
public class CreateAssetBundles { [MenuItem("Assets/Build AssetBundles")] static void BuildAllAssetBundles() { string assetBundleDirectory = "Assets/AssetBundles"; if(!Directory.Exists(assetBundleDirectory)) { Directory.CreateDirectory(assetBundleDirectory); } BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows); } } |
此脚本将在Assets菜单底部创建一个名为“Build AssetBundles”的菜单项,该菜单项将执行与该标记关联的函数中的代码。单击“构建AssetBundles”时,将显示一个带有构建对话框的进度条。这将获取您使用AssetBundle名称标记的所有资产,并将它们放在assetBundleDirectory定义的路径中的文件夹中。
有关此代码正在执行的操作的更多详细信息,请参阅有关构建AssetBundles的文档。
将AssetBundles上传到场外存储
此步骤对每个用户都是唯一的,而不是Unity可以告诉您如何操作的步骤。如果您打算将AssetBundles上传到第三方托管网站,请在此处执行此操作。如果您正在进行严格的本地开发并打算将所有AssetBundle都放在磁盘上,请跳到下一步。
加载AssetBundles和Assets
对于打算从本地存储加载的用户,您将对AssetBundles.LoadFromFile API感兴趣。看起来像这样:
public class LoadFromFileExample extends MonoBehaviour { function Start() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle")); if (myLoadedAssetBundle == null) { Debug.Log("Failed to load AssetBundle!"); return; } var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject"); Instantiate(prefab); } } |
LoadFromFile 获取bundle文件的路径。
如果您自己托管AssetBundles并需要将它们下载到游戏中,您将对UnityWebRequest API感兴趣。这是一个例子:
IEnumerator InstantiateObject()
{ string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; UnityEngine.__Networking__.UnityWebRequest request = UnityEngine.__Networking__.UnityWebRequest.GetAssetBundle(uri, 0); yield return request.Send(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); GameObject cube = bundle.LoadAsset<GameObject>("Cube"); GameObject sprite = bundle.LoadAsset<GameObject>("Sprite"); Instantiate(cube); Instantiate(sprite); }
|
GetAssetBundle(string, int)获取AssetBundle的位置以及要下载的包的版本。在这个例子中,我们仍然指向一个本地文件,但字符串uri可以指向您在AssetBundles上托管的任何URL。
UnityWebRequest有一个特定的句柄来处理AssetBundles:即DownloadHandlerAssetBundle,它从请求中获取AssetBundle。
无论您使用哪种方法,您现在都可以访问AssetBundle对象。从该对象开始,您需要使用LoadAsset<T>(string)将T您尝试加载的资产的类型,以及对象的名称作为包内的字符串。这将返回您从AssetBundle加载的任何对象。您可以像使用Unity中的任何对象一样使用这些返回的对象。例如,如果要在场景中创建GameObject,则只需要调用Instantiate(gameObjectFromAssetBundle)。
有关加载AssetBundle的API的更多信息,请参阅本机使用AssetBundles的文档。
为AssetBundles准备资产
使用AssetBundles时,您可以自由分配任何资产到您想要的任何捆绑。但是,在设置捆绑包时需要考虑某些策略。这些分组策略旨在用于您认为适合您的特定项目。您可以根据需要随意混合和匹配这些策略。
逻辑实体分组
逻辑实体分组是指资产根据其所代表的项目的功能部分分配给AssetBundles的位置。这包括诸如用户界面,字符,环境以及在应用程序的整个生命周期中可能经常出现的任何其他内容。
例子
捆绑用户界面屏幕的所有纹理和布局数据
捆绑一个字符/字符集的所有模型和动画
将多个级别共享的场景片段的纹理和模型捆绑在一起
逻辑实体分组是可下载内容(DLC)的理想选择,因为通过这种方式将所有内容分开,您可以对单个实体进行更改,而无需下载其他未更改的资产。
能够正确实施此策略的最大诀窍是,开发人员将资产分配给各自的捆绑包必须准确熟悉项目将使用每个资产的时间和地点。
键入分组
对于此策略,您可以将类似类型的资产(例如音频轨道或语言本地化文件)分配给单个AssetBundle。
类型分组是构建AssetBundles以供多个平台使用的更好策略之一。例如,如果您的音频压缩是设置在Windows和Mac平台之间是相同的,您可以将所有音频数据自行打包到AssetBundle中并重用这些捆绑包,而着色器倾向于使用更多特定于平台的选项进行编译,因此着色器包中为mac构建的内容可能无法在Windows上重复使用。此外,这种方法非常适合使您的AssetBundles与更多统一播放器版本兼容,如纹理压缩格式和设置变化的频率低于代码脚本或预制件。
并发内容分组
并发内容分组是指您将资产捆绑在一起,同时加载和使用。您可以将这些类型的捆绑包视为用于基于关卡的游戏,其中每个级别包含完全独特的字符,纹理,音乐等。您可能希望绝对确定其中一个AssetBundles中的资产仅用于同时,将使用该捆绑中的其余资产。依赖于Concurrent Content Grouping bundle中的单个资产会导致显着增加的加载时间。您将被迫下载该单个资产的整个捆绑包。
并发内容分组捆绑包最常见的用例是基于场景的捆绑包。在此分配策略中,每个场景包应包含大部分或全部场景依赖项。
请注意,项目绝对可以并且应该根据您的需求混合使用这些策略。对任何给定方案使用最优资产分配策略可以大大提高任何项目的效率。
例如,项目可能决定将不同平台的用户界面(UI)元素分组到他们自己的Platform-UI特定包中,但按级别/场景对其交互式内容进行分组。
无论您遵循何种策略,这里都有一些额外的提示,这些提示可以全面记住:
- 将频繁更新的对象拆分为AssetBundle,与很少更改的对象分开
- 对可能同时加载的对象进行分组。例如模型,纹理和动画
- 如果您注意到多个AssetBundle中的多个对象依赖于来自完全不同的AssetBundle的单个资产,请将依赖项移动到单独的AssetBundle。如果多个AssetBundle引用其他AssetBundle中的同一组资产,则可能值得将这些依赖项拉入共享AssetBundle以减少重复。
- 如果不可能同时加载两组对象,例如标准和高清属性,请确保它们位于自己的AssetBundle中。
- 如果同时经常加载50%的捆绑包,请考虑拆分AssetBundle
- 考虑组合小型(少于5到10个资产)但其内容经常同时加载的AssetBundle
- 如果一组对象只是同一对象的不同版本,请考虑AssetBundle Variants
构建AssetBundles
在AssetBundle工作流程的文档中,我们有一个代码示例,它将三个参数传递给BuildPipeline.BuildAssetBundles函数。让我们更深入地了解我们实际说的话。
Assets / AssetBundles:这是AssetBundles输出的目录。您可以将其更改为您想要的任何输出目录,只需确保在尝试构建之前文件夹实际存在。
BuildAssetBundleOptions
BuildAssetBundleOptions您可以指定几种具有各种效果的不同。有关所有选项的表,请参阅BuildAssetBundleOptions上的脚本API参考。
虽然您可以BuildAssetBundleOptions根据需求的变化自由组合,但有三种特定的方法BuildAssetBundleOptions可以处理AssetBundle 压缩:
- BuildAssetBundleOptions.None:此捆绑选项使用LZMA格式压缩,这是一个压缩的LZMA序列化数据文件流。LZMA压缩要求在使用之前对整个包进行解压缩。这导致文件大小尽可能小,但由于解压缩,加载时间略长。值得注意的是,在使用此BuildAssetBundleOptions时,为了使用捆绑包中的任何资产,最初必须对整个捆绑包进行解压缩。
解压缩捆绑后,它将使用LZ4压缩在磁盘上重新压缩,这不需要在使用捆绑包中的资产之前解压缩整个捆绑包。最好在捆绑包含资产时使用,以便从捆绑中使用一个资产意味着将加载所有资产。打包角色或场景的所有资产是可能使用此功能的捆绑包的一些示例。
由于文件较小,建议仅使用LZMA压缩从异地主机初始下载AssetBundle。通过UnityWebRequestAssetBundle加载的LZMA压缩资产包会自动重新压缩为LZ4压缩并缓存在本地文件系统上。如果您通过其他方式下载并存储捆绑包,则可以使用以下方式重新压缩它AssetBundle.RecompressAssetBundleAsync API。
- BuildAssetBundleOptions.UncompressedAssetBundle:此捆绑选项以使数据完全未压缩的方式构建捆绑包。未压缩的缺点是文件下载量越大。但是,下载后的加载时间会快得多。
- BuildAssetBundleOptions.ChunkBasedCompression:此捆绑选项使用称为LZ4的压缩方法,这导致比LZMA更大的压缩文件大小,但不像LZMA那样需要解压缩整个捆绑包,然后才能使用它。LZ4使用基于块的算法,允许将AssetBundle加载成片或“块”(loaded in pieces or “chunks.”)。解压缩单个块允许使用包含的资产,即使AssetBundle的其他块未解压缩也是如此。
使用ChunkBasedCompression具有与未压缩束相当的加载时间,并且具有减小磁盘大小的额外好处。
BuildTarget
BuildTarget.Standalone:这里我们告诉构建管道我们将使用这些AssetBundles的目标平台。您可以在BuildTarget的Scripting API Reference中找到可用的显式构建目标列表。但是,如果您不想在构建目标中进行硬编码,请随意利用EditorUserBuildSettings.activeBuildTarget哪个将自动找到您当前为其构建的平台,并根据该目标构建AssetBundle。
一旦你正确设置了构建脚本,就终于可以构建你的bundle了。如果您按照上面的脚本示例进行操作,请单击“ 资产” > 构建AssetBundles以启动流程。
现在您已经成功构建了AssetBundle,您可能会注意到您的AssetBundles目录包含的文件数量超出了原先的预期。准确地说,2 *(n + 1)个文件。让我们花点时间BuildPipeline.BuildAssetBundles仔细研究收益率。
对于您在编辑器中指定的每个AssetBundle,您会注意到一个文件包含您的AssetBundle名称和您的AssetBundle名称+“.manifest”。
将会有一个额外的包和清单,它与您创建的任何AssetBundle不共享名称。相反,它以它所在的目录(构建AssetBundles的目录)命名。这是Manifest Bundle。我们将在下面讨论更多关于此以及如何使用它的内容。
AssetBundle文件
这是没有.manifest扩展名的文件,以及您在运行时加载以加载资产的内容。
AssetBundle文件是一个内部包含多个文件的存档。此归档的结构可能会略有变化,具体取决于它是AssetBundle还是Scene AssetBundle。这是普通AssetBundle的结构:
Scene AssetBundle与普通的AssetBundle不同,它是针对Scene及其内容的流加载进行了优化的。
清单文件
对于生成的每个捆绑包(包括附加的Manifest Bundle),都会生成关联的清单文件。清单文件可以使用任何文本编辑器打开,并包含诸如循环冗余校验(CRC)数据和捆绑包的依赖性数据之类的信息。对于普通的AssetBundles,它们的清单文件将如下所示:
ManifestFileVersion: 0 CRC: 2422268106 Hashes: AssetFileHash: serializedVersion: 2 Hash: 8b6db55a2344f068cf8a9be0a662ba15 TypeTreeHash: serializedVersion: 2 Hash: 37ad974993dbaa77485dd2a0c38f347a HashAppended: 0 ClassTypes: - Class: 91 Script: {instanceID: 0} Assets: Asset_0: Assets/Mecanim/StateMachine.controller Dependencies: {} |
其中显示了包含的资产,依赖关系和其他信息。
生成的Manifest Bundle将有一个清单,但它看起来更像是这样:
ManifestFileVersion: 0 AssetBundleManifest: AssetBundleInfos: Info_0: Name: scene1assetbundle Dependencies: {} |
这将显示AssetBundles如何关联以及它们的依赖关系。现在,只需要了解这个bundle包含AssetBundleManifest对象,这对于确定在运行时加载哪些bundle依赖项非常有用。要了解有关如何使用此捆绑包和清单对象的更多信息,请参阅有关使用AssetBundles本地的文档。
AssetBundle依赖项
如果其中一个或多个UnityEngine.Objects包含对UnityEngine.Object另一个包中的位置的引用,则AssetBundles可以依赖于其他AssetBundle 。如果UnityEngine.Object包含对UnityEngine.Object任何AssetBundle中未包含的引用,则不会发生依赖关系。在这种情况下,在构建AssetBundle时,捆绑包依赖的对象的副本将复制到捆绑包中。如果多个包中的多个对象包含对未分配给包的同一对象的引用,则每个对该对象具有依赖性的包将创建其自己的对象副本并将其打包到构建的AssetBundle中。
如果AssetBundle包含依赖项,则在加载您尝试实例化的对象之前,必须加载包含这些依赖项的bundle。Unity不会尝试自动加载依赖项。
考虑以下示例,Bundle 1中的Material 引用了Bundle 2中的Texture :
在此示例中,在从Bundle 1加载Material之前,您需要将Bundle 2加载到内存中。不要紧,它需要你加载包1和包2,重要的是,包2,装载材料之前加载包1。在下一节中,我们将讨论如何使用AssetBundleManifest上一节中涉及的对象来确定和加载运行时的依赖关系。
本机使用AssetBundles
您可以使用四种不同的API来加载AssetBundle。它们的行为因捆绑软件的加载平台和压缩而异构建AssetBundle时使用的方法(未压缩,LZMA,LZ4)。
我们必须使用的四个API是:
- AssetBundle.LoadFromMemoryAsync
- AssetBundle.LoadFromFile
- WWW.LoadfromCacheOrDownload
- UnityWebRequest的DownloadHandlerAssetBundle(Unity 5.3或更高版本)
AssetBundle.LoadFromMemoryAsync
此函数采用包含AssetBundle数据的字节数组。您也可以根据需要传递CRC值。如果捆绑包是LZMA压缩的,它将在加载时解压缩AssetBundle。LZ4压缩束以其压缩状态加载。
以下是如何使用此方法的一个示例:
using UnityEngine; using System.Collections; using System.IO;
public class Example : MonoBehaviour { IEnumerator LoadFromMemoryAsync(string path) { AssetBundleCreateRequest createRequest = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); yield return createRequest; AssetBundle bundle = createRequest.assetBundle; var prefab = bundle.LoadAsset<GameObject>("MyObject"); Instantiate(prefab); } } |
但是,这不是使LoadFromMemoryAsync成为可能的唯一策略。File.ReadAllBytes(path)可以替换为获得字节数组的任何所需过程。
AssetBundle.LoadFromFile
从本地存储加载未压缩的包时,此API非常高效。如果捆绑包未压缩或压缩块(LZ4),LoadFromFile将直接从磁盘加载捆绑包。使用此方法加载完全压缩(LZMA)捆绑包将首先解压缩捆绑包,然后再将其加载到内存中。
如何使用的一个例子LoadFromFile:
public class LoadFromFileExample extends MonoBehaviour { function Start() { var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "myassetBundle")); if (myLoadedAssetBundle == null) { Debug.Log("Failed to load AssetBundle!"); return; } var prefab = myLoadedAssetBundle.LoadAsset.<GameObject>("MyObject"); Instantiate(prefab); } } |
注意:使用Unity 5.3或更早版本在Android设备上,尝试从Streaming Assets
加载AssetBundle时,此API将失败路径。这是因为该路径的内容将驻留在压缩的.jar文件中。Unity 5.4和更新版本可以将此API调用与Streaming Assets一起使用。
WWW.LoadFromCacheOrDownload(弃用)
被弃用(使用UnityWebRequest)
此API对于从远程服务器下载AssetBundle或加载本地AssetBundle非常有用。它是较旧且不太理想的UnityWebRequest API版本。
从远程位置加载AssetBundle将自动缓存AssetBundle。如果AssetBundle被压缩,工作线程将启动以解压缩捆绑包并将其写入缓存。一旦捆绑包被解压缩并缓存,它就会像AssetBundle.LoadFromFile一样加载。
如何使用的一个例子LoadFromCacheOrDownload:
using UnityEngine;
using System.Collections;
public class LoadFromCacheOrDownloadExample : MonoBehaviour
{
IEnumerator Start ()
{
while (!Caching.ready)
yield return null;
var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle", 5);
yield return www;
if(!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return;
}
var myLoadedAssetBundle = www.assetBundle;
var asset = myLoadedAssetBundle.mainAsset;
}
}
由于在WWW对象中缓存AssetBundle字节的内存开销,建议所有使用WWW.LoadFromCacheOrDownload的开发人员确保他们的AssetBundles保持较小 - 最多几兆字节。还建议在有限内存平台(如移动设备)上运行的开发人员确保其代码一次只下载一个AssetBundle,以避免内存峰值。
如果缓存文件夹没有任何用于缓存其他文件的空间,LoadFromCacheOrDownload将迭代地从缓存中删除最近最少使用的AssetBundle,直到有足够的空间来存储新的AssetBundle。如果无法创建空间(因为硬盘已满,或者当前正在使用缓存中的所有文件),LoadFromCacheOrDownload()将绕过缓存并将文件流式传输到内存中
为了强制LoadFromCacheOrDownload,需要更改版本参数(第二个参数)。如果传递给函数的版本与当前缓存的AssetBundle的版本匹配,则只会从缓存加载AssetBundle。
UnityWebRequest
UnityWebRequest有一个特定的API调用来处理AssetBundles。首先,您需要使用创建Web请求UnityWebRequest.GetAssetBundle。返回请求后,将请求对象传递给DownloadHandlerAssetBundle.GetContent(UnityWebRequest)。此GetContent调用将返回您的AssetBundle对象。
下载捆绑包后,您还可以assetBundle在DownloadHandlerAssetBundle类上使用该属性,以便以高效加载AssetBundle AssetBundle.LoadFromFile。
下面是如何加载一个包含两个的AssetBundle一个例子GameObjects 并实例化它们。要开始这个过程,我们只需要调用StartCoroutine(InstantiateObject());
IEnumerator InstantiateObject()
{ string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName; UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0); yield return request.Send(); AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request); GameObject cube = bundle.LoadAsset<GameObject>("Cube"); GameObject sprite = bundle.LoadAsset<GameObject>("Sprite"); Instantiate(cube); Instantiate(sprite); } |
使用UnityWebRequest的优点是它允许开发人员以更灵活的方式处理下载的数据,并可能消除不必要的内存使用。这是UnityEngine.WWW类中更新,更首选的API。
从AssetBundles加载资产
现在您已经成功下载了AssetBundle,现在是时候最终加载一些Assets了。
通用代码段:
T objectFromBundle = bundleObject.LoadAsset<T>(assetName); |
T是您尝试加载的资产的类型。
决定如何加载资产时有几个选项。我们有LoadAsset,LoadAllAssets和他们对应的异步LoadAssetAsync和LoadAllAssetsAsync。
这是如何同步从AssetBundles加载资产:
要加载单个GameObject:
GameObject gameObject = loadedAssetBundle.LoadAsset<GameObject>(assetName);
要加载所有资产:
Unity.Object[] objectArray = loadedAssetBundle.LoadAllAssets();
现在,在前面显示的方法返回您要加载的对象类型或对象数组的情况下,异步方法返回AssetBundleRequest。在访问资产之前,您需要等待此操作完成。要加载资产:
AssetBundleRequest request = loadedAssetBundleObject.LoadAssetAsync<GameObject>(assetName); yield return request; var loadedAsset = request.asset; |
和
AssetBundleRequest request = loadedAssetBundle.LoadAllAssetsAsync(); yield return request; var loadedAssets = request.allAssets; |
加载资产后,您就可以开始了!您可以像使用Unity中的任何对象一样使用加载的对象。
加载AssetBundle清单
加载AssetBundle清单可能非常有用。特别是在处理AssetBundle依赖时。
要获得可用的AssetBundleManifest对象,您需要加载另外的AssetBundle(与其所在的文件夹名称相同的那个)并从中加载AssetBundleManifest类型的对象。
加载清单本身与AssetBundle中的任何其他资产完全相同:
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); |
现在,您可以AssetBundleManifest通过上面示例中的清单对象访问API调用。从这里,您可以使用清单获取有关您构建的AssetBundle的信息。此信息包括AssetBundles的依赖关系数据,哈希数据和变体数据。
在前面的部分中我们讨论过AssetBundle Dependencies以及如果bundle对另一个bundle有依赖关系,那么在从原始bundle加载任何Assets之前,需要加载那些bundle吗?清单对象可以动态地查找加载依赖项。假设我们想要为名为“assetBundle”的AssetBundle加载所有依赖项。
AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); string[] dependencies = manifest.GetAllDependencies("assetBundle"); //Pass the name of the bundle you want the dependencies for. foreach(string dependency in dependencies) { AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency)); } |
现在您正在加载AssetBundles,AssetBundle依赖项和Assets,现在是时候讨论管理所有这些已加载的AssetBundle了。
管理已加载的AssetBundles
另请参阅:Unity Learn有关管理已加载AssetBundles的教程
从活动场景
中删除对象时,Unity不会自动卸载对象。资产清理在特定时间触发,也可以手动触发。
了解何时加载和卸载AssetBundle非常重要。不正确地卸载AssetBundle会导致在内存中复制对象或其他不良情况,例如缺少纹理。
关于AssetBundle管理最重要的事情是何时调用AssetBundle.Unload(bool); 如果你应该将true或false传递给函数调用。卸载是一个非静态函数,可以卸载您的AssetBundle。
此API卸载正在调用的AssetBundle的标头信息。参数指示是否还卸载从此AssetBundle实例化的所有对象。
AssetBundle.Unload(true)卸载从AssetBundle加载的所有GameObjects(及其依赖项)。这不包括复制的GameObjects(例如Instantiated GameObjects),因为它们不再属于AssetBundle。发生这种情况时,从该AssetBundle加载的纹理(并且仍然属于它)从场景中的GameObjects消失,Unity将它们视为缺少纹理。
假设材料M从AssetBundle AB加载,如下所示。
如果调用AB.Unload(true)。活动场景中的任何M实例也将被卸载并销毁。
如果您改为调用AB.Unload(false),它将破坏M和AB当前实例的链。
如果稍后再次加载AB并且调用AB.LoadAsset(),Unity不会将现有的M副本重新链接到新加载的材质。而是将加载两个M的副本。
一般来说,使用AssetBundle.Unload(false)
不会导致理想的情况。大多数项目应该AssetBundle.Unload(true)
用来防止在内存中复制对象。
大多数项目应该使用AssetBundle.Unload(true)
并采用一种方法来确保对象不会重复。两种常用方法是:
- 在应用程序的生命周期中,在卸载瞬态AssetBundle时具有明确定义的点,例如在级别之间或在加载屏幕期间。
- 维护单个对象的引用计数,并仅在所有组成对象未使用时卸载AssetBundle。这允许应用程序卸载和重新加载单个对象,而无需重复内存。
如果应用程序必须使用AssetBundle.Unload(false)
,则只能以两种方式卸载单个对象:
- 消除场景和代码中对不需要的Object的所有引用。完成此操作后,调用Resources.UnloadUnusedAssets。
- 非加性地加载场景。这将破坏当前场景中的所有对象并自动调用Resources.UnloadUnusedAssets。
如果您不想自己管理加载Asset Bundes,依赖项和资产,您可能会发现自己需要AssetBundle Manager。
AssetBundle Manager
AssetBundle Manager已弃用,资产商店中不再提供。您仍然可以从AssetBundleDemo Bitbucket存储库下载它。
AssetBundle Manager是Unity制作的一个工具,可以更精简地使用AssetBundles。
下载和导入AssetBundle Manager软件包不仅添加了一个新的API调用来加载和使用AssetBundles,还添加了一些编辑器功能来简化工作流程。此功能可在资产下找到菜单选项。
此新部分将包含以下选项:
模拟模式
启用模拟模式允许AssetBundle Manager使用AssetBundle,但不需要实际构建捆绑包本身。编辑器查看Assets已分配给AssetBundles并直接使用Assets,而不是实际从AssetBundle中提取它们。
使用模拟模式的主要优点是可以修改,更新,添加和删除资产,而无需每次都重新构建和部署AssetBundle。
值得注意的是,AssetBundle Variants不适用于模拟模式。如果您需要使用变体,可以使用Local AssetBundle Server。
本地AssetBundle服务器
AssetBundle Manager还可以启动本地AssetBundle服务器,该服务器可用于在编辑器或本地构建(包括Mobile)中测试AssetBundle。
使Local AssetBundle Server工作的规定是您必须在项目的根目录中创建一个名为AssetBundles的文件夹,该文件夹与Assets文件夹的级别相同。如:
创建文件夹后,您需要将AssetBundle构建到此文件夹。为此,请从新菜单选项中选择Build AssetBundles。这将为您构建它们到该目录。
现在您已经构建了AssetBundle(或已决定使用模拟模式),并准备开始加载AssetBundles。让我们看一下通过AssetBundle Manager可以使用的新API调用。
AssetBundleManager.Initialize()
此函数加载AssetBundleManifest对象。在使用AssetBundle Manager开始在Assets中加载之前,您需要调用它。在一个非常简单的示例中,初始化AssetBundle Manager可能如下所示:
IEnumerator Start()
{
yield return StartCoroutine(Initialize());
}
IEnumerator Initialize()
{
var request = AssetBundleManager.Initialize();
if (request != null)
yield return StartCoroutine(request);
}
该AssetBundle管理器使用该清单你的初始化()过程中加载,以帮助多项功能的背后幕后,包括依赖关系管理
加载资产
让我们直截了当。您正在使用AssetBundle Manager,您已初始化它,现在您已准备好加载一些资产。我们来看看如何加载AssetBundle并从该bundle实例化一个对象:
IEnumerator InstantiateGameObjectAsync (string assetBundleName, string assetName)
{
// Load asset from assetBundle.
AssetBundleLoadAssetOperation request = AssetBundleManager.LoadAssetAsync(assetBundleName, assetName, typeof(GameObject) );
if (request == null)
yield break;
yield return StartCoroutine(request);
// Get the asset.
GameObject prefab = request.GetAsset<GameObject> ();
if (prefab != null)
GameObject.Instantiate(prefab);
}
AssetBundle Manager异步执行所有加载操作,因此它返回一个加载操作请求,该请求在调用yield return StartCoroutine(request)时加载bundle。从那里我们需要做的就是调用GetAsset <T>()从AssetBundle加载游戏对象。
加载场景
如果你有一个AssetBundle名称分配给一个场景,你需要加载该场景,你需要遵循一个稍微不同的代码路径。图案相对相同,但略有不同。以下是如何从AssetBundle加载场景:
IEnumerator InitializeLevelAsync (string levelName, bool isAdditive)
{
// Load level from assetBundle.
AssetBundleLoadOperation request = AssetBundleManager.LoadLevelAsync(sceneAssetBundle, levelName, isAdditive);
if (request == null)
yield break;
yield return StartCoroutine(request);
}
如您所见,加载场景也是异步的,LoadLevelAsync返回一个加载操作请求,需要将其传递给StartCoroutine以加载场景。
加载变量
使用AssetBundle Manager加载变体实际上并不会更改在场景或资源中加载的代码。所有需要完成的工作都是设置AssetBundleManager的ActiveVariants属性。
ActiveVariants属性是一个字符串数组。只需构建一个字符串数组,其中包含您在将它们分配给Assets时创建的变体的名称。以下是如何使用hd变量加载场景AssetBundle。
IEnumerator InitializeLevelAsync (string levelName, bool isAdditive, string[] variants)
{
//Set the activeVariants.
AssetBundleManager.ActiveVariants = variants;
// Load level from assetBundle.
AssetBundleLoadOperation request = AssetBundleManager.LoadLevelAsync(variantSceneAssetBundle, levelName, isAdditive);
if (request == null)
yield break;
yield return StartCoroutine(request);
}
你传入你在代码中其他地方构建的字符串数组的地方(可能来自按钮点击或其他一些情况)。这将加载与设置的活动变体匹配的包(如果可用)。
修复AssetBundles
故障排除
Unity Asset Bundle浏览器工具
暂略,需要时再看