Unity AssetBundle

2022-09-12 12:01:04 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

#AssetBundle作用原理 把资源导出成一种叫做AssetBundle的文件,然后打包后可以在Unity程序运行的时候再加载回来用。 AssetBundle是采取某一种压缩方式压缩成的资源文件。节省存储空间,控制游戏包的大小,实现游戏的热更新。

AssetBundle文件分类

AssetBundle文件可以分为两类:序列化文件(serialized file)和资源文件(resource files)。 serialized file:资源被打碎放在一个对象中,最后统一被写进一个单独的文件(只有一个)。 resource files:某些二进制资源(图片、声音)被单独保存,方便快速加载。

AssetBundle分组策略

Ab分组策略:

  1. 逻辑实体分组 1)一个UI界面 或 所有UI界面一个包(界面里的贴图和布局信息一个包) 2)一个角色 或 所有角色一个包(这个角色里面的模型和动画一个包) 3)所有的场景所共享的部分一个包(包括贴图和模型)
  2. 按照类型分组 所有声音资源打成一个包,所有shader打成一个包,所有模型打成一个包,所有材质打成一个包
  3. 按照使用分组 1)把在某一时间需要同时加载的所有资源打成一个包。可以按照关卡或场景划分,一个关卡或场景所需要的所有资源包括角色、贴图、声音等打成一个包 2)把被其他包所共享的资源放在一个单独的包里面 3)把经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
  4. 版本更新分组 如果对于一个同一个资源有两个版本,可以考虑通过后缀来区分,如v1/v2/v3.unity3d

BuileAssetBundles

AssetBundle使用相关API:BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64); 结合Unity Editor使用如下:

代码语言:javascript复制
#if UNITY_EDITOR
using UnityEditor;
using System.IO;

public class BulidAssetBundle
{
    //编辑器扩展,在菜单栏Assets下生成Build AssetBundles菜单
    [MenuItem("Assets/Build AssetBundles")]

    //进行资源打包
    static void BuildAllAssetBundles()
    {
       
        string dir = "AssetBundle";   //使用相对路径保存,文件夹AssetBundle在为工程目录下
        if (Directory.Exists(dir) == false)  //打包之前要保证文件夹存在,不存在的话会报错
        {
            Directory.CreateDirectory(dir);
        }

        //BuildPipeline是UnityEditor中用于打包的类,其中的BuildAssetBundle用于AssetBundle打包
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows64);  
        //dir:存储的路径
        //BuildAssetBundleOptions:表示压缩式的算法
        //BuildTarget: 表示用于什么平台
    }

    /*关于BuildAssetBundleOptions的算法选择*/
    //BuildAssetBundleOptions.None:LZMA压缩,压缩包小,但加载时间长。下载之前需要整体解压。下载完成后,包会使用LZ4重新压缩保存于本地,从而在使用资源的时候不需要整体解压。
    //BuildAssetBundleOptions.ChunkBasedCompression:LZ4压缩,压缩率没LZMA高,但可以加载指定资源而不用全解压。一般使用LZ4压缩,占存小,却几乎可以跟不压缩的加载速度相媲美。
    //BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包占存大,加载快

}
#endif

LoadAssetBundles

Ab包的加载主要有四种方式:

  1. AssetBundle.LoadFromMemory和AssetBundle.LoadFromMemoryAsync //内存加载
  2. AssetBundle.LoadFromFile和AssetBundle.LoadFromFileAsync //本地文件加载
  3. WWW.LoadFromCacheOrDownload //服务器和本地都可加载,不推荐
  4. UnityWebRequest //服务器加载
代码语言:javascript复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking; //UnityWebRequest

public class LoadAssetBundle : MonoBehaviour
{
    IEnumerator Load()  //定义一个加载ab包的协程
    {
        string path = @"AssetBundles/xxx.unity3d";
        string url = @"http://localhost/AssetBundles/xxx.unity3d"; 


        /*第一种加载AB的方式,同步内存加载,AssetBundle.LoadFromMemory*/
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //直接加载AB包
        AssetBundle ab = AssetBundle.LoadFromMemory(File.ReadAllBytes(path)); //同步加载二进制文件
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }

