当前位置:   article > 正文

【Unity】Asset资源加载详解_unity资源加载

unity资源加载

加载类型:

加载方式加载接口加载后缀异步热更卸载某个
ResourceLoad×
AssetDatabaseLoadAssetAtPath文件后缀×××
AssetBundleGetDependent→LoadDpAB→LoadAB→LoadAsset

加载资源,指从磁盘加载到内存中,反序列化然后实例化Instance才会真正到Scene中。

资源加载接口

  1. Resource.Load
     

    Resource.Load是Unity加载Resources文件夹的加载方式,Resources文件夹会随着打包一起被打到游戏包内。
    正式项目切勿使用此接口,无法热更,没有找到分包的方法,每次发包都要重新打。

    一般来说最先接触到的资源加载接口是Res.Load,对于项目前期或者小项目会比较方便,因为不需要对打包资源做任何操作,放在resources下的资源就能随着build一起打了,但对于大项目需要热更项目来说,基本不会使用了,因为不能分包也无法热更(也可能是我没找到办法),上线就肯定用AssetBundle



     
  2. UnityEditor.AssetDatabase.LoadAssetAtPath
     

     Editor下的加载方式,加载路径是项目下的路径,除了Resources文件夹都不会随着打包打到游戏本体内。
    注意路径是带Assets开头,并且需要后缀

    AssetDataBase不支持异步,所以editor下的效果和打包效果不一样,而且这样的加载还需要后缀,或者用ab名去索引,否则导致会多些一些代码,不过问题不大,习惯就好。
    如果DestroyImmediately(true)后除非重启unity,再也加载不出来

  3. AssetBundle.Load

     加载Asset Bundle可以用于热更资源。

    1. public class SampleBehaviour : MonoBehaviour
    2. {
    3. IEnumerator Start()
    4. {
    5. var uwr = UnityWebRequestAssetBundle.GetAssetBundle("http://myserver/myBundle.unity3d");
    6. yield return uwr.SendWebRequest();
    7. // Get an asset from the bundle and instantiate it.
    8. AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(uwr);
    9. var loadAsset = bundle.LoadAssetAsync<GameObject>("Assets/Players/MainPlayer.prefab");
    10. yield return loadAsset;
    11. Instantiate(loadAsset.asset);
    12. }
    13. }

    AssetBundle(简称AB)这是一个分包和热更的方案,调用打包接口之后就能产生出一堆的AB包,这样做使得增量打包更快(不需要每次把所有资源重新打),热更时更新的内容也相对的小。
    Asset才是真正的资源例如贴图、网格、动画那些都是Asset,AB可以理解成Asset的包,AB只会记录里面有那些Asset,依赖了其他的哪些AB,是一个非常小的东西
    一般来说同一类的资源会打成一个AB,例如角色Prefab会依赖贴图网格动画等..
    AB可能又会依赖多个其他AB,所以加载经常会需要先加载依赖,否则加载出来的东西也还是空的,依赖使用Manifest获取
    Manifest是会随着打包产生出来,最好能在初始化的时候把Manifest加载了,然后存起来使用。
    但AB缺点是editor下使用比较麻烦,总不能每次改了资源都重新打AB,所以editor可以用AssetDataBase

认识Asset

meta

guid就唯一标识这个资源
同时保存了一些设置 和 ABName..

Library

Unity会把Asset下支持的资源导入成自身识别的格式,以及编译代码成为DLL文件,都放在Library文件夹中。
如果打包编译的shader也会存在这
如果打图集也会存在这
大项目如果删除这个文件夹会导致导入很久很久...
在AssetDataBaseV2之前,如果切平台也会重导很久...

Asset

用到的资源,比如,模型文件,贴图文件,声音文件等等
注:这才是真正占用内存的大头,一般Prefab的目标Asset也很小,大的是依赖,依赖被目标Asset引用,只有卸载了Asset再调用UnloadUnuse才真正的释放了内存
hashCode为正

GameObject

hashCode为负数

AssetBundle

AssetBundle是一个存档文件,其中包含平台在运行时加载的特定资产(模型,纹理,预制,音频剪辑,甚至整个场景)。
AssetBundles可以表示彼此之间的依赖关系;例如AssetBundle A中的一个材质可以引用AssetBundle B中的一个纹理。
为了通过网络进行有效的传递,可以根据用例要求,选择内置算法(LZMA和LZ4)来对AssetBundles进行压缩。
AssetBundles可用于可下载内容(DLC),减少初始安装大小,加载为最终用户平台优化的资产,并降低运行时内存压力。

