您的位置:首页 > 移动开发 > Unity3D

Unity5的AssetBundle与NGUI的坑

2015-04-09 22:19 267 查看
Unity5的AssetBundle机制相较于以前的改动非常大,它把以前繁杂的push pop依赖和各种自定义名称的步骤全简化了~具体新的用法在这里我就不提了,官方的Demo或是在网上都能看到新机制的用法,这里我只想说下我遇到的新AssetBundle与NGUI的坑。

这个坑很容易发现,当UIFont或是UIAtlas与使用它们的Prefab不在一个Bundle里时,加载Prefab就会出现scripit misssing的问题:


在Editor下 unity可能还会找到原代码给你Fixing了,但是在实机上是百分百会导致UI显示不出来的。虽然我可以把UIAtlas或UIFont全部扔到prefab的bundle里,但是如果有很多prefab引用同一个UIAtlas或UIFont的话,那bundle的大小就不忍直视了。所以有没有一种方法可以继续保持prefab对UIAtlas或UIFont的依赖关系且不会出错呢?

找解决方案之前,我们得先了解出错的原因,NGUI官网上有人总结得很精辟,我直接引用了:“Loading will
fail if exist game object that have component with a script property type and value is prefab's script”,需要给他补充一点就是,只有2个prefab不在同一AssetBundle时loading才会出问题。大概就是下图这样一个脚本,引用了另一个AssetBundle的Prefab(即Sphere)。



好了,了解出问题的本质之后,我们来试着找一下解决方法

1、NGUI官网上有人给出了一种方法,就是修改NGUI底层。把代码里脚本属性全换成GameObject。

如果以前是Atlas

class UISprite
{
[SerializeField] UIAtlas mAtlas; // It's prefab's script. Not works for AssetBundle
}


那么就把它替换成GameObject,然后通过GetComponent<UIAtlas>()来获得UIAtlas

class UIScript
{
[SerializeField] GameObject mAtlasPrefab; // for AB, use mAtlasPrefab.GetComponent<UIAtlas>()...
}


这个问题确实可以从本质上解决因为自定义序列化脚本带来的问题,不过改NGUI底层实在是个不小的工作量,对应类的Inspector编辑器都要修改,所以我稍微试了一下之后马上就放弃了。

2、同事告诉我一种动态赋值的方法,因为我们在做UISprite时不给UIAtlas赋值的话,unity是不会报错的,所以我们可以写一个脚本,当运行时去动态加载对应的Atlas的Bundle,然后动态给UISprite附上UIAtlas以及spriteName。

具体做法是脚本里可以设置BundleName,AtlasName,SpriteName,以及要赋值的UISprite。


然后我们通过BundleName从一个预先设定好的位置(newPathName)取出atlas的AssetBundle,然后根据AtlasName来load出我们需要的Atlas的prefab赋值给m_sprite,然后把spriteName附上,代码大致如下:

AssetBundle bundle = AssetBundle.CreateFromFile(newPathName);
m_atlas = bundle.LoadAsset<GameObject>(assetName);
m_sprite.GetComponent<UISprite>().atlas = m_atlas.GetComponent<UIAtlas>();
m_sprite.GetComponent<UISprite>().spriteName = m_spriteName;
bundle.Unload(false);
把这个脚本挂在面板上,设置好各种名称,然后把对应Sprite的UIAtlas设为None(这步是这个方法的关键),打包。加载时,脚本会动态把UIAtlas赋值上去,成功运行。

不过这种方法的缺点也是很明显的,要在打AssetBundle包之前把UIAtlas设为None,且挂上脚本手动输入名称。这会很大地影响UI界面的开发效率,而且一个面板里公共的Atlas如果很多的话,那得挂多少个动态加载Atlas的脚本,我还没算公共的UIFont呢。只能说这个方法可以绕过问题,但是不适于处于开发阶段的项目。

3、最终解决方案

后来认真想了一下问题的本质,引用的prefab要在同一个bundle下,而像material、texture这类component可以分到不同bundle下。那我是不是可以把Atlas的三部分(prefab、texture、material)分开,让prefab存在于所有用到的bundle
993d
里,而把texture和material单独提出来呢?



如果是旧版的AssetBundle机制,想按这种方式打包代码里估计得写不少依赖相关的判断逻辑。恰好,新的AssetBundle机制通过命名就可以实现这种打包关系:直接把Common Atlas.mat和Common Atlas.png命名为同一个bundle,而Common Atlas.prefab不进行命名(None)即可。当然如果想用脚本自动化命名的话估计还是得写不少判断逻辑。

这样打包之后每个Panel的AssetBundle包只比不加入Common Atlas.prefab前大了几KB,即解决了新机制的打包依赖问题,又没有过大的增加包体容量,一举两得。至此问题解决,下一步就是写一个自动化命名和检测的脚本了。毕竟不能总是人工去命名,而且也要防止其他人误操作。

PS:有人按照方法试了一下没成功,这里需要注意的是,在加载UIPanel的Bundle时需要先加载其依赖包也就是CommonAtlas所在的Bundle



不得不说,新的AssetBundle还有很多不成熟的地方,比如检测是否需要重新打包的逻辑,未进行bundle命名的文件不出现在Manifest里等等,想用让人完全放心的AssetBundle的话,估计还得等上一段时间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Unity5 NGUI AssetBundle