相关文章推荐
活泼的卤蛋  ·  Get-Process ...·  4 月前    · 
热心的大熊猫  ·  IBM Documentation·  1 年前    · 

一个AssetBundle中包含两部分:数据头和数据段。

通过AssetBundle加载游戏资源,分为三步:

  1. 加载AssetBundle对象。

  2. 通过AssetBundle对象加载需要的资源。

  3. 对于非引用类型的资源,还需通过GameObject.Instantiate()创建clone。

1. 加载AssetBundle对象方式

  • AssetBundle.LoadFromMemory(可选择异步模式)
  • AssetBundle.LoadFromFile(可选择异步模式)
  • AssetBundle.LoadFromStream(可选择异步模式)
  • UnityWebRequest的DownloadHandlerAssetBundle (异步模式)
  • WWW.LoadFromCacheOrDownload (Unity 5.6以及更低版本)(异步模式)
  • WWW(异步模式)

1.1 WWW.LoadFromCacheOrDownload(异步-缓存机制),2017以后被废弃。

Unity5.2之前推荐使用 WWW.LoadFromCacheOrDownload(),从2017开始被整合到UnityWebRequest中。以下适用于5.6以前版本。(异步加载,每次打包都需要更新资源版本,保存的路径无法更改,也没有接口去获取此路径)

  • 可以从远程服务器加载Object,也可以从本地存储加载Object。可以通过 file:// URL从本地存储加载文件。
  • 每次下载时候,都会创建一个线程,每个线程会在托管堆中缓存一份解压后的AssetBundle,这可能导致内存飙升。
  • 如果本地没有缓存,使用WWW网络下载文件。从网络下载的AssetBundle如果没有压缩,通过 固定大小的缓冲区 直接并写入本地缓存,如果有压缩还需要在固定大小的缓冲区中解压缩后,才能写入本地缓存。但是WWW对象将在 托管堆 中保留解压缩后的AssetBundle字节的完整副本。保留AssetBundle的额外副本以支持www.bytes等属性。一旦解压缩后的AssetBundle被缓存到本地,LoadFromCacheOrDownload将从本地缓存的解压缩AssetBundle加载数据头。
  • 如果本地已有缓存,调用LoadFromCacheOrDownload,和调用LoadFromFile一样,由于本地缓存中是未压缩的AssetBundle,因此只会加载AssetBundle数据头。