由上图可见AssetBundle是包含了多个或者单个Asset的包,里面的Asset可能是贴图可能是模型,
所以加载过程上来说,一个prefab可能会包含多个AssetBundle,
加载必须先加载AssetBundle以此来寻找Asset,可以通过AssetBundleManifest.GetAllDependencies获取依赖的AssetBundle。

细节:AssetBundle是一个小文本,记录了依赖和aaset的一些预制信息,一个没有依赖的AB镜像文件大概会有7kb左右的内存,一般文件会在10多k,一般几个依赖的ab加载会有0-2ms左右,很小如果把很多文件都打成一个ab会引起依赖特别多的情况,所以分包是一个需要好好控制的事情

Manifest

随着调用打包的API会自动产生Manifest,是一个保存所有AssetBundle关系的清单。
一般在加载过程中都需要通过Manifest获取依赖然后再加载真正的Asset.

加载与Instance
为什么要Instance呢,因为可能会有多个重复加载,用一个加载好的,通过部分复制,大部分引用的方式来使用。

图示

AssetBundle使用流程

1.设计AssetBundle

//todo 如果多人看就搞个全程截图教程

应用:
按照一定策略打包
一个关卡/UI面板多张贴图打成一张贴图,以此减少drawcall但也增加了内存。
如果不使用loadassetall,单独的资源打成一个Asset,减少加载量和加载的包量,但也使需要打的包数量增加。

2.打包

接口:BuildPipeline.BuildAssetBundles
使用:会打包所有标记了assetbundle名的资源,并且会产生一个AssetBundleManifest 其会listing all AssetBundles included in this build.

BuildPipeline.BuildAssetBundles("Assets/ABs", BuildAssetBundleOptions, BuildTarget);
  1. public class TestEditor
  2. {
  3. [MenuItem("Test/build Test")]
  4. public static void BuildTest()
  5. {
  6. BuildAssetBundleTest(EditorUserBuildSettings.activeBuildTarget);
  7. }
  8. const string AssetBundlesOutputPath = "Assets/StreamingAssets/";
  9. static void BuildAssetBundleTest(BuildTarget buildTarget)
  10. {
  11. string outputPath;
  12. outputPath = AssetBundlesOutputPath;
  13. Debug.Log("outputPath:" + outputPath);
  14. if (!Directory.Exists(outputPath))
  15. {
  16. Directory.CreateDirectory(outputPath);
  17. }
  18. BuildAssetBundleOptions buildOptions = BuildAssetBundleOptions.ChunkBasedCompression |
  19. BuildAssetBundleOptions.DeterministicAssetBundle |
  20. BuildAssetBundleOptions.DisableWriteTypeTree;
  21. BuildPipeline.BuildAssetBundles(outputPath, buildOptions, buildTarget);
  22. AssetDatabase.SaveAssets();
  23. AssetDatabase.Refresh();
  24. Debug.Log("打包完成");
  25. }
  26. }

3.Build Asset上传到场外

4.加载依赖

获取 : Manifest.GetAllDependencies(abPath);

4.同步加载

4.AB异步加载

目的:缓解同步加载卡顿
注意:异步的时候同步加载会造成冲突,导致加载失败等问题

5.Cache

把加载的AB Cache住,因为ab不能重复加载。
把加载的Asset Cache住,不用每次都去判断ab是否加载、寻找依赖、加载依赖。load的时候直接返回cache。

应用:
方案①做池管理加载和卸载,底层不cache只负责unload
方案②cache了加载好的Obj,用于下次调用直接返回obj ,每次调用进行了计数+1,调用Unload可以-1

6.实例化

注:实例化如果是异步,可以在底层列队
 

7.卸载Assets

目的: 
①游戏内容多,导致内存过大,
②没有内存问题的游戏可以只在切换场景的时候才清空Cache、GC释放内存

使用api:
①asset.unload(false) 只会卸载包头文件
②asset.unload(true) 包头与asset都会删除(如果场景还有GameObject会丢失 或者 如果不卸载会内存过高/泄漏)

