Unity资源加载方式

3,643 阅读7分钟

参考资料

前言

1.Unity打包发布策略

按需打包:打包发布程序后,unity会自动将场景需要引用到的资源打包到安装包里,没有用到的不会跟进去。

(我们在编辑器里看到的Asset中的文件结构只是工作于编辑器环境下的,在游戏中unity会重新组织文件结构)

2.动态加载资源

如此打包就会有问题:场景中一开始没有用到的资源就不会被打包,但在游戏中我们却要动态加载它(如换装系统)

3.GUID与fileID

Assets目录中的每个资源都有与之对应的meta文件,文件中记录了GUID,相当于身份证

FileID(LocalID),表示的就是资源内部的依赖关系

资源加载方式

1.面板拖拽

(最简单,但通常不用)

实现方式:

定义public属性的变量,在面板上拖拽赋值,在Update中实例化

2.AssetDatabase

(只能在编辑器中使用,打包后就不能用了)

Assets文件夹

在编辑器中,Unity用到的所有资源都放在Assets文件夹下

Assets下的资源除特殊文件夹,或者在会打入包内的场景中引用的资源,其余资源不会被打入包中。

实现方式

需要完整路径及后缀名

AssetDatabase.LoadAssetAtPath("Assets/x.txt"); 

GameObject DBobj = Instantiate(
    UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>("Assets/GameData/Prefabs/cube.prefab"));

3.Resources

(保留文件夹,全部打包,最大2G,不建议使用)

实现方式

不加后缀名,路径是否写全都可以,倘若Assets文件夹下包含多个Resources文件夹,运行时会被当成一个

1.获取第一个匹配条件的对象
GameObject loadObj = Instantiate(Resources.Load("Prefabs/cube"))as GameObject;

2.获取所有符合条件的对象
Object[] assets = Resources.LoadAll("fileName"); 
TextAsset[] assets = Resources.LoadAll("fileName"); 

3.卸载资源
Resources.UnloadAsset(Object);//卸载对应Object 
Resources.UnloadUnusedAssets();//卸载所有没有被引用以及实例化的Object 

4.AssetBundle

(就是一个资源压缩包,方便分包发布和版本升级,推荐使用)

打包方式

  • 1.指定资源为AssetBundle属性:工程窗口选中资源,检视面板右下角分别设置 文件名 和 后缀
  • 2.编译器扩展,实现一键打包AB包
using UnityEngine;
using UnityEditor;
using System.IO;
 
public class CreateAssetBundles : MonoBehaviour
{
    [MenuItem("AssetBundles/Build AssetBundles")] //菜单栏显示
    static void BuildAssetBundle()
    {
        string dir = "AssetBundles"; //相对路径
        if(!Directory.Exists(dir))   //判断是否存在
         {
            Directory.CreateDirectory(dir);
         }
         BuildPipeline.BuildAssetBundles(dir, 
            BuildAssetBundleOptions.None,
            BuildTarget.StandaloneWindows64);
    }
}

方法参数详解(BuildPipline.BuildAssetBundle)

(a). Build的路径(只要是在硬盘上都可以的)

(b). BuildAssetBundleOptions(枚举类型)

   a)、BuildAssetBundleOptions.None:使用LZMA算法压缩,压缩的包更小,但加载时间更长,
        使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。 
        在下载的时候可以使用LZMA算法。一旦它被下载了之后,它会使用LZ4算法保存到本地上。

   b)、BuildAssetBundleOption.UncompressedAssetBundle (不压缩,包大,加载速度快)。

   c)、BuildAssetBundleOption.ChunkBasedCompression:(使用LZ4算法,压缩率没有LZMA高,但我们可
        以加载指定资源的不用解压全部);

   注意:使用LZ4算法,可以获得可以跟不压缩相媲美的加载速度,而且比不压缩的文件要小。

(c). BuildTarget:(选择Build出来的AB包需要的平台)

加载AB包的方式:

  • 从文件中加载 AssetBundle.LoadFromFile
1.加载AB包,注意资源存在依赖关系的话要先加载依赖的资源
AssetBundle abShare = AssetBundle.LoadFromFile("AssetBundles/materials/mapmaterials");
AssetBundle ab = AssetBundle.LoadFromFile("AssetBundles/scene/map_001");

2.加载AB包内资源
GameObject panelPrefab = ab.LoadAsset<GameObject>("Plane");

3.实例化
Instantiate(panelPrefab);
  • 从内存中加载 AssetBundle.LoadFromMemoryAsync
string path = "AssetBundles/scene/map_01";

1.异步读取LoadFromMemoryAsync
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path)); 
yield return request; //异步的方式必须等待完成后才继续执行
AssetBundle ab = request.assetBundle;

2.同步读取LoadFromMemory
//AssetBundle ab = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));

3.实例化
GameObject gameObj = ab.LoadAsset<GameObject>("map_001");
Instantiate(gameObj);
  • WWW加载(可以本地,也可以服务器)
