Unity实用小工具或脚本—加载外部图片的三种方式

一、前言
       项目上需要加载多个地图,每个地图的贴图将近200M,所有地图加起来贴图将近2G。因此,想着能不能将贴图放到工程外加载,逐研究了一下,得出了三种加载方式,分别是WWW加载、C#原生的IO加载在转换成Texture2DAssetbundle打包和加载。
       前面两种方式都会存在卡顿现象,因为最消耗性能的计算方式最终都还是放在主线程里。尽管第二种方式可以使用另外一个线程加载图片成Bytes数组,但是将字节数组转成成Texture2D还是在主线程里,而这个过程在图片5M的时候还是很卡顿,何况我的地形贴图每张有20M左右。对于前面两种方式没有找到任何其他好的优化方式来解决。第三种方式是我用到最理想的,在加载的过程中不会有卡顿。
二、WWW加载
首先,获取当前工程同目录下的“MapImages”文件夹路径,然后获取每张图片的全部路径,并将路径存到列表中。
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  /// <summary>
    /// 获取当前物体应该读取的地形贴图的文件的路径
    /// </summary>
    /// <returns></returns>
    private string GetFilePath()
    {
        string[] strTempPath = Application.dataPath.Split('/');
        string strPath = string.Empty;
        //去掉后面两个,获取跟工程相同目录的路径,如“E:/ZXB/MyProject/Asset/",去掉后面两个就得到和工程同级的目录为:“E:/ZXB”
        for (int i = 0; i < strTempPath.Length-2; i++)
        {
            strPath += strTempPath[i] + "/";
        }
        //加上整个地形贴图的文件命
        strPath += TERRAINMAP_FILENAME + "/";
        //最后加上当前文件夹的名称,最后的文件夹名称和当前物体的名称一致
        strPath += gameObject.name + "/";
 
        return strPath;
}
    /// <summary>
    /// 获取所有地图贴图文件路径
    /// </summary>
    /// <param name="info"></param>
    private void GetAllFile(FileSystemInfo info)
    {
        if (!info.Exists)
        {
            Debug.Log("该路径不存在!");
            return;
        }
        DirectoryInfo dir = info as DirectoryInfo;
        if(null==dir)
        {
            Debug.Log("该目录不存在!");
            return;
        }
        FileSystemInfo[] si = dir.GetFileSystemInfos();
        for (int i = 0; i < si.Length; i++)
        {
            FileInfo fi = si[i] as FileInfo;
            if(null!=fi&&IsImage(fi.Extension))
            {
                listStrFileName.Add(fi.FullName);
            }
            else
            {
 
            }
        }
    }

然后根据路径列表使用WWW逐个加载图片
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
   /// 根据文件路径加载图片
   /// </summary>
   private IEnumerator GetAllTexture()
   {
       curFilePath = GetFilePath();
       DirectoryInfo tempInfo = new DirectoryInfo(curFilePath);
       GetAllFile(tempInfo);
       foreach (string item in listStrFileName)
       {
           WWW www = new WWW("file://" + item);
           yield return www;
           //先得到最后的图片文件名
           string[] tempAllFileName = item.Split(Path.DirectorySeparatorChar);
           //去掉后缀
           string tempStrKey = tempAllFileName[tempAllFileName.Length - 1];
           tempStrKey = tempStrKey.Substring(0, tempStrKey.Length - 4).Trim();
           if(null!=tempStrKey&&!dicTextures.ContainsKey(tempStrKey))
           {
               dicTextures.Add(tempStrKey, www.texture);
           }
           else
           {
               Debug.LogError("图片文件名为空或者有相同的文件名!");
               continue;
           }
           if(dicSubTerrainMat.ContainsKey(tempStrKey))
           {
               dicSubTerrainMat[tempStrKey].SetTexture("_MainTex", www.texture);
           }
           else
           {
               Debug.LogError("文件名"+tempStrKey+"在材质列表中没有找到对应的材质名!");
           }
       }
       isLoadAllTexture = true;
       Debug.Log("Load All Terrain Texture!");
   }

