unity资源加载

unity资源加载

打包的资源

  • Android
    所有资源打包在 unityLibrary\src\main\assets\bin\Data\data.unity3d
    • 场景存放在 level[n]
    • 共享资源存放在 sharedassets[n].asset
    • Resources文件夹下的资源存放在 resources.asset
  • iOS
    • 场景存放在 Data\level[n]
    • 共享资源存放在 sharedassets[0].asset
    • Resources文件夹下的资源存放在 resources.asset

Resources.Load

  • 参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/Scripting with Assets/在运行时加载资源

  • 目的
    在游戏运行时根据需要动态加载资源,注意资源还是在安装包中

  • 原理
    根据creator推测其原理是unity内部对每个资源分配uid,打包时是用uid替换文件路径,
    如果文件在Resources下则会把uid和原始文件路径的映射关系保存在一个配置文件中,
    加载时先把路径映射成uid,再用uid加载文件

  • 资源配置
    在Project下的任意文件夹中创建的Resources文件夹,其下的文件如果被场景引用,将打包进sharedassets[level].assets
    否则所有Resources文件夹下的资源将被打包成resources.assets,
    因此不同目录下的Resources文件夹下的资源最好有不同的路径,以免冲突

  • 资源加载
    在运行时用Resources.Load动态加载,要用相对Resources的路径,且不带扩展名
    如:Assets/Resources/a/glass.png或Assets/MyResource/Resources/a/glass.png
    在运行时都是用Resources.Load(“a/glass”,typeof(Texture2D) as Texture2D来加载

  • 资源卸载
    Resources.UnloadAsset不会卸载资源,要用UnloadUnusedAssets

  • 查找脚本
    Resources.FindObjectsOfTypeAll

Streaming Assets

  • 目的
    直接使用原始文件,比如视频文件,有些播放器需要直接使用文件路径去访问

  • 原理
    位于 StreamingAssets 目录下的文件将不进行打包操作,以原始文件路径直接存储

  • 资源配置
    在Assets目录下建一目录StreamingAssets,把资源放到该目录下

  • 资源加载
    目标机器的StreamingAssets目录 Application.streamingAssetsPath 跟平台有关:

    • Editor 或 win Application.streamingAssetsPath = Application.dataPath + “/StreamingAssets”;
    • ios 平台 Application.streamingAssetsPath = Application.dataPath + “/Raw”
    • android 平台 Application.streamingAssetsPath = “jar:file://” + Application.dataPath + “!/assets”
      存储在压缩文件中,必须使用 UnityWebRequest 加载文件
    • webGL 平台没有权限访问
    • 其它平台直接使用:
          GameObject cam = GameObject.Find("Main Camera");
          videoPlayer = cam.AddComponent<UnityEngine.Video.VideoPlayer>();
          // Obtain the location of the video clip.
          videoPlayer.url = Path.Combine(Application.streamingAssetsPath, "SampleVideo_1280x720_5mb.mp4");
      

AssetBundle

  • 参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle

  • 目的
    把一些资源打包成一个文件,可以从外部进行加载,外部可以通过从网络下载的方式来实现更新

  • 资源配置
    参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/AssetBundle 工作流程
    5.x版本对生成assetBundle进行一定的支持,在每个资源的属性窗口右下角 AssetBundle 栏有2个选项

    • 左边选择该资源所属分组(也就是包名),支持目录结构用"/"
    • 右边选择变体名称(也就是包的后缀名,不能是预留的txt rar等,需要才加不需要留空)
      包名和后缀名相同的资源会打在同一个包里,
    • 注意相同包名的要么都有后缀,要么都没有后缀,不能同时存在 aaa 和 aaa.abb
    • 注意如果文件夹设置包名,该文件夹下没设置包名的资源将被打进文件夹所在包名
  • 资源打包
    参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/构建 AssetBundle

    • 资源分组后可以执行下面脚本将各资源分组打包并放入目标目录,目标目录是以工程路径(Assets的上一层)为相对目录

      	//	将这段代码放到  Assets/Editor/XXX.cs 中
      	//	通过 菜单Assets->Build AssetBundles 执行
      	[MenuItem("Assets/Build AssetBundles")]
      	static void BuildAllAssetBundles()
      	{
              
              
      		//	打包输出目录
      		string assetBundleDirectory = "Assets/AssetBundleOut";
      		if (!Directory.Exists(assetBundleDirectory))
      		{
              
              
      			Directory.CreateDirectory(assetBundleDirectory);
      		}
      		//	第3个参数表示 window 平台,根据需要改成对应平台
      		//	可以通过 EditorUserBuildSettings.activeBuildTarget 获得配置的目标平台
      		BuildPipeline.BuildAssetBundles(assetBundleDirectory,
      										BuildAssetBundleOptions.None,
      										BuildTarget.StandaloneWindows);
      	}
      
    • 很重要的一点

      • 打包时会把资源打包成目标平台的格式
        但是打包时不会先切换到目标平台,重新编译Editor代码,然后重新导入资源,最后再打包
      • 对于大多数资源,在不同平台下导入时,生成的都是同样的对象,所以不存在问题,
        也就是在win平台打android的bundle包也能得到正确的结果
      • 但是如果有某种资源,如果你在win平台导入时生成的是A对象,在android平台导入时生成的是B对象,
        那么你在win平台下执行打包操作,打包android平台的 bundle 包,则 bundle 包里的将是 A 对象
        想得到正确的结果,你必须先切换到android平台再打android平台的bundle包
      • 还有一点,打包时对着色器的编译跟 GraphicsApi 相关,也就是同样是window平台,
        使用 vulkan 和 dx12 打出的包是不一样的,使用 vulkan 打出的ab包在使用 dx12 打出的应用里是运行不了的
      • 某个平台打包时,会使用GraphicsApi列表中第一个支持的进行编译,如果是 Auto Graphics ,那就用默认的
      • linux不支持dx12打包,有个变通的方式,把dx12相关的shader关联到一个组件上,打包时删除该组件,由包的加载方创建该组件
    • 执行后在目标目录输出
      xxx 打包好的资源包
      xxx.manifest 资源包清单文件,包含包中的资源列表和依赖关系,清单文件是方便用户做自己的热更程序
      AssetBundleOut 以输出目录命名的清单捆绑包,包含资源包列表和依赖关系
      AssetBundleOut.manifest 以输出目录命名的清单捆绑包的清单文件,包含资源包列表和依赖关系

    • 只包含图片的AssetBundle,压缩的一般是不压缩的1/5,如不压缩1.3m,压缩后270k,Unity用lzma算法压缩
      要注意,图片是Texture类型时,会自动调整到适合该平台可压缩的图片格式,如大小是POT,方形之类
      而图片是Sprite类型时,要自己将图片保存成该平台可压缩格式,否则打包成AssetBundle时会很大,上述的不压缩的AssetBundle中图片1.3m
      如果没压缩将达到9.7m生成的AssetBundle将达9.6m
      Android平台压缩用ETC1(4bit/pixel),成功压缩的要求是POT且不带透明通道,否则将以16bit/pixel的方式压缩保存
      Ios平台压缩用PVRTC,成功压缩的要求是POT且方形,否则将以true color(32bit/pixel)不压缩保存

  • 资源加载

    • 参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/AssetBundle 工作流程
      注意这里加载的都是资源包,也就是上面的 xxx
      加载资源时传入的路径是资源相对 Assets 目录的路径,比如 Assets/Obj/a.png 打包成成bundle包后,加载时就传入 Assets/Obj/a.png ,也可以直接传入文件名 a.png
      AssetBundle中包含的资源可以在生成的 manifest 中查看,Assets 字段下就是所有资源路径

      1. 从内存加载,内存负担较重,不建议使用
        跟方法2从文件加载的原理相同
            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);
            }
        
      2. 从文件加载,常用
        注意用文件创建时不要加file://,否则会报奇怪的错误,read bundle header error
        从文件创建,支持任何压缩格式,如果是 LZMA 会先解压到内存中,LZ4和未压缩则需要时直接从硬盘读取
        资源名称按完整路径,名称加扩展名,名称 三种方式来匹配如Assets/a/glass.png可以用glass或glass.png或Assets/a/glass.png
            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);
        
      3. 从网络下载并加载
        默认从网络下载并解压到内存中,如果调用 GetAssetBundle 提供了版本号或哈希值,则下载的文件将重新压缩存放到Cache文件夹
        Caching.compressionEnabled=true,则保存成 LZ4 格式,否则保存成未压缩格式
        UnityWebRequest 也可用于加载本地文件,如果本地文件是 LZMA 格式,建议用 UnityWebRequest 加载,提供版本号保存成缓存文件
        因为使用 AssetBundle.LoadFromFile 加载 LZMA 文件,每次都会解压到内存中
        Cache 目录
        在windows中是在C:\Users\Administrator\AppData\LocalLow\Unity\WebPlayer\Cache\DefaultCompany_UnityLua\80f87c8847da6c872ce2fc9fec0a98a73ebc578d
            IEnumerator InstantiateObject()
            {
                  
                  
                string uri = "file:///" + Application.dataPath + "/AssetBundleOut/" + 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");
                Instantiate(cube);
            }
        

      可以用 bundle.mainAsset 访问主资源,新版本已经弃用,并且没有替代方法,只能通过名称查找

    • 注意事项: 如果 AssetBundleA 依赖 AssetBundleB ,则你要先加载 AssetBundleB 再加载 AssetBundleA,再创建对象,不然引用的
      AssetBundleB 中的对象会被置空,如果我们在每个使用的地方都去手动处理依赖,那就太烦琐了,这时候上面打包时生成的
      AssetBundleOut 的捆绑包就派上用场了,该捆绑包包含了所有包列表和依赖关系

          AssetBundle manifesAB = AssetBundle.LoadFromFile("AssetBundleOut/AssetBundleOut");
          AssetBundleManifest manifest= manifesAB.LoadAsset<AssetBundleManifest>("AssetBundleOutManifest");
          //	获取所有包
          foreach (string name in manifest.GetAllAssetBundles())
          {
              
              
              print(name);
          }
          //	获取并加载依赖包
          string []strs=manifest.GetAllDependencies("xxx");
          foreach (var name in strs)
          {
              
              
              AssetBundle.LoadFromFile("AssetBundleOut/"+name);
          }
      
    • 可以把场景打包到AssetBundle中,当用Application.LoadLevel加载场景时,如果包含该场景的AssetBundle已被加载,
      则优先使用AssetBundle中的场景,否则会使用打包的场景

    • 把同一个AssetBundle拷贝并改名,先加载原始的,再加载改名的会提示已经存在,说明AssetBundle不是以名称为标识,
      而是里面集成了AssetBundle的标识,加载改名的,再加载引用原始AssetBundle资源的AssetBundle,成功

    • Resources其实也是一份AssetBundle而且不会被释放,所以Resources.Load加载的资源,名称一样就是同一个资源

    • 同一个AssetBundle加载出来的同一名称的资源,是同一个资源,AssetBundle.Unload后AssetBundle已经被释放,
      这时不能再调用LoadAsset,必须重新加载AssetBundle,但是重新加载AssetBundle后再LoadAsset同一个名称的资源,
      其实是创建了一份新的资源

  • 资源使用

    • 使用场景
      //  判断ab包中是否包含场景
      bool CheckContainScene(AssetBundle assetBundle,string sceneName)
      {
              
              
           if (assetBundle.isStreamedSceneAssetBundle)
          {
              
              
              string[] scenePaths = assetBundle.GetAllScenePaths();
              foreach (string path in scenePaths)
              {
              
              
                  if (path.Contains(sceneName))
                  {
              
              
                      return true;
                  }
              }
          }
          return false;
      }
      //  加载场景,跟普通加载场景相同
      AsyncOperation loadScene = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
      
  • 资源压缩

    • LZMA
      默认使用 LZMA 创建 AssetBundle ,具有较大的压缩率,适合网络传输
    • LZ4
      块压缩,能单独解压某个对象,默认做为缓存文件,从网络下载 LZMA 的ab包后会解压再用 LZ4 压缩放到 Cache 目录中
      后续直接读取 LZ4 文件,加载对象时可快速单独解压该对象的数据
    • 未压缩
      加载速度快,但文件大,适合做为本地文件,一般是随 StreamingAssets 发布
    • 总结:
      对大小有要求用 LZMA,对速度有要求用未压缩,LZ4 处于比较均衡的位置
      默认打AB包用 LZMA,可使用参数 BuildAssetBundleOptions.ChunkBasedCompression 强制用 LZ4,或 BuildAssetBundleOptions.UncompressedAssetBundle 强制未压缩
      UnityWebRequest 默认下载后使用内存缓存,如果你提供了版本号或哈希,则会存储到缓存文件, Caching.compressionEnabled 设为true使用 LZ4 压缩,否则不压缩
  • 资源缓存
    参考资源压缩

  • 资源打包和加载总结:
    如果是从网络下载,应该用 LZMA 格式
    方法1.使用 UnityWebRequest 并提供版本号,Caching.compressionEnabled=true 使用 LZ4 保存成缓存文件
    方法2.自己手动下载包文件,使用 AssetBundle.RecompressAssetBundleAsync 重新压缩成 LZ4 文件,使用 AssetBundle.LoadFromFile 加载文件
    如果是本地文件,应该用 LZ4 格式,直接使用 AssetBundle.LoadFromFile 加载
    如果是未压缩文件,直接使用 AssetBundle.LoadFromFile加载

  • 资源卸载
    参考 Unity User Manual (2019.4 LTS)/Working in Unity/资源工作流程/AssetBundle/本机使用 AssetBundle
    在最下面介绍了 Addressable Assets 包来管理资源包的依赖、加载和卸载

    用AssetBundle加载的asset一样可以用Resources.UnloadUnusedAssets卸载,即使该AssetBundle还未Unload
    使用 LoadFromFile 加载AssetBundle时,有没有调用AssetBundle.Unload占用内存相差不大,说明该函数并未将文件加载到内存,
    只是读取了文件句柄,需要时才读取内容,

    用 UnityWebRequest 来加载AssetBundle时,有没有调用AssetBundle.Unload,内存相差很大,说明该函数将整个文件内存加载到
    内存中并解压缩,因为Unload后释放的内存跟未压缩的AssetBundle大小差不多

  • 资源查看
    参考 Unity User Manual (2019.4 LTS)Working in Unity资源工作流程AssetBundleUnity Asset Bundle Browser 工具
    unity 提供了一个工具可以查看、修改、打包 assetBundle 的插件
    下载地址 https://github.com/Unity-Technologies/AssetBundles-Browser
    把 Editor 文件夹拷贝到 Assets 目录即可
    菜单 Window->AssetBundle Browser 打开AssetBundles 面板

  • 示例
    资源创建和加载可以参考CharacterCustomization示例,
    Assets/Plugins/Editor/CreateAssetBundles.cs 创建资源
    Assets/Plugins/CharacterGenerator.cs 读取资源
    还可以参考 ET 框架的 ResourcesComponent

