1. Texture,都去掉alpha通道,作为背景展示的图片,基本都没有透明要求,有特殊要求的则放到atlas里面
a. Loading图这类需要比较精细的,则把图片设置为Automatic TrueColor,设置真彩色,保证不失真
b. 地图、缩略图、UI背景图等等要求不精细的,则可以设置为自动压缩格式(有压缩情况,都需要图片宽高尺寸是2的幂,可以在Advance里面设置toNearest)
注意:ios下会自动把图片宽高拉伸为2的幂次方尺寸,这样会导致图片显示失真,解决办法是制作图片的时候就保证是2的幂大小。如果图片显示的区域确实不能做出2的幂大小,可以用补黑边的方式把图片做出2的幂大小,设置图片的时候,就需要调整图片的UV
要点:android下,带alpha通道的图片,自动压缩是以ETC2 8bit的方式压缩的,不带alpha通道,是压缩成ETC 4bit的格式(ETC2 支持alpha通道),ios下是压缩成PVRTC 4格式。手机硬件对各种格式图片的加载效率不一样,RGBA32是最慢的。所以需要对图片进行处理,改压缩方式,ETC和pvr是加载最快的。
2. animation clip
动作片段的优化,主要是减少动作的无用帧,就是两个Keyframe之间的旋转或者位移或者缩放的差别很小很小,则可以把Keyframe去掉,这样一个几百k的动作片段优化下来可能只有几十k,还是相当可观的
1 static List<Keyframe> TrimScaleKeyframes(AnimationClipCurveData curve) 2 { 3 List<Keyframe> keyframes = __keyframes; 4 float maxValue, minValue, averageValue; 5 keyframes.Clear(); 6 List<KeyframeSample> samples = TakeSamples(curve, out maxValue, out minValue, out averageValue); 7 int depth = curve.propertyName.Split('/').Length; 8 var kcount = samples.Count; 9 keyframes.Add(samples[0].keyframe); 10 bool lastIsRemoved = false; 11 Keyframe lastKeyframe = new Keyframe(); 12 var epsilon = m_scaleError; 13 float error = 0; 14 for (int k = 1; k < kcount - 1; ++k) 15 { 16 var kf = samples[k].keyframe; 17 var diff = samples[k].pos.y - keyframes[keyframes.Count - 1].value; 18 error += diff; 19 if (Mathf.Abs(error) > epsilon) 20 { 21 if (lastIsRemoved) 22 { 23 keyframes.Add(lastKeyframe); 24 lastIsRemoved = false; 25 } 26 keyframes.Add(kf); 27 error = 0; 28 } 29 else 30 { 31 lastIsRemoved = true; 32 lastKeyframe = kf; 33 } 34 } 35 keyframes.Add(samples[kcount - 1].keyframe); 36 if (keyframes.Count == 2) 37 { 38 if (Math.Abs(keyframes[0].value - keyframes[1].value) < Mathf.Abs(m_positionError)) 39 { 40 keyframes[0] = KeyframeUtil.GetNew(keyframes[0].time, keyframes[0].value, TangentMode.Linear, TangentMode.Linear); 41 keyframes[1] = KeyframeUtil.GetNew(keyframes[1].time, keyframes[1].value, TangentMode.Linear, TangentMode.Linear); 42 } 43 } 44 return keyframes; 45 }
3. 检查无效的脚本
很多时候美术制作资源的时候,为了即时看到效果,会把脚本给挂上去,但是制作完以后通常会忘了把脚本卸载。通常这些资源会打包成bundle资源,这个时候脚本就会缺失,加载bundle的时候会一直报警告消息,这样会增加CPU的开销,严重的话会引起卡顿。所以检查无效的脚本是很有必要的。不过有哪些无效脚本,这个就需要统计了,是个体力活
4. 特效的检查
游戏里面的特效非常多,几百上千个。而特效里面引用相同材质的情况是很多的,如果不把这些材质的剔出来,会造成很多资源的冗余。一个最简单的办法就是把所有特效的材质贴图都抠出来打包成一个bundle文件,加载特效的时候动态加载材质贴图,还是会减少几MB内存的。
还有一点,特效贴图很多都使用了alpha通道,渲染的时候容易造成重绘情况(alpha test和alpha blend),会加大CPU和开销。解决办法是拆分贴图的alpha,shader里面做alhpa的混合。这样会时游戏更加流畅一点,就是实现起来稍微麻烦一些
5. 场景处理
纹理合并+静态批处理,lightmap贴图设置自动压缩。一个是减少drawcall,一个是减少内存占用。静态批处理的
6. atlas贴图
和特效贴图类似,也是拆分alpha通道,在shader里面做混合。不过有一个很大的好处就是,atlas都是需要显示很精细的,做贴图压缩处理的时候基本都是使用true color,一个1024x1024的 RGBA32的图片在内存中会占用4MB的大小。做了alpha通道拆分后,就可以把alpha图片和RGB图片做自动压缩成ETC 4bit格式,占用的内存会大大减少。对于游戏同时会使用几个atlas的情况下,这种处理方式一个atlas大概就能减少2到3MB的内存,几个下来,20MB都有可能,还是很客观的
7. GameUI 游戏窗体界面
窗体界面在打包的时候把挂载的脚本去掉,加载窗体的时候动态挂上去。还有一点,打包的时候处理资源的依赖关系是很麻烦的,所以一劳永逸的办法是,把窗体引用的atlas和font都去掉,在加载窗体的时候再动态挂回来,有点像脚本的处理方式,只是代码实现上稍微复杂点,还是很简单的。这样也不用担心窗体打包成bundle后过大的问题。200个窗体,通过这样打包bundle,总共5MB左右,atlas和font单独打包成bundle。还有一点,有些项目是把atlas、font、UI打包成一个bundle资源,这样也可以解决重复依赖的问题。但是这样会增加内存开销,毕竟所有atlas、font、UI都一次性加载完了。所以一个文件打成一个bundle,在加载的时候可以使内存过度比较平缓。
8. 角色模型和怪物模型处理依赖
角色模型通常都会有几套动作,所以打包角色模型时都需要先把角色上的动作给去除掉,游戏中在动态挂上去。这样处理需要动作文件命名规范,有可能会用到配置表。
怪物、npc、坐骑等模型和动作基本都是一一对应的,不存在冗余的情况,所以不用拆分动作,直接全依赖打包
9. 音效、texutre不需要考虑依赖情况,直接打包。不过,因为这类资源本身都是经过压缩的,打包的时候可以尝试选择不压缩打包的方式打成bundle,加载bundle的时候就是加载的没有压缩的资源,可以提高加载资源的速度
10. 场景打包,unity 5.0以后加入了LZ4的压缩方式,对比LZMA的压缩方式,在加载速度上提升是非常大的,我测试过加载场景,使用LZMA的bundle同步加载时间是0.8s,使用LZ4的bundle加载的时间只有0.005s,快赶上不压缩的方式了。一个场景文件通过LZMA压缩后的大小是10MB, LZ4的大小是15MB,不压缩的大小就是25MB。所以个人推荐还是使用LZ4的方式打包场景,不过也不是所有的场景都需要用LZ4,毕竟还是大了有大概0.3倍的大小。所以可以把游戏的前面几张场景使用LZ4打包,后面的用LZMA。这样,玩家开始玩的时候还是能有不错体验的。最终怎么取舍还是看项目具体的需求吧
如果资源采用不压缩的方式打包,然后再对总资源进行zip压缩,包体最终是小了,不过提交到ios后台看到的包体大小却是zip解压后的大小。目测ios后台是会自动识别zip压缩的。一个办法是自己写压缩算法,个人觉得还是比较麻烦。在手机硬件提升越来越快的情况下,没有必要为了低端机做不压缩的处理。中高端机对于不压缩的资源加载和LZ4的资源加载,几乎无感,孰高孰低,仁者见仁吧