        /*第一种加载AB的方式,异步内存加载,AssetBundle.LoadFromMemoryAsync*/
        //间接获取请求
        AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); //异步加载二进制文件
        //等待请求完成
        yield return request; 
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //获取AB对象
        AssetBundle ab = request.assetBundle;
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }


        /*第二种加载AB的方式,同步本地文件加载,AssetBundle.LoadFromFile*/
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //直接加载AB包
        AssetBundle ab = AssetBundle.LoadFromFile(path); //同步加载本地文件
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }
       
        /*第二种加载AB的方式,异步本地文件加载,AssetBundle.LoadFromFileAsync*/
        //间接获取请求
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path); //异步加载本地文件
        //等待请求完成
        yield return request; 
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //获取AB对象
        AssetBundle ab = request.assetBundle;
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }
       

        /*第三种加载AB的方式,服务器和本地都可加载,WWW.LoadFromCacheOrDownload*/
        while (Caching.ready == false)
            yield return null;
        //获取www请求
        WWW www = WWW.LoadFromCacheOrDownload(url, 1);
        yield return www;
        if( !string.IsNullOrEmpty(www.error) )
        {
            Debug.Log(www.error);
         yield break;
        }
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //直接加载AB包
        AssetBundle ab = www.assetBundle; 
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }
       

        /*第四种加载AB的方式,服务器加载 ,UnityWebRequest*/
        //获取服务器请求
        UnityWebRequest request = UnityWebRequest.GetAssetBundle(url);
        //等待请求发送完
        yield return request.SendWebRequest();
        //卸载加载缓存数据,如果有某个系统来管理加载好的数据就不需要
        AssetBundle.UnloadAllAssetBundles(true);
        //从DownloadHandlerAssetBundle获取request,得到AB对象
        AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
        AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
        //加载资源对象
        //Object obj = AssetBundle.LoadAsset<T>(assetName);
        //Object[] objArray = AssetBundle.LoadAllAssets();
        GameObject obj = ab.LoadAsset<GameObject>("assetname");
        GameObject[] objs= ab.LoadAllAssets();
        //实例化对象
        Instantiate(obj); 
        foreach(Object o in objs)
        {
            Instantiate(o);
        }

    } //协程

}

DealDependent by Manifests

通过加载Manifests文件可以处理资源的依赖。

代码语言:javascript复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking; //UnityWebRequest

public class Manifest : MonoBehaviour
{
    IEnumerator Manifests()  //定义一个处理资源依赖的协程
    {
        AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); //加载Ab资源
        AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); //通过加载到的Ab资源对象获得Manifest资源
        
        string[] assets  = manifest.GetAllAssetBundles() //获取Manifests中的所有AssetBundle的名字
        //传递所有Manifest中所有AssetBundle的包的名称,通过迭代器加载所有包
        foreach(string asset in assets)
        {
            AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, asset) ); //Path.Combine(path1, path2)作用是合并两个路径字符串
        }
        
        string[] dependencies = manifest.GetAllDependencies("assetBundle"); //通过获得的Manifest资源对象获得assetBundle包的所有依赖包的名字
        //传递当前包需要依赖包的名称,通过迭代器加载依赖的所有包
        foreach(string dependency in dependencies)
        {
            AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency) ); //Path.Combine(path1, path2)作用是合并两个路径字符串
        }
    }
}

UnloadAssetBundles

卸载AssetBundle主要是为了减少内存占用,但有可能导致依赖丢失。

  1. AssetBundle.Unload(true):卸载所有资源,即使有资源被使用着,并且卸载内存中已加载的资源。 PS:一般在关卡切换、场景切换时候调用。
  2. AssetBundle.Unload(false):卸载所有没用被使用的资源,但会保留内存中已加载的资源。如果重新创建该方法卸载的对象,内存会存相同的资源,造成内存泄漏。

关于资源的其他卸载方式: 场景对象(GameObject):这类物件可通过Destroy函数进行销毁;

资源对象(Resources),除了Prefab以外,资源文件还可以通过两种方式来卸载: 1)通过Resources.UnloadAsset卸载指定的资源,CPU开销小; 2)通过Resources.UnloadUnusedAssets一次性卸载所有未被引用的资源,CPU开销大;需要注意被静态变量引用的资源,调用该方法并不会被卸载,在Profiler中能够看到其引用情况。

WWW对象:调用对象的Dispose函数或将其置为null即可; WebStream:在卸载WWW对象以及对应的AssetBundle对象后,这部分内存即会被引擎自动卸载; SerializedFile:卸载AssetBundle后,这部分内存会被引擎自动卸载;

代码语言:javascript复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking; //UnityWebRequest

public class UnloadAssetBundle : MonoBehaviour
{
    IEnumerator Unload()  //定义一个卸载ab包的协程
    {
        //此处根据加载ab包的方式进行相应的卸载
    }

}

文件校验(CRC、MD5、SHA1)

CRC、MD5、SHA1都是通过对数据进行计算,来生成一个校验值,该校验值用来校验数据的完整性。 Unity在加载Ab包之后,会通过计算生成一个校验值,去和传递过来的校验值进行比对,从而判断数据是否完整。 三种校验算法的不同点:

  1. 算法不同。CRC采用多项式除法,MD5和SHA1使用的是替换、轮转等方法;
  2. 校验值的长度不同。CRC校验位的长度跟其多项式有关系,一般为16位或32位,MD5是16个字节(128位),SHA1是20个字节(160位);
  3. 校验值的称呼不同。CRC一般叫做CRC值;MD5和SHA1一般叫做哈希值或散列值(Hash);
  4. 安全性不同。这里的安全性是指检错的能力,即数据的错误能通过校验位检测出来。CRC的安全性跟多项式有很大关系,相对于MD5和SHA1要弱很多;MD5的安全性很高,SHA1的安全性最高。
  5. 效率不同,CRC的计算效率很高;MD5和SHA1比较慢。
  6. 用途不同。CRC一般用作通信数据的校验;MD5和SHA1用于安全(Security)领域,比如文件校验、数字签名等。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/149607.html原文链接:https://javaforall.cn

0 人点赞