1.检查cache 缓存是否准备好了
//www.LoadFromCacheOrDownload 这个方法是先下载存到cache的缓存空间内 然后在进行提取。
//如果第二次也进行下载一样的 则会在这个缓存空间内提取之前已经下载的数据
//开始下载前必须先判断是否准备好了,否则一直循环下去,等待准备好
while (!Caching.ready)
    yield return null;

2.本地下载
//string allPath = @"E:/03_TestDemo/01_UnityDemo/ShowMap_ABPackageTest_001/AssetBundles/scene/map_01";
//WWW www = WWW.LoadFromCacheOrDownload(allPath, 1);

3.远程服务器下载
WWW www = WWW.LoadFromCacheOrDownload(@"http://localhost/AssetBundles/scene/map_01", 1); //从远程服务器上下载AB包
yield return www;

4.因万维网是非常复杂的,经常容易出现非常意外的错误,所以必须加入一个判断
if(!string.IsNullOrEmpty(www.error)) //当什么都没有时
{
    Debug.Log(www.error);
    yield break; //跳出协程
}

5.取到AB包
AssetBundle ab = www.assetBundle;

6.实例化
GameObject gameObj = ab.LoadAsset<GameObject>("map_001");
Instantiate(gameObj);
  • UnityWebRequest
1.本地或服务器路径
//string uri = @"E:/03_TestDemo/01_UnityDemo/ShowMap_ABPackageTest_001/AssetBundles/scene/map_001";
string uri = @"http://localhost/AssetBundles/scene/map_001";

2.获取request
nityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(uri);
yield return request.SendWebRequest();  //注意是异步方式所以必须等待他完成后再执行

3.两种获取ab对象的方式
//AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request); //第一种方法
AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle; //第二种方法

4.实例化
GameObject gameObj = ab.LoadAsset<GameObject>("map_001");
Instantiate(gameObj);

各加载方式的区别

  • 前两种是从本地加载的,后两种既可从本地加载也可从服务器加载
  • 第一种和第二种的区别体现在加载不同压缩类型的AB包(从内存加载:加载LZMA时就把它解压,加载LZ4时还是压缩状态;从文件加载:效率高,直接把未压缩的和LZ4压缩的AB包加载,对LZMA先把其解压再放入内存中)
  • 第三种弃用,建议用第四种

加载AB包中资源的方式:

  • LoadAsset<>()
  • LoadAllAsset()
  • LoadAssetAsync<>()
  • LoadAllAssetAsync()

卸载方式:

- AssetBundle.Unload(true)

    卸载所有资源,即使有资源使用着(1,在关卡切换或场景切换时  2、资源没被用的时候调用);

- AssetBundle.Unload(false)

    卸载所有没有被使用的资源。(这个适用时间比较多 ,可自行把控)。

- 个别资源怎么卸载,通过Resources.UnloadUnusedAssets();

打包策略:

  1. 逻辑实体分组(Logical Entity Grouping)

    • 一个UI界面或者所有UI界面一个包(这个界面里面的贴图和布局信息一个包)
    • 一个角色或者所有角色一个包(这个角色里面的模型和动画一个包)
    • 所有的场景所共享的部分一个包(包括贴图和模型)
  2. 类型分组(Type Grouping)

    所有声音资源打成一个包,所有shader打成一个包,所有模型打成一个包,所有材质打成一个包

  3. 并发内容分组(Concurrent Content Grouping)

    把在某一时间内使用的所有资源打成一个包。可以按照关卡分,一个关卡所需要的所有资源包括角色、贴图、声音等打成一个包。也可以按照场景分,一个场景所需要的资源一个包

  4. 注意

  • 经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
  • 把需要同时加载的资源放在一个包里面,例如模型,其纹理和动画
  • 把其他包共享的资源(公共资源)放在一个单独的包里面
  • shader字体等其他细碎并且需要常驻内存的资源打包到一起,启动游戏的时候常驻内存
  • 通常情况下,1M左右的AssetBundle包加载性能最好,冗余也可以接受,但是在Unity 5.3版本之后,对于AB文件的文件大小其实不必再限定于1MB之内。使用LZ4压缩,基于其Chunk的加载特点,AB加载很快,且内存占用要比之前小很多。所以LZ4的AB其实可以考虑更加粗粒度一些。

踩过的坑:

  • 如何将多个资源打成同一个包:将标签和后缀都设为一样的,取AB包的代码填的是标签.后缀,取具体物体的代码填的是物体名
  • AB包名.manifest:Assets表示包内所有物体的路径,Dependencies表示其他包的依赖
  • AssetBundles.manifest:存放所有的AB包的信息
  • 包的依赖只存在于:一个物体被打为A包,而该物体包含的“子物体”被打为B包,才会产生依赖,若该“子物体”不属于任何AB包,则不会有依赖产生
  • 存在依赖时的加载顺序:A包预设,B包材质,预设包含材质,A包、B包加载的顺序无所谓,就是从A包取预设前必须加载完B包,若不这么做,会出现材质丢失,预设无材质,若材质没有被打包,则不会出现材质丢失的情况