效果图如图所示:一开始显示的是默认模糊处理的贴图,在加载的过程中帧率非常低,造成卡顿。
Unity实用小工具或脚本—加载外部图片的三种方式
二、IO文件流加载

       首先,同样的是加载文件路径。然后开启另外一个线程将图片的字节流加载到字典中。
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  /// <summary>
    /// 根据文件路径读取图片并转换成byte
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static byte[] ReadPictureByPath(string path)
    {
        FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
        fileStream.Seek(0, SeekOrigin.Begin);
        byte[] binary = new byte[fileStream.Length];
        fileStream.Read(binary, 0, (int)fileStream.Length);
        fileStream.Close();
        fileStream.Dispose();
        fileStream = null;
        return binary;
}
然后,加载整个文件列表里的图片到字典缓存中。
    /// <summary>
    /// 根据文件路径加载图片
    /// </summary>
    private void GetAllTerrainTex()
    {
        DirectoryInfo tempInfo = new DirectoryInfo(curFilePath);
        GetAllFile(tempInfo);
        foreach (string item in listStrFileName)
        {
            byte[] tempImageBuffer = ReadPictureByPath(item);
            if(null==tempImageBuffer)
            {
                Debug.Log("读取路径"+item+"图片失败!");
            }
            string[] tempAllFileName = item.Split(Path.DirectorySeparatorChar);
            //去掉后缀
            string tempStrKey = tempAllFileName[tempAllFileName.Length - 1];
            tempStrKey = tempStrKey.Substring(0, tempStrKey.Length - 4).Trim();
            if (null != tempStrKey && !dicImageBuffer.ContainsKey(tempStrKey))
            {
                dicImageBuffer.Add(tempStrKey, tempImageBuffer);
            }
            else
            {
                Debug.LogError("图片文件名为空或者有相同的文件名!");
                continue;
            }
        }
        isLoadAllTexture = true;
        Debug.Log("Load All Terrain Texture!");}

最终的加载效果图如图所示:同样会导致帧率很低造成卡顿,但是这种方式有一个好处是可以先将图片使用其他线程加载到缓存中,造成卡顿是因为将字节流转成Texture2D
Unity实用小工具或脚本—加载外部图片的三种方式 
[C#] 纯文本查看 复制代码
?
 
1
Texture2D tempTex = new Texture2D(TERRAIN_MAP_DI, TERRAIN_MAP_DI);
             tempTex.LoadImage(item.Value);

这里的地形贴图都很大,就算单张图片压缩到4M左右,使用LoadImage方法还是会卡顿。而这个方法又只能在主线程里使用,也即不能在新开的加载线程里使用。Unity对这类的类都限制了,只能在主线程使用。
三、Assetbundle打包再加载
   首先,要将贴图导入到Unity工程里面,然后对一个文件夹里的贴图进行打包。.0之后的打包方式比较简单,如图所示:
Unity实用小工具或脚本—加载外部图片的三种方式 
先选中要打包的贴图,在Inspectors面板下有个AssetBundle,这里新建你要打包成集合的名字,我以文件夹的名字新建了一个标签“40_73.4_36.69237_77.61875”。选中文件夹里其他图片,将它们都设置成这个标签。这样就可以将整个文件夹里的图片打包成一个集合,方便一次性加载。

       打包的代码和简单,可以在Editor文件下,创建一个编辑脚本,代码如下:
[C#] 纯文本查看 复制代码
?
 
1
2
3
4
5
  [MenuItem("Example/Build Asset Bundles")]
    static void BuildABs()
    {
        // Put the bundles in a folder called "ABs" within the Assets folder.
        BuildPipeline.BuildAssetBundles("Assets/Assetbundle", BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows);
}

对比以前的方式,新的打包方法确实简化方便了很多。接下来将打包好的Assetbundle文件夹放到工程的同级目录下,加载Assetbuudle的代码如下:
[C#] 纯文本查看 复制代码
?
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <summary>
    ///异步加载地形的贴图
    /// </summary>
    /// <returns></returns>
    private IEnumerator SetAllTexAsy()
    {
        yield return null;
        var bundleLoadRequest = AssetBundle.LoadFromFileAsync(curFilePath);
        yield return bundleLoadRequest;
        var myLoadedAssetBundle = bundleLoadRequest.assetBundle;
        if (myLoadedAssetBundle == null)
        {
            Debug.Log("Failed to load AssetBundle!");
            yield break;
        }
        foreach (var item in dicSubTerrainMat)
        {
            var assetLoadRequest = myLoadedAssetBundle.LoadAssetAsync<Texture2D>(item.Key);
            yield return assetLoadRequest;
 
            Texture2D tempTex = assetLoadRequest.asset as Texture2D;
            if (null == tempTex)
            {
                Debug.Log(gameObject.name + "Assetbundle里没有对应" + item.Key + "的贴图");
            }
            else
            {
                item.Value.SetTexture("_MainTex", tempTex);
            }
        }
        myLoadedAssetBundle.Unload(false);
}

加载得到集合包,然后根据每个图片的文件名得到具体的图片。最终的效果图如图所示:
Unity实用小工具或脚本—加载外部图片的三种方式
可以看到帧率几乎不受任何影响,但是会在加载到显示的过程中停留2秒时间左右,对于程序的卡顿,这个时间的停滞还是可以接受的。
四、总结

虽然,第三种方式在加载的时候比较流畅,不会影响到主线程的执行。但是,打包的过程也是一个很繁琐,且需要细致认真的工作。总的来说第三种方式最好的,前面两种方式应该用在其他方面,几天的研究算是作为一个记录,或许以后就能用的上。