异步加载资源是现在的主流技术,于是先废话一段。
先从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应该是常驻内存都没问题的类型。