public class LoadFromCacheOrDownloadExample : MonoBehaviour
    IEnumerator Start()
        while (!Caching.ready)
            yield return null;
        using (var www =             
        WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle.unity3d", 5))
            yield return www;
            if (!string.IsNullOrEmpty(www.error))
                Debug.Log(www.error);
                yield return null;
            var myLoadedAssetBundle = www.assetBundle;
            var asset = myLoadedAssetBundle.mainAsset;


1.2 WWW (异步-无缓存机制)

WWW是一个Unity封装的网络下载模块,支持Http以及file两种URL协议。

直接使用LoadFromCacheOrDownload和WWW不同的是,读取不会再走WWW这一套 WWW进行读取或者下载时候,都会创建一个线程,托管堆中缓存一份下载后的AssetBundle,同时开启多个下载,这可能导致内存飙升。使用WWW.Dispose将终止仍在加载过程中的进程,并释放掉托管堆中的AssetBundle。 

通过www下载资源,然后释放托管堆内存。代码如下:

/// <summary>
///从服务器下载到本地
/// </summary>
/// <param name="AssetsHost">服务器路径</param>
/// <param name="AssetName">请求资源名称</param>
/// <param name="saveLocalPath">保存到本地路径,一般存在Application.persistentDataPath</param>
/// <returns></returns>
IEnumerator DownLoadAssetsWithDependencies2Local(string AssetsHost, string AssetName, string saveLocalPath)
    WWW tempWwwAsset = new WWW(AssetsHost + "/" + AssetName);
    //获取加载进度
    yield return tempWwwAsset;
    //直接读取AssetBundle
    AssetBundle temp = tempWwwAsset.assetBundle;
    //保存到本地
    Stream tempStream = null;
    FileInfo fileInfo = new FileInfo(saveLocalPath + "/" + AssetName);
    if (fileInfo.Exists)
        fileInfo.Delete();
    //如果此文件不存在则创建
    tempStream = fileInfo.Create();
    tempStream.Write(tempWwwAsset.bytes, 0, tempWwwAsset.bytes.Length);
    tempStream.Flush();
    //关闭流
    tempStream.Close();
    //销毁流
    tempStream.Dispose();
    Debug.Log(name + "成功保存到本地~");
  1. 如图所示,WWW对象和通过WWW.assetBundle属性所加载的AssetBundle对象会对Web Stream数据持有引用,同时AssetBundle也会引用到从它所加载的所有Assets。值得一提的是,真正的数据都是存放在Web Stream数据中,如纹理、模型等,而WWW和AssetBundle对象只是一个结构指向了Web Stream数据。
  2. 对于WWW对象本身,可以使用www = null或WWW.Dispose()来进行卸载。二者的效果一样,只是www = null不会及时引起内存释放操作,而是在系统自动垃圾回收时进行释放,WWW对象释放后,其对于Web Stream数据的引用计数也会相应减1。
  3. 对于Web Stream数据,其所占用的内存会在其引用计数为0时,自动被系统进行释放。如上图中,当AssetBundle对象以及WWW对象被释放后,Web Stream 数据所占用的内存也会被系统自动回收。

通过下载存储在内存中的WebStream,可以直接转化为Unity支持的资源格式:

Texture2D texture = WWW.texture;

string text = WWW.text;

byte[] bytes = WWW.bytes;

MovieTexture movie = WWW.movie; 

AssetBundle assetbundle = WWW.assetBundle; 

AudioClip audioClip = WWW.audioClip; 

1.3 LoadFromMemory(同步) LoadFromMemoryAsync(异步) , 一般只会拿来读取加密的AssetBundle

IEnumerator LoadAB(string _filePath)
    byte[] tempByteAry = File.ReadAllBytes(_filePath);
  AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(tempByteAry);
  yield return request;
  AssetBundle ab = request.assetBundle;
  1. 从托管堆中C#中的byte[])中加载AssetBundle。该方法总是将托管堆中的源数据复制到新分配的连续的非托管堆内存区块中。如果AssetBundle使用了LZMA压缩,在复制过程中AssetBundle会被解压;如果使用了LZ4压缩或者没有压缩,AssetBundle会被原样复制。
  2. 这个API占用内存的峰值至少是它所处理的AssetBundle大小的两倍:一份由此API创建在本机内存中的副本和一份传递给此API的位于托管堆字节数组中的副本。通过此API从AssetBundle加载的Asset会在内存中被复制3次:在托管堆的字节数组、本机内存中的副本以及在GPU或系统内存中的Asset本体。

1.4 LoadFromStream(本地-同步)  LoadFromStreamAsync(本地-异步)

        var fileStream = new FileStream(Application.streamingAssetsPath, FileMode.Open, 
        FileAccess.Read);
        var myLoadedAssetBundle = AssetBundle.LoadFromStream(fileStream);
        if (myLoadedAssetBundle == null)
            Debug.Log("Failed to load AssetBundle!");
            return;
        var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("MyObject");
        Instantiate(prefab);
        myLoadedAssetBundle.Unload(false);
  1. 可以通过文件流一点点读取,它会分次从文件里读取32KB放在非托管内存中,只到将文件全部放去非托管内存中,也就是说堆内存中它只会占用32KB。托管堆每次会读32KB放入非托管内存,只到全部读取完毕。
  2. 默认值32KB可以修改,比如修改为64KB。AssetBundle.LoadFromStream(fileStream,0,64)

1.5 UnityWebRequest(异步-提倡)

        var www = UnityWebRequest.Get("file://" +Application.streamingAssetsPath +                 
        "/10MB.unity3d");
        string downloadPath = Application.persistentDataPath + "/11MB.unity3d";
        www.downloadHandler = new DownloadHandlerFile(downloadPath);
        yield return www.SendWebRequest();
        Debug.Log("下载完成,加载assetbundle");
        var assetbundle = AssetBundle.LoadFromFile(downloadPath);
  1. UnityWebRequest绑定一个DownloadHandlerAssetBundle对象,DownloadHandlerAssetBundle可以使用一个线程(Worker Thread)通过固定大小的缓冲区( 8x64KB)把服务器下载数据存储到临时存储或者AssetBundle缓存中,具体的存储位置取决于DownloadHandler的配置。DownloadHandler不会保留所有下载的字节数据,进一步降低了下载AssetBundle的内存开销。
  2. 使用LZMA压缩的AssetBundle在下载时会被解压并使用LZ4压缩进行缓存。这一行为可以通过设置Caching.CompressionEnabled来修改。
  3. 下载完成后,可以通过DownloadHandler的assetBundle属性访问下载的AssetBundle,如同对下载的AssetBundle调用 AssetBundle.LoadFromFile 方法。
  4. 在Unity 5.6之前,UnityWebRequest系统使用一个固定的工作线程池和内部任务系统来防止重复、并发下载,线程池的大小不可配置。在Unity 5.6中,该保护措施被移除了,目的是适应现代硬件以及更快的访问HTTP响应代码和数据头。
  5. 下载时候固定大小的缓冲区( 8x64KB)托管堆内存,在非托管中生成未压缩或者LZ4格式的AssetBundle文件。(LZMA下载过程中会将LZMA解压后,重压缩为LZ4。)

allowSceneActivation = false,加载progress一直为0。原因(官方解释

1.6 LoadFromFile(本地-同步),LoadFromFileAsync(本地-异步)加载

// ---本地同步---
AssetBundle tempLogo = AssetBundle.LoadFromFile(AssetBundlesOutputPath + "/logo.unity3d");
// ---本地异步---
IEnumerator LoadAsyncCoroutine(string path, Action<AssetBundle> callback) {
    AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(path);
    yield return abcr;        
    callback(abcr.assetBundle);
  1. AssetBundle.LoadFromFile是用于从本地存储加载未压缩或使用LZ4压缩的AssetBundle的高效API。LZMA格式的将会先被解压,然后再压缩成LZ4格式,放到非托管堆。
  2. 在桌面系统(PC、Mac、Linux)、主机和移动平台上,这个API只会加载数据头,把其余的数据留在磁盘上。AssetBundle中的Object会在调用了加载方法(例如AssetBundle.Load)或者在它们的InstanceID被引用时按需加载。在这种情况下,不需要消耗额外的内存。在Unity编辑器中,这个API会将整个AssetBundle加载到内存中,就像这些字节被从磁盘中读出而且使用了 AssetBundle.LoadFromMemoryAsync 方法。如果在Unity编辑器中对项目进行分析,会发现在使用此方法加载AssetBundle时会出现内存使用高峰,在发布版本中这应该不会对设备性能造成影响。
  3. 提示:在Unity 5.3以及更早版本地Android工程中,使用此API从StreamAssets中加载AssetBundle会失败,这个问题已经在Unity 5.4中修复。更多详细内容,请查看本系列文章地第四篇中的分发-附加在项目中一节。

不同API加载方式,内存占用和效率对比:

加载方式

无压缩

块压缩(LZ4)

流压缩(LZMA)

WWW *

内存:未压缩的包的大小-非托管堆+(当WWW对象未释放时,未压缩的包的大小-托管堆内存)。

 

内存:LZ4压缩的包的大小-非托管堆+(当WWW对象未释放时,LZ4压缩的包的大小-托管堆内存)。

 

内存:LZ4压缩的包的大小-非托管堆+(当WWW对象未释放时,LZMA压缩的包的大小-托管堆内存)。

 

性能:没有额外的开销。性能:没有额外的开销。性能:LZMA解压+ 在下载过程中LZ4压缩。

LoadFromCacheOrDownload

(此行为本地加载时候,已缓存的都已解压缩,加载AssetBundle和LoadFromFile一样,只加载数据头)

内存:数据头。

 

内存:数据头。

 

内存:数据头。

 

性能:从磁盘读取。性能:从磁盘读取。性能:从磁盘读取。

LoadFromMemory(同步,异步)

内存:未压缩的包的大小-托管堆。+ (未压缩的包的大小-非托管堆内存)

内存:LZ4压缩的包的大小-托管堆。+ (LZ4未压缩的包的大小-非托管堆内存)

内存:LZ4压缩的包的大小-托管堆。

+ (LZ4未压缩的包的大小-非托管堆内存)

性能:没有额外的开销。性能:没有额外的开销。性能:LZMA解压+ LZ4压缩。

LoadFromFile(同步,异步)

内存:没有额外的开销。

 

内存:没有额外的开销。

 

内存:LZ4压缩的包的大小。

 

性能:从磁盘读取。性能:从磁盘读取。性能:从磁盘读取+ LZMA解压+ LZ4压缩。

WebRequest的(读取本地缓存时候和LoadFromFile一样)

内存:未压缩的包的大小-非托管堆。 

 

内存:LZ4HC压缩的包的大小-非托管堆。

内存:LZ4压缩的包的大小-非托管堆。

 

性能:没有额外开销[+是否从磁盘读取缓存]。性能:没有额外的开销[+是否从磁盘读取缓存]。性能:下载过程中LZMA压缩+ LZ4压缩[+是否从磁盘读取缓存]。

2. AssetBundle加载后,从已加载的AssetBundle中加载资源

AssetBundle tempAssetBundle = 通过上边各种方式加载的AssetBundle;
string[] tempAssetNames = tempAssetBundle.GetAllAssetNames();//此AssetBundle中包含的所有资源名称
tempAssetBundle.LoadAsset("");//通过资源名称从AssetBundle加载指定的资源
tempAssetBundle.LoadAllAssets();//加载当前资源包中所有的资源
tempAssetBundle.LoadAssetAsync("");//从资源包中异步加载资源

加载AssetBundle时候,此AssetBundle依赖其他AssetBundle,需要先加载其他AssetBundle。

    [MenuItem("AssetsBundle/Load")]
    static void LoadAssetbundle()
        LoadAssetBundleManifest();
        LoadBundle();
    //本地AssetBundle存储路劲 自己存哪读哪
    private static string m_rootFilePath = Application.dataPath + "/StreamingAssets/";
    private static AssetBundleManifest manifest = null;
    // 加载Manifest
    private static void LoadAssetBundleManifest()
        var bundle = AssetBundle.LoadFromFile(System.IO.Path.Combine(m_rootFilePath,"StreamingAssets"));
        manifest = bundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        // 压缩包释放掉
        bundle.Unload(false);
        bundle = null;
    //加载AssetBundle前,先加载AssetBundle依赖的AssetBundle
    private static void LoadBundle()
        string bundleName = "logo";
        // GetAllDependencies会返回直接和间接关联的AssetBundle 当前AssetBundle所依赖的所有AssetBundle
        // 加载依赖包没有顺序要求,
        string[] dependence = manifest.GetAllDependencies(bundleName);
        for (int i = 0; i < dependence.Length; ++i )
            //这地方同步加载,异步也可以(AssetBundle.LoadFromFileAsync)
            AssetBundle.LoadFromFile(System.IO.Path.Combine(m_rootFilePath, dependence[i]));
        //加载完所有依赖的AssetBundle,加载本身
        var bundle = AssetBundle.LoadFromFile(System.IO.Path.Combine(m_rootFilePath, bundleName));
        //这里不需要手动LoadAsset 依赖的AssetBundle中资源
        //LoadAsset(logo) 资源的时候自动加载与它关联的Asset
        GameObject tempGameObject = bundle.LoadAsset<GameObject>("logo");
        GameObject tempGameObject1 = Instantiate(tempGameObject) as GameObject;

        //这里不需要手动LoadAsset 依赖的AssetBundle中资源
        //LoadAsset(logo) 资源的时候自动加载与它关联的Asset

   AssetBundle内存卸载:

        var tempAb = AssetBundle.LoadFromFile(Application.streamingAssetsPath +     
        "/assetbundle.unity3d");
        var tempPrefab = tempAb.LoadAsset<GameObject>("GameObject");
        var tempGo = Instantiate<GameObject>(prefab);
        //tempAb.Unload(false);
        tempAb.Unload(true);

tempPrefab,tempGo,tempGo .Net 分配的内存,他们都指向非托管堆中的相应变量,如下图所示:

内存卸载:

方法卸载内存
tempAb.Unload(false)卸载assetbundle.unity3d
tempAb.Unload(true)

卸载assetbundle.unity3d 以及 prefab-mesh等

Resources.UnloadAsset(obj)传入参数为mesh,material,shader等对象,卸载对应的非托管堆资源对应的内存
Resources.UnloadUnusedAssets()会先调用一次GC,非托管堆没有托管堆引用变量引用的资源将被释放

tempAb,tempPrefab,tempGo 此处为临时变量,下次GC回收内存。

AssetBundle中可以实例化的类型:

Texture

设置read/write enabled后可以

AudioClip

不可以

Scene

可以

TextAsset

可以,但一般没有必要

Material

可以

Shader

可以

Mesh

可以

Animator

可以

Animation

可以

TerrainData

可以

GameObject

可以,Prefab就是GameObject,一般都是实例化prefab,实例化其他的没有必要。

https://blog.csdn.net/swj524152416/article/details/54022282 

https://docs.unity3d.com/Manual/UnityWebRequest.html

https://blog.csdn.net/itsxwz/article/details/83378045

https://learn.unity.com/tutorial/assets-resources-and-assetbundles#5c7f8528edbc2a002053b5a8

http://stalhandske.dk/UnityDocs/Manual/AssetBundleCompression.html

https://blog.uwa4d.com/archives/ABTheory.html

https://blog.csdn.net/lodypig/article/details/51879702

https://www.cnblogs.com/wgslucky/p/11523575.html

AssetBundle资源加载一个AssetBundle中包含两部分:数据头和数据段。通过AssetBundle加载游戏资源,分为三步: 加载AssetBundle对象。 通过AssetBundle对象加载需要的资源。 对于非引用类型的资源,还需通过GameObject.Instantiate()创建clone。1.加载AssetBundle对象方式Asset... Unity资源设置ab格式,编辑器工具栏打包,C#生成ab包的脚本 二、3种ab包加载方式 直接本地加载LoadFromFile。本地异步加载LoadFromFileAsync。 服务器异步加载UnityWebRequest。 三、补充说明及相关报错解决 UnityWebRequest获取不同资源数据使用方法会不同; 重新再次加载ab包,出现报错及其解决方法; 大部分是忘记卸载加载过的ab包,然后又再次加载
一、认识AssetsbundleUnity提供的AssetBundle,最早出现于2017版本。允许您通过Unity的WebRequest类下载资源,并在运行时实例化它们。 二、使用Assetsbundle 2.1 准备资源工作 新建Unity工程项目 工程命名:Assetsbundle(其他均可) 将必要的模型以预制件形式存储于资源文件夹内 选中对应预制件资源(可多选) Inspector窗口会出现关于预制件资源的可视窗口。在窗口的正下方即为Assetsbundle 直接就开门见山了,Unity提供了很多AB包的加载的接口。 AssetBundle.LoadFromFile :从本地加载 AssetBundle 资源包 AssetBundle.LoadFromFileAsync :异步从本地加载 AssetBundle 资源包 AssetBundle.LoadFromMemory :从缓存中下载 Asset...
先来看看AssetBundle类为我们提供的静态方法。 1.AssetBundle.LoadFromFile:从磁盘上同步加载一个AssetBundle资源。 就是本地加载咯?我们在前面几篇博文中使用的就是这一种加载方式,只要我们指定了正确的路径和文件名,相应的AssetBundle资源包就会被加载到我们的内存中等待使用。 2.Ass... 项目需要用到AssetBundle加载,根据官方API整理的几种加载方式。 一、使用AssetBundle.LoadFormFile,从本地磁盘上同步加载(这种加载方式是最快的) private void LoadFormFileExample() AssetBundle ab = AssetBundle.LoadFromFile(rootPath+"AB路径"); if (ab!=null) GameO
资源加载接口以及分析 AssetBundle.LoadFromFile (string path, uint crc, ulong offset); 同步方法 AssetBundle.LoadFromFileAsync (string path, uint crc, ulong offset); 异步方法 最快的ab包加载方式,内存占用低,不可自定义硬盘数据格式 优点: 加载效率高,内存占用低,加载时只加载文件头,只有在真正加载对应资源的时候才会加载进内存 缺点: 指定的ab包必须是未经自压缩与加密的
一、第一种加载方式本地相对路径资源加载 AssetBundle ab = AssetBundle.LoadFromFile("AssetBundle/sphere.unity3d"); // 本地加载相对路径加载 加载ab包 GameObject cube = ab.LoadAsset&lt;GameObject&gt;("Sphere"); // 获取AB包 Instantiate(cube);...
AssetBundle 引用计数是指在使用 AssetBundle 打包资源的时候,记录每个 AssetBundle 被使用的次数,以便在使用完后及时释放资源,从而避免内存泄漏。在使用 AssetBundle 加载资源时,会对加载的 AssetBundle 进行引用计数的增加,使用完后再进行引用计数的减少,当引用计数为 0 时,就可以释放该 AssetBundle 的资源。 AssetBundle 的打包和加载一般分为以下几个步骤: 1. 打包资源文件:使用 Unity Editor 自带的 AssetBundle 打包工具,将需要打包的资源文件进行打包,生成 AssetBundle 文件。 2. 加载 AssetBundle 文件:在游戏运行时,使用 Unity 提供的 AssetBundle.LoadFromFile 或 AssetBundle.LoadFromMemory 函数来加载 AssetBundle 文件。 3. 加载资源文件:使用加载的 AssetBundle,使用 AssetBundle.LoadAsset 或 AssetBundle.LoadAssetAsync 函数加载需要使用的资源文件。 4. 使用完成后,释放资源:使用 AssetBundle.Unload(false) 函数来释放 AssetBundle 中的资源,同时进行引用计数的减少。如果不再需要该 AssetBundle 中的任何资源,可以使用 AssetBundle.Unload(true) 函数来彻底释放该 AssetBundle,包括清除 AssetBundle 的缓存。 使用 AssetBundle 打包和加载资源可以有效地减少应用程序的内存占用,提高应用程序的性能。同时,在使用 AssetBundle 的时候,需要注意避免重复加载同一个 AssetBundle,以及及时释放不再使用的 AssetBundle 资源,避免内存泄漏。