成长,也许从来都不是一件容易的事儿。
- 内存优化
- 图形学、渲染优化
- AssetBundle资源加载和卸载
- C#高级特性 (闭包和匿名函数的区别)
闭包和匿名函数的区别:
解释1: 闭包是指有权访问另一个函数作用域中的变量的函数。
解释2:闭包是一个函数和该函数被定义时的词法环境的组合,一个函数的返回值是一个函数引用,外部调用这个函数的引用时,需要堆栈中保留函数执行的变量环境,这种关系就叫做闭包。
匿名函数,一般用到匿名函数的时候都是立即执行的。通常叫做自执行匿名函数或者自调用匿名函数。常用来构建沙箱模式,作用是开辟封闭的变量作用域环境,
闭包跟函数是否匿名没有直接关系,匿名函数和具名函数都可以创建闭包。
Unity5.X的AB机制
http://www.cnblogs.com/murongxiaopifu/p/5629415.html#autoid-3-3-0
ChunkBasedCompression 新增LZ4进行压缩,加载时不需要加载整个AB,只加载对应数据块儿。
新增Manifest清单文件记录AB依赖关系,不必要再需要人为的处理PopAssetDependencies/PushAssetDependencis
AB压缩格式:
Lzma 默认的ab压缩格式,压缩比很高,但是加载时需要整个加载ab文件,会影响运行时加载速度,形成较大的内存开销。
不压缩,不压缩的AB加载速度快,但是体积大。
Lz4时5.3版本之后新增的压缩选项,压缩比没有lzma高,但优点在于加载时不需要整个加载AB,因此在加载速度和内存上有较大优势。
随包的AB建议lz4压缩,兼顾文件大小和加载速度,网络更新的ab建议lzma压缩,高压缩比可以减少数据流量。
www(url)的加载方式,加载完成后会在内存中创建较大的webStream,通常为原AB大小的3-5倍,便于后续加载直接从内存加载。
www.loadFromCacheOrDownload()改方法的优势在于能将解压缩之后的ab文件存入磁盘作为缓存,只会在内存中创建较小的serilizedFile。而后的AssetBundle.loadAsset直接通过IO从磁盘缓存中获取。
AssetBundle.loadFromFile 与老版本CreatFromFile机制上的区别在于,之前版本只支持从解压后的AB文件创建资源,新版本对于无压缩或者LZ4方式的AB文件可以直接创建AssetBundle对象。
UWA关于AssetBundle的建议:
1/ 对于需要常驻内存的Bundle文件来说,优先考虑减小内存占用,因此对于存放非Prefab资源(特别是纹理)的Bundle文件,可以考虑使用WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile加载,从而避免WebStream常驻内存;
2/ 而对于存放较多Prefab资源的Bundle,则考虑使用new WWW加载,因为这类Bundle用WWW.LoadFromCacheOrDownload加载时产生的SerializedFile可能会比new WWW产生的WebStream更大。
3/ 对于加载完后即卸载的Bundle文件,则分两种情况:优先考虑速度(加载场景时)和优先考虑流畅度(游戏进行时)。
1)加载场景的情况下,需要注意的是避免WWW对象的逐个加载导致的CPU空闲,可以考虑使用加载速度较快的WWW.LoadFromCacheOrDownload或AssetBundle.CreateFromFile,但需要避免后续大量地进行Load资源的操作,引起IO开销(可以尝试直接LoadAll)。
2) 游戏进行的情况下,则需要避免使用同步操作引起卡顿,因此可以考虑使用new WWW配合AssetBundle.LoadAsync来进行平滑的资源加载,但需要注意的是,对于Shader、较大的Texture等资源,其初始化操作通常很耗时,容易引起卡顿,因此建议将这类资源在加载场景时进行预加载。
只在Bundle需要加密的情况下,考虑使用CreateFromMemory,因为该接口加载速度较慢。
尽量避免在游戏进行中调用Resources.UnloadUnusedAssets(),因为该接口开销较大,容易引起卡顿,可尝试使用Resources.Unload(obj)来逐个进行卸载,以保证游戏的流畅度。
AB加载卸载的操作流:
各种加载方式的内存、性能对比图:
内存优化&流畅度(fps)优化
代码GC和卡顿:
字符串拼接、
循环内部New对象、
反射减损、FieldInfo/MethodInfo缓存
循环for/foreach/iterator、foreach效率最差
装箱拆箱,
缓存所需的动态MonoBehaviour组件,或者在频繁instantiate/destroy应考虑pooling
异步waitfor进行缓存
不再运行时大量使用闭包(闭包效率?)
尽可能重用容器,以及容器操作的合理性(dic/list扩容,移除顺序)
不要大量使用delegate的-=/+=,
当你的对象订阅了外部的事件,而又没有取消订阅,那么该对象是不会被GC回收的!这会造成很恐怖的问题,产生了几千万个对象没法被回收。而且匿名函数无法取消订阅,就算是两个代码格式完全一样的匿名函数,编译器生成的也是两个不同的方法签名。链接:http://www.cnblogs.com/buptzym/archive/2013/03/15/2962300.html
减少不必要的 shader varients
关于代码GC的原理和优化思路:
https://www.cnblogs.com/zblade/p/6445578.html
内存优化:
如果有较大的assetBundle,不要使用lzma格式,使用lz4
确保AssetBundle里没有重复的资源(AB依赖处理)
确保资源正确加载和卸载
不需要RW的texture&mesh关闭RW
不需要的texture关闭mipmap
模型顶点的normals和tangents,应该在导入设置或playersetting里去除
确保大多数纹理使用压缩格式(后处理工具相关)
确保贴图尺寸合理、确保mesh面数合理
AnimationClip的分情况加载
流畅度优化:
减少meshColider的使用
适当情况下使用static batching/dynamic batching/自定义 batching
UI图集的使用,动静态分离,避免材质穿插、
使用合理的光照方案
使用合理的后处理
合理的骨骼数
合理的半透明物体占屏比例,避免过多OverDraw
避免使用Unity自带的地形系统
机型适配策略及品质控制
画面质量控制:
**基本品质控制
实时阴影品质、骨骼动画质量、纹理质量、垂直同步
**角色品质控制
根据和玩家主角的距离改变角色的渲染,如:是否开启描边、角色阴影模式实时/投影片儿/关闭、根据距离控制动画更新频率
**其他
分辨率整体缩放、根据对象layer控制渲染距离、根据配置切换抗锯齿类型、后处理开关:FXAA(快速抗锯齿/bloom/调色/镜头精神)
角色渲染优化:
**角色渲染合并
****纹理合并(通过相机拍摄RT合并、通过获取TextureRawData合并、通过GraphicsCopy合并)
支持不同平台下支持不同的纹理压缩格式,shader实际上对不同类型图的精度要求不同。
****Mesh合并
Unity的Mesh合并方法的问题:控制力不足、submesh合并会复制顶点,顶点数据翻倍
自定义mesh合并:获取所有mesh顶点、法线等数据、重新拼接成一个mesh。
****重新绑定动画:skinMeshrender设置正确的bones、mesh有正确的BindPose--骨骼对应顶点信息。
bindpose计算量巨大,使用ComputeShader将计算量推给GPU。
bindPose预先烘培mesh数据
部分不适合做合并的角色组件不参与合并。
总归是在内存和DrawCall中寻找平衡。
场景画面品质控制
****在较低配置上减少细节的渲染
****减少难以观察到的细节渲染
****在不同的品质上真的做出性能差异
****在低端机上也能有更好的画面
****在高端机上将性能集中到更有意义的地方
具体手段:植被分组,赋予不同优先级,场景特效根据重要性设置不同layer,决定特效细节的展示分级、氛围特效控制,设置相机最远渲染距离。
lua和c#交互
c#和lua交互是怎么实现的?
从最早的lua纯反射调用C# ---> 纯C#实现的lua虚拟机 ---->luajit+c#静态lua导出方案
猜想1:lua虚拟机有lua堆栈的完全控制权,lua调用C#需提供C#函数的桥接代码,并将其注册到lua虚拟机中,即压入lua栈。
c#调lua,首先lua虚拟机从lua栈中获取函数信息,然后在lua编译器在Global Table查找luafunction,并运行返回结果,将结果压入lua栈,宿主语言可以访问lua栈中的内存数据,返回给c#。
所以最重要的是搞清楚lua虚拟机工作原理和lua堆栈访问。
tips:如何在lua调用中体现c#语言中的高级特性,取决于lua框架(xlua、tolua)的实现?
热更框架如何实现hotfixed?
在需要热更的类或者方法上打上[hotfix]标签,然后在lua调c#的桥接代码中获取热更标识,如果出现标识则执行lua补丁代码,否则执行c#原生代码。
更舒服的开源方案InjectFix:
https://blog.youkuaiyun.com/tencent__open/article/details/100789546