现在的位置: 首页 > 综合 > 正文

关于Unity资源异步加载的研究

2013年04月27日 ⁄ 综合 ⁄ 共 2969字 ⁄ 字号 评论关闭

异步加载资源是现在的主流技术,于是先废话一段。

先从DX说起

最早的想法是即使Unity 不支持异步加载,也可以用C#的多线程机制自己实现,不过这个想法明显行不通。玩过DX的都知道异步加载需要涉及到显示设备的管理问题,管理不好就会直接崩溃。从磁盘到文件这点做异步是一点问题也没有,最早我自己尝试的时候也是这样,后来发现没多少用处,因为最费时的其实不是加载,而是资源的初始化!

是的,从内存到显存的过程其实是很费时的。DX有多种管理显存的模式,但基本上最常用的就是直接到显存或者托管,直接到显存后就无法对显存进行修改,而且当出现设备丢失的时候也无法找回,托管的好处就是在设备丢失的时候体现。比较直接到显存和托管,明显前者要快很多。

其实废话说多了也就跑题,简单来说,显示设备没有办法多线程操作(DX9),当你要初始化显存的时候,画面卡顿问题难以解决,只能说此时可以让逻辑线程正常工作,其实也不排除使用了各种黑科技,理论上这些问题都可以解决,就是难度问题而已。据说DX11默认支持多线程,指令可以乱序输入,具体我没去研究过也不知道,首先XP占主流这个事实已经让DX11变成可望而不可及的黑科技。

不管怎么说,结论是资源的初始化一般都只能在主线程了,所以Unity绝对的异步估计是不行了。只能说去改善。为什么突然想到去弄这个?其实我目前并没有用到这个的必要,只是看到有人说加载大场景的时候会卡住,希望有不卡住的方案,有人说用WWW加载就可以,实际上测试之后发现完全不行。因为我手头也没什么大场景,所以没测试过异步加载场景那个,那个据说也是不行的。那么翻来翻去,能用的貌似就只有AssetBundle,不过这货光看API不懂怎么用,试了几种方法都没法加载。算了,先放着。

百度了一下Unity异步加载,虽然结果很多,结果发现都全是同一篇文章,而且重点不讲,讲逻辑,懂得原理了逻辑只是时间问题,不懂原理就无法掌控。

于是找Google去,结果最终找到的方案不是别的,就是Unity的官方文档。http://docs.unity3d.com/Documentation/Manual/LoadingAssetBundles.html,是的,就是这货。

就完全没意思不过注意看就会发现加载只能加载Unity3D文件,否则后面的LoadAsync 就完全没意思,LoadAsync 明显是从包里读取资源。于是就涉及到怎么打包了,打包官方也给出方法了,但是需要Pro版本,这里直接贴出来。

using UnityEngine;
using UnityEditor;
public class ExportAssetBundles
{
    [MenuItem("Assets/Build AssetBundle From Selection - Track dependencies")]
    static void ExportResource()
    {
        // Bring up save panel
        string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "unity3d");
        if (path.Length != 0)
        {
            // Build the resource file from the active selection.
            Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
            BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, path, BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets);
            Selection.objects = selection;
        }
    }
    [MenuItem("Assets/Build AssetBundle From Selection - No dependency tracking")]
    static void ExportResourceNoTrack()
    {
        // Bring up save panel
        string path = EditorUtility.SaveFilePanel("Save Resource", "", "New Resource", "unity3d");
        if (path.Length != 0)
        {
            // Build the resource file from the active selection.
            BuildPipeline.BuildAssetBundle(Selection.activeObject, Selection.objects, path);
        }
    }
}

根据这个方法,我打包了60张很大的图片(768*768),总大小24M,打包后只有3M,然后在游戏进行时进行加载,看看画面是不是会有卡顿。大致的代码就是这样。

        var www = WWW.LoadFromCacheOrDownload(@"file://E:\U3DTemp\big.unity3d", 5);

        yield return www;
        
        if (www.error != null)
        {
            Debug.Log(www.error);
            yield break;
        }
        print("Download Ok");
        var myLoadedAssetBundle = www.assetBundle;
        for (var i = 0; i < pictureNumber;i++ )
        {
            var number = string.Format("{0:D4}",i);
            print(number);
            var request = myLoadedAssetBundle.LoadAsync(number, typeof(Texture2D));
            yield return request;
            textures[i] = request.asset as Texture2D;
        }
        myLoadedAssetBundle.Unload(false);

测试的结果是加载过程中并无卡顿,加载完之后对动画进行播放,也不会出现卡顿。不过加载完60张耗时挺长,我估计如果不是异步加载,应该很快就能完成。

不过问题并未结束。我测试了一张4M的PNG,图片大小2984*4093,对其进行打包后只有850K,虽然不知道具体是怎么打包的,但是压缩包相当高。如果用作普通纹理进行加载并无问题,然后我用GUI渲染出来,发现图片太模糊,于是把图片类型改成GUI的

然后打包,再次加载测试。问题出现了,虽然GUI图片清晰显示了,但这次就出现了明显的卡顿,异步加载完全无用。再看打包后大小,并无大多改变,也就是如果用GUI纹理,那么初始化的时候就非常消耗资源,虽然不清楚其中的原理,但明显GUI为了让图片清晰显示,可能采样上有所不同。另外MAX SIZE始终都是4096,并没有用默认的1024。

之后试了两个模型,基本上没什么问题,也没有任何卡顿,估计是大部分时候,这样的异步加载都是有用的,GUI那个,可能用第三方GUI可以解决,当然我也没测试过加载不是很清楚,不过GUI的话,资源一般不会太大吧,除非没控制好了,我一直认为GUI应该是常驻内存都没问题的类型。

抱歉!评论已关闭.