AssetDatabase

在UNITY_EDITOR模式下还可以用AssetDatabase操作资源
一般用assetsBundle加载的资源,在编辑器里可以用AssetDatabase来加载,以提高开发效率

一些问题的试验:

  • prefab打包到assetbundle上,再从assetbundle加载该prefab并实例化

    1. 删除本地脚本,则提示脚本Missing
      保留本地脚本,成功

    2. 删除本地图片,成功
      修改本地图片,显示的依然是修改前的图片

    3. 图片打包到assetbundle1上
      没有加载assetbundle1,图片丢失,本地有没有删除都一样
      加载assetbundle1到 AssetBundle bundle = www.assetBundle后再加载assetbundle成功
      修改图片重新打包到assetbundle1上,则显示的图片随之改变

    4. 加载asset时传入的资源名称,原始资源路径Assets/Test/TextObject.prefab
      名称传入TextObject,TextObject.prefab,Assets/Test/TextObject.prefab均能找到资源
      诸如Test/TextObject,Assets/Test/TextObject均不能找到资源

    5. 加载assetbundle,LoadAsset获得prefab,Unload该assetbundle并重新加载,再次LoadAsset获得prefab
      发现2次获得的prefab不是同一个对象

  • 结论:

    1. 脚本被编译成程序集,脚本对象只能使用本地程序集中的脚本对象,打包进assetbundle中的用不了
    2. 打包进assetbundle中的资源如果使用了其它assetbundle中的资源,则要先加载其它assetbundle,
      否则会找不到资源,如果某个资源既在场景中使用,又在assetbundle中使用,则将会被打包成2份,
      场景中的使用场景中的资源,assetbundle使用assetbundle中的资源,其实相当于2个对象(脚本除外)
    3. assetbundle查找资源时按完整路径,名称加扩展名,名称 三种方式来匹配
    4. 为了不重复创建资源,必须把打开的AssetBundle缓存住,这样从AssetBundle加载出的资源才不会重复
  • 场景文件.unity打包到assetbundle上,使用Application.LoadLevel加载场景,Assets/main.unity均使用main做场景名称

    1. 没有加载AssetBundle,直接加载场景,显示打包时的场景
    2. 先加载AssetBundle再加载场景,显示AssetBundle中的场景
  • 结论:
    场景可以打包到AssetBundle中,加载AssetBundle再加载场景可以产生覆盖效果

  • 场景文件和场景中的图片分别打包到scene和image,测试这几个文件分别加载的效果

    1. scene和image都不加载,显示打包的场景
    2. image加载scene不加载,还是显示打包的场景且图片资源没被替换
    3. image不加载scene加载,显示scene中的场景且图片资源消失(没找到)
    4. image加载scene加载,显示scene中的场景且图片是image中的图片
    5. 重新打包image,scene不变,image和scene都加载,显示的是新的image中的图片
  • 结论:
    静态scene引用的是静态的资源,动态的scene引用的是动态的资源,不会交叉引用
    也就是说引用资源除了资源标识外,应该还指明了资源所在包,只有Application.LoadLevel例外
    该函数应该是逆向搜索所有加载的资源包,找到相应的场景文件
    动态scene中所引用的资源更新,scene文件可不更新
    把资源放到Resources中,用prefab替代scene,仍有同样的验证
    说明Resources.Load加载的资源也不会引用assetbundle中的资源

  • 场景文件中引用一张图片,分别打包到scene和image

    1. image文件更新后重新打包,scene文件不会被再次打包
    2. 加入材质,引用图片并打包到material,image和scene不会变
    3. 再次更改图片后重新打包,material和scene均不会变
    4. 更改材质后重新打包,image和scene均不会变
  • 结论:
    引用关系的资源分别更新时另一方不会更新
    只有打包的资源才能使用打包的资源
    要注意如果文件夹设置包名,该文件夹下没设置包名的资源将被打进文件夹所在包名

  • 加载assetbundle文件,并创建assetbundle对象,释放WWW对象,对比内存占用大小,测试用assetbundle未压缩大小8.1m

    1. 用new WWW加载,内存增大8,1m,说明将assetbundle文件复制到内存区
    2. 用AssetBundle.CreateFromFile加载,内存无变化,该函数只能加载未压缩文件
    3. 用WWW.LoadFromCacheOrDownload加载,内存无变化,说明没有加载到内存,但在
      C:\Users\Administrator\AppData\LocalLow\Unity\WebPlayer\Cache\DefaultCompany_UnityLua\80f87c8847da6c872ce2fc9fec0a98a73ebc578d
      下发现多了一个文件CAB-19aa249d139a2d14f62c5858d4ef7116,大小跟assetbundle未压缩大小一致,
      说明该函数将assetbundle解压成未压缩的assetbundle放到这里,然后再用AssetBundle.CreateFromFile加载
  • 结论:
    为了不释放AssetBundle而又不占用内存,必须采用从文件加载,且最好用Assetbundle.CreateFromFile,这样不用解压资源,效率更高

  • android平台上将一张2024x1024的jpg打包到assetbundle中,对比生成的文件大小

    1. etc压缩,assetbundle压缩 0.355m
    2. etc压缩,assetbundle未压缩 1.37m
    3. 图片未压缩,assetbundle压缩 1.452m
    4. 图片未压缩,assetbundle未压缩 8.101m
  • 结论:
    尽量让图片压缩并且assetbundle也压缩
    由于为了使用AssetBundle.CreateFromFile,所以assetbundle只能采用不压缩,网络传输时自己再用zip对assetbundle压缩
    为了图片压缩,texture格式会自动处理,Sprite格式在
    Android平台用ETC要求图片大小POT且不带透明通道,否则将以16bit/pixel的方式压缩保存
    Ios平台压缩用PVRTC,成功压缩的要求是POT且方形,否则将以true color(32bit/pixel)不压缩保存

  • android平台上将一张2024x1024的jpg分别打包到assetbundle和Resources中,对比生成的文件大小

    1. 打包到assetbundle中,未压缩,8.101m
    2. 打包到assetbundle中,压缩1.452m
    3. 打包到resources.asset中,8.101m
  • 结论:
    resources.asset和sharedassets[level].assets其实就是未压缩的Assetbundle

  • 通过上面的试验,可以得出最好的热更新方案,就是将资源打包成未压缩的AssetBundle,下载过程用外部工具压缩解压,
    使用时用AssetBundle.CreateFromFile加载AssetBundle,并且加载后的AssetBundle不用释放(不占内存),这样就不会
    重复创建资源,只有在某个阶段比如切换场景时再一次性释放
    游戏打包时,有2种方案:

    1. 资源先打包成AssetBundle,再放到StreamingAsset中,读取资源时直接从AssetBundle中读取
      缺点:android版本需要将AssetBundle从StreamingAsset解压出来,占用更大的存储空间
      如果不解压则占用更大的内存,且要用异步方式

    2. 资源放到Resources目录下,读取资源时先判断是否在下载的AssetBundle中,是则从AssetBundle中加载,否则从Resources中加载
      缺点:AssetBundle a依赖AssetBundle b时,则当b重新打包更新,若a未被更新过,则a也必须更新
      因为打包后的资源只能被打包后的资源访问,a若不更新,用Resources加载的a中资源引用的还是Resources中的b资源

猜你喜欢

转载自blog.csdn.net/qmladm/article/details/142346540