说明:
加载系统一般会保存目标asset,而其引用着依赖的asset
目标asset的unload只会unload目标 但一般引用的asset才是内存占用最高的 所以存在2种卸载方式①调用卸载时 把依赖的也unloadtrue(目前只卸载了目标ab和asset) ②调用unloadunuse才卸载依赖的asset

如何卸载:
①当asset引用为0 unload(true) 此时只是把目标的AB卸载掉 依赖依然在内存中 但引用为0  调用UnloadUnuseAssets 下次GC便会清除 
②切场景全部删除(除了列表里的)

UnloadUnuseAsset这个API是Unity写的 是个异步的 一般会有几帧的小卡 做个回调 等UnloadUnuse做完才调用C#的GC C#的GC是会同步的卡顿的。需要在适合的时机调用 

优化

  • AB.tolower的Dic//存起来 减少gc//可以在打包时存 也可以在首次加载的时候存一下
  • AB同步,Asset异步
  • 异步加载回调分帧Invoke
  • 拆分加载和反序列化
  • 预先反序列化和实例化
  • Asset的DontDestroy列表
  • 压缩,lz4知道第几块 然后取出 lam是要整个压缩 整个读取
  • 打bundle包是存在粒度问题,太大的包会使得热更时包很大,包太小会使得ab非常的多,加载IO和耗时上升
  • 可以使用Addressable 异步实例化(实际里面也是做了拆分?)
  • 更多应用级优化看大世界加载优化

资源内存Profiler

assetbundle:Not Saved→AssetBundle  序列化Other→SerializedFile

问题记录:

  • 加载卡住或者加载不出或者闪退的也许原因:同步异步冲突、ab与Asset对不上、Editor下AB与Unity版本不对
  • 如果对AB.LoadFormFile进行耗时检测 那么这个时间会在0-几毫秒之间 似乎AB的内容多少 但很迷有时候会有几十毫秒 但是看文件却又不大 DeepF开了也只是看到File.Open或者找不到这么高的耗时 很迷..
  • 回调的方式不是很好,做Task会比较优雅
  • 如果不卸载依赖的AB ,问题也不大因为AB也不大,也怕如果有计数,计数错误的导致问题(虽然做了但注释了),AB对Asset其实有引用关系,如果把AB Unload(false),又被其他引用,那么可能会泄露
  • Editor下使用AssetDataBase无法卸载和异步,无法模拟真实AB,而项目又常常非常的大,打AB很不方便,解决就得搞个自动打AB,然后还得放项目外,项目大了就检查资源卡,如果想debug卸载,还得给个口子开关log和设定要debug的资源名字。。。
    想要editor下跑AB,设置一些接口简单的开启AB,把AB放到库里,打包机定期打包上传,让每个人都轻易跑本地AB,然后想要打单独AB也比较简单的界面操作就好了
  • 打包很久,主要在shader和AB,即使增量AB的检查也是很久的,每个文件hash检查的IO也很久。
  • shader可以收集和提前WarnUp,但是还是会卡,shader首次运行会从CommonShader生成对应平台shader,第二次就会快一些,但有些shader的复杂逻辑也依然会卡

-------------------------------------------------------------------------------------

  1. using System.Collections.Generic;
  2. using System.IO;
  3. using UnityEngine;
  4. public class AssetBundleMgr
  5. {
  6. protected AssetBundleManifest manifest;
  7. protected string manifestPath = "StreamingAssets";
  8. protected Dictionary<string, AssetBundle> cacheAB = new Dictionary<string, AssetBundle>();
  9. private static AssetBundleMgr mInstance = null;
  10. public static AssetBundleMgr GetInstance()
  11. {
  12. if (mInstance == null)
  13. {
  14. mInstance = new AssetBundleMgr();
  15. }
  16. return mInstance;
  17. }
  18. private AssetBundleMgr()
  19. {
  20. AssetBundle ab = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, manifestPath));
  21. if (ab != null)
  22. {
  23. var manifest = ab.LoadAsset("AssetBundleManifest");
  24. if (manifest == null)
  25. Debug.LogError("Load Manifest Asset Fail");
  26. else
  27. this.manifest = manifest as AssetBundleManifest;
  28. }
  29. else
  30. {
  31. Debug.LogError("Load Manifest AssetBundle Fail");
  32. }
  33. }
  34. public Object Load(string abPath, string prefabName)
  35. {
  36. LoadDependencies(abPath);
  37. if (!cacheAB.TryGetValue(abPath, out var ab))
  38. ab = LoadAssetBundle(abPath);
  39. var asset = LoadAsset(ab, prefabName);
  40. return asset;
  41. }
  42. protected void LoadDependencies(string abPath)
  43. {
  44. if (manifest == null)
  45. return;
  46. string[] dependences = manifest.GetAllDependencies(abPath);
  47. for (int i = 0; i < dependences.Length; i++)
  48. {
  49. string dependABPath = dependences[i];
  50. if (!cacheAB.ContainsKey(dependABPath))
  51. {
  52. cacheAB[dependABPath] = LoadAssetBundle(dependABPath);
  53. }
  54. }
  55. }
  56. protected AssetBundle LoadAssetBundle(string abPath)
  57. {
  58. var fullAbPath = Path.Combine(Application.streamingAssetsPath, abPath);
  59. var ab = AssetBundle.LoadFromFile(fullAbPath);
  60. cacheAB[abPath] = ab;
  61. if (ab == null)
  62. Debug.LogError("Failed to load AssetBundle" + abPath);
  63. return ab;
  64. }
  65. protected Object LoadAsset(AssetBundle assetBundle, string assetName)
  66. {
  67. Object asset = assetBundle.LoadAsset(assetName);
  68. if (asset == null)
  69. Debug.Log("Failed to load asset:" + assetName);
  70. return asset;
  71. }
  72. }
  1. using System.IO;
  2. using UnityEditor;
  3. using UnityEngine;
  4. public class BuildTest
  5. {
  6. static string mResPath = "Assets/Res";
  7. static string outputPath = Application.streamingAssetsPath;
  8. [MenuItem("Tools/Build PC")]
  9. public static void BuildPCAssetBundle()
  10. {
  11. BuildAssetBundle(BuildTarget.StandaloneWindows);
  12. }
  13. public static void BuildAssetBundle(BuildTarget buildTarget)
  14. {
  15. SetFolderBundleName(mResPath);
  16. if (!Directory.Exists(outputPath))
  17. {
  18. Directory.CreateDirectory(outputPath);
  19. }
  20. BuildAssetBundleOptions buildOptions =
  21. BuildAssetBundleOptions.ChunkBasedCompression |
  22. BuildAssetBundleOptions.DeterministicAssetBundle |
  23. BuildAssetBundleOptions.DisableWriteTypeTree;
  24. BuildPipeline.BuildAssetBundles(outputPath, buildOptions, buildTarget);
  25. AssetDatabase.SaveAssets();
  26. AssetDatabase.Refresh();
  27. Debug.Log("打包完成");
  28. }
  29. [MenuItem("Tools/Set All BundleName")]
  30. public static void SetResFolderAllBundleName()
  31. {
  32. SetFolderBundleName(mResPath);
  33. Debug.Log("设置AB Name完成");
  34. }
  35. public static void SetFolderBundleName(string rootPath)
  36. {
  37. DirectoryInfo folder = new DirectoryInfo(rootPath);
  38. FileSystemInfo[] files = folder.GetFileSystemInfos();
  39. int length = files.Length;
  40. for (int i = 0; i < length; i++)
  41. {
  42. if (files[i] is DirectoryInfo)
  43. {
  44. SetFolderBundleName(files[i].FullName);
  45. }
  46. else
  47. {
  48. if (!files[i].Name.EndsWith(".meta") &&
  49. !files[i].Name.EndsWith(".cs"))
  50. {
  51. file(files[i].FullName);
  52. }
  53. }
  54. }
  55. }
  56. static void file(string source)
  57. {
  58. string assetPath = "Assets" + source.Substring(Application.dataPath.Length);
  59. string assetName = source.Substring(Application.dataPath.Length + 1);
  60. //在代码中给资源设置AssetBundleName
  61. AssetImporter assetImporter = AssetImporter.GetAtPath(assetPath);
  62. if (Path.GetExtension(assetName) != null && Path.GetExtension(assetName) != "") { assetName = assetName.Replace(Path.GetExtension(assetName), ""); }
  63. assetImporter.assetBundleName = assetName;
  64. }
  65. }

----------------------------------------------------------------------------------

end

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/81914
推荐阅读
相关标签
  

闽ICP备14008679号