版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、图片格式 -1DrawCall -2l 图片格式:NGUI生成的图集的图片格式是PNG格式,但是无论是什么格式的图片 Unity都会自己生成一套格式,并且打包的时候不会用文件夹下面的格式,而是Unity自己格式。如果你用的UITexture 你可以对每一张图来修改格式,比如颜色数比较少的图片可以使用16bit,如果没有透明,可以使用pvr或者etc 这样图片会小很多。如果是UISprite 只要有透明的 就必须使用RGBA32 要不然UI会很难看。除去 UITextuer和Atlas的图片之外(因为有透明),其余的贴图必须是2的幂次方。因为只有2的幂次方图片,并且没有透明通道的才会被压缩。Io
2、s会被压缩成pvr格式,Andriod会压缩成etc格式。压缩之后会小很多。人物贴图 场景题图 特效贴图 一定要是2的幂次方。贴图透明通道分离,压缩格式设为ETC/PVRTC 最初我们使用了DXT5作为贴图压缩格式,希望能减小贴图的内存占用,但很快发现移动平台的显卡是不支持硬件解压DXT5的。因此对于一张1024x1024大小的RGBA32贴图,虽然DXT5可将它从4MB压缩到1MB,但系统将它送进显卡之前,会先用CPU在内存里将它解压成4MB的RGBA32格式(软件解压),然后再将这4MB送进显存。于是在这段时间里,这张贴图就占用了5MB内存和4MB显存;而移动平台往往没有独立显存,需要从内
3、存里抠一块作为显存,于是原以为只占1MB内存的贴图实际却占了9MB!所有不支持硬件压缩格式都有相同的问题。解决方案: 现在Andriod硬件最广泛支持的时Etc ,IOS上支持的时PVRTC 。但这两种格式都是不带透明(Alpha)通道的,因此我们需要将每张原始贴图的透明通道分离出来,写进另一张贴图的红色通道里,这两张特图都采用Etc/Pvrtc压缩。渲染的时候 将两张贴图都送进显存。同时 我们修改了NGUI的shader 在渲染时第二张贴图的红色通道写入到第一张贴图的透明通道里恢复原来的颜色:1. fixed4frag(v2fi):COLOR2. 3. fixed4col;4. col.rg
4、b=tex2D(_MainTex,i.texcoord).rgb;5. col.a=tex2D(_AlphaTex,i.texcoord).r;6. returncol*i.color;这样,一张4MB的1024x1024大小的RGBA32原始贴图,会被分离并压缩成两张0.5MB的ETC/PVRTC贴图(我们用的是ETC/PVRTC 4 bits)。它们渲染时的内存占用则是2x0.5+2x0.5=2MB。l DrawCall 当两个renderQueue相邻的DrawCall使用了相同的贴图、材质和shader实例时,这两个DrawCall就可以合并。但需要注意的是DrawCall合并不见得会
5、提高性能,有时反而会降低性能 如果是UIGeometry为了渲染绘制准备数据,那么UIDrawCall其实是定义了渲染绘制需要的基本组件。这里拿煮菜做个比喻帮助理 解:UIGeometry好比为煮菜准备食材,UIDrawCall好比是煮菜的工具(锅,炉子等),UIPanel就是大厨了决定着什么时候该煮 菜,UIWidget(UILabel,UISprite和UITexture)是这道菜怎么样的最终呈现。UIWidget分别用UpdateGeometry和WriteToBuffers对UIGeometry的ApplyTransform和 WriteToBuffers进行封装调用,ApplyTra
6、nsform是根据UIPanel的坐标调整Vertices的坐 标,WriteToBuffers将UIGeometry的Vertices,UVs和Colors添加进UIPanel的Vertices,UVs和 Colors的BetterList中。 这里还有一个细节就是:UIGeometry中的Vertices,UVs和Colors的BetterList的buffer什么时候得到,因为这些都 是UIWidget或其子类的信息,所以在UIWidget的子类UILabel,UISprite和UITexture中OnFill函数生成 UIGeometry的BetterList的buffer。UIWid
7、get这个脚本中的两个函数:WriteToBuffers,OnFill,UpdateGeometry。WriteToBuffers和OnFill这两个函数都是将Vertices,UVs和Colors等add进参数的List中去,查看 WriteToBuffers的调用出发现其参数是UIPanel的Vertices,UVs和Colors,而OnFill的参数是 UIGeometry的Vertices,UVs和Colors。 WriteToBuffers只是对UIGeometry的封装调用,也就是说将UIGeometry的Vertices,UVs和Colors等信息add进UIPanel的对应Li
8、st中。UIGeometry的脚本,一直有一个疑问:UIGeometry的Vertices,UVs和Colors的List没有看到add方法的执行, 只有在WriteToBuffers被add。直到看到了OnFill才恍然大悟,虽然UIWidget的OnFill是虚函数,没有具体实现,看了下 UISprite重写的OnFill函数,就是把Vertices,UVs和Colors 添加到UIGeometry的Vertices,UVs和Colors中。所有UI组件的Vertices,UVs和Colors都汇集到UIPanel的Vertices,UVs和Colors去了,然后 UIPanel指定给UI
9、DrawCall渲染就行了UIWidget的一些实现细节,MakePixelPerfect():对gameObject的localPosition和locaScale进行微调和纠正。SetDirty():调用UIPanel的SetDirty()对组件的变更进行重建(rebuilt)。Uiwidge UIGeometry& UIDrawCall 的关系:UIWidget 中有两个变量UiDrawCall mDrawcall 和UIGeometry mGeo 的verts uvs cols 的BetterList,然后UiWidget 的UpdateGeometry函数对UIGeometry 的A
10、pplyTransform()和WriteToBuffer() 调用进行更新.每一个UIwidget都有一个UIGeometry 但是不都有一个UIDrawCall,而是通过Batch合并达到减少DrawCall的数量,UiDrawCall是有UiPanel生成的。 DrawCall的数量优化 根据上述描述可以得出一个结论:使用相同material的连续的Uiwidget(UILable UiSprite)公用一个UIDrawcall。通过这个结论我们可以得到一个解决DrawCall过多的问题,UIPanel生成DrawCall时是Fill()方法。Fill 方法对UiWidget.list进
11、行检测把使用相同Material的连续的Uiwidget合并生成一个DrawCall ,UIWidget.List 的排序是根据UiWidget的Depth进行的。所以解决方案有两种:1.修改UiWidget(UiLable UIwidget )的Depth,限定UIwidget.List的排序2.重写Uiwidget的CompareFunc() 方法。(重写UIWidget的CompareFunc也是可以的,按照Material的name优先排序,只有当material一样是才考虑depth进行排序:)3. 无重叠时自动重排。 绘制顺序按照Hierarchy 采用第二种方式减少DrawCal
12、l: MaterialleftMat=left.material; MaterialrightMat=right.material;if(leftMat=rightMat) if(left.mDepthright.mDepth)return1; elsereturn0; if(leftMat!=null&rightMat!=null) returnstring.Compare(leftM,rightM); if(leftMat!=null)return-1; if(rightMat!=null)return1; return(leftMat.GetInstanceI
13、D()rightMat.GetInstanceID()?-1:1;夹层问题:因为Material使用的Shader使用了透明,这样就不能做深度测试,也就是Mesh的“深度”是不影响的,这样最终的显示就跟Shader的 renderQueue有关了,即renderQueue越大,显示的越靠前面(重叠的图层,renderQueue越大,越靠前)。当然现在只有增加一 个DrawCall(如多使用一个UIPanel或用另外一个Material)来做到Material的夹层效果。 特效层级: 粒子系统的渲染顺序列默认为3000 而NGUI的渲染顺序默认是从3000开始,当有嵌套的Panel或者Deoth
14、更高的panel时,NGUI的渲染顺序会高于3000 解决方案:1.修改Ngui中的panel脚本中的默认RenderQueue 调整到3000以下,这样就不会挡住粒子特效。当窗口显示在特效上面时把窗口的RenderQueue调整到3000以上,就解决了。 2.使用另外一个摄像机显示特效,但是Ui窗口切换时不太好控制3.修改例子特效的shader中的RenderQueue的值(需要考虑特效中的层级关系)一级界面 二级界面 .浮动窗口1.不同图集 项目中做到复杂一些的界面,经常会用到多个图集,以技能界面为例,项目中常用的图片放到共用图集中, 这是一个图集,技能界面本身独有的元素,比如跟技能职业相
15、关的背景,算作第二个图集,还有一些技能图标, 图标单独归类到一个图集中,再一个就是字体的图集。基本一个界面如此分法,最多需要4个图集。NGUI的图集之间的 处理,默认是靠调整控件的Z值来区分的,但是这里他可以调整同一个图集每个一个控件的Z值,其实不是很好。经常会出现图层相互遮挡的情况,尤其对于控件比较多的界面,一段时间回过来再修改界面的时候,整个要崩溃。解决方案:在UIPanel中,为每一个Material添加一个layer的变量,当同一图层靠depth来决定前后关系,不同图层靠 layer来决定前后关系,在绘制UIDrawCall的时候,根据layer对跟节点做一定偏移。这样就能从Z值中解放
16、出来。 如果大家也有碰到图层的问题,可以参考这样的做法,以此种方法来处理图层关系,简单,做过项目的圈套UI,还未有不能解决的情况。l UI自适应 Scaling Style 的作用是制定UiRoot的缩放类型,如果是PixelPerfect ,Minimum Height 和Maximum Height 才起作用,scaling style 选择的是Pixelperfect 要对Minimum Height和Maximum Height进行设置。(如果是PixelPerfect缩放类型,当屏幕的分辨率大于Maximum Height,则以Maximum Height 为基础缩放,反之,如果屏幕
17、分辨率小于Minimum Height 则以Minimum Height为基础进行缩放。例如,如果屏幕高度为1000,而设置的Maximum Height值为800,则UI界面整体放大为原来的1000/800=1.25倍。)FixedSize : 跟Manual Height有关FixedSizeOnMobiles :跟Manual Height有关 只是针对IOS和Android上的判断,也就是说只有IOS和Android平台下FixedSizeOnMobiles才起作用.FixedSize或FixedSizeOnMobiles,则缩放只以Manual Height为参考,屏幕分辨率的高度值
18、不同于此设置值时,则根据其比例(即Screen Height / Manual Height)对整棵UI树的进行“等比”缩放(宽度的缩放比也是此比例值)。注释:如果设置FixedSize,UIWidget.height(以UiRoot默认值进行高度缩放)是不会改变的,不管实际屏幕分辨率的像素是多少,Anchor Stretch的背景图片高度始终是manualHeight.UIRoot下的UIWidget的height参数一直都是实际的值 UIRoot 是基于高度进行缩放的如何做以宽度适配。UIRoot是基于高度放缩的,即放缩的比例是以高度为参考的,所以UIRoot有一个manualHeight
19、的参数。那么对于横版游戏显然不行,要是 能实现基于宽度放缩。所以可以通过设置一个“manualWidth”的参数来做。比如我们项目中使用的是 1024 作为UI的宽屏尺寸,通过换算设置manualHeight的值:intheight=Mathf.Max(2,Screen.height);manualHeight=Screen.height*1024/Screen.width;/基于宽度的屏幕分辨率自适应注释:UIRoot其实就做了一件事情:根据Screen.height和UIRoot.activeHeight的比例来调整UIRoot的 loaclScal,从而保证UIWidget(UISpri
20、te,UILabel)可以按照其本身的大小进行设置,而不用经过复杂的换算过程。l 资源分离打包与加载资源分离打包与加载是最有效的减小安装包体积与运行时内存占用的手段。一般打包粒度越细,这两个指标就越小。但打包粒度也并不是越细就越好。如果运行时要同时加载大量小bundle,那么加载速度将会非常慢时间都浪费在协程之间的调度和多批次的小I/O上了。此需要有策略地控制打包粒度。一般只分离字体和贴图这种体积较大的公用资源。l 关闭贴图的读写选项 Unity中导入的每张贴图都有一个启用可读可写(Read/Write Enable)的开关,对应的参数是TextureImporter.isReadable。选
21、中贴图后可在Import Setting选项卡中看到这个开关。只有打开这个开关才可以对贴图使用Textuer2D.GetPixel,读取或改写贴图资源的像素,但这就需要系统在内存中保留一份贴图的拷贝,以供Cpu访问,但是一般游戏运行过程中不会有这样的需求,因此我们对所有贴图都关闭这个开关,只在中做贴图导入后处理(比如对原始提贴图分离透明通道)时需要打开这个选项。这样,上文提到的1024x1024大小的贴图,其运行时的2MB内存占用又可以少一半,减小到1MB。Texture图片空间和内存占用分析纹理大小影响:可以将其他(非二的幂 -“NPOT”)纹理大小用于 Unity非二的幂纹理大小通常占用的
22、内存稍多一点,由 GPU 进行读取的速度可能较慢,因此考虑到性能,最好尽可能使用二的幂大小。如果平台或 GPU 不支持 NPOT 纹理大小,则 Unity 会缩放纹理并将其填补为下一个二的幂大小,这甚至会使用更多内存并使加载更慢 Iphone:空项目 空间占用量42.3M ipa包10M10张1200*520 无压缩Texture 单张图占用量2.8M 空间占用两70.2M ipa包 22.9M10张1200*520压缩成1024*1024 PVRTC4 单图占用量0.5M 空间占用量47.3M ipa包 13.2M10张1024*1024 无压缩Texture 单图占用量4M 空间占用量82
23、.M ipa包14.6M10张1024*1024压缩为PVRTVC4格式 单张图占用量0.5M 空间占用量 47.3M ipa包 11.6M 综上所述: 1.2的N次方大小的图片会得到引擎更大的支持,包括压缩比率,内存消耗 打包压缩大小,而且支持的力度非常大。 2.减小图片的占用大小和内存方式有:图片大小变化(Maxsize),色彩位数变化(16位色 32位色),压缩PVRC格式 3.U3D对于图片的格式是自己生成的,而并不是你给它什么它用什么格式。一张1024*1024图在无压缩的格式下,他会被U3D无压缩文件像是存放,也就是说U3D 里的Texture Perview 里显示的占用大小*M
24、 不只是内存占用的大小,还是空间占用大小。Unity 图片压缩格式介绍: U3D 的内部机制为自动生成图片类型来替代我们的图片在图片的压缩方式需要进行谨慎的选择。 几种比较主要的压缩格式:RGBA32 BIT/AutomaticTurecolor (256*256 256k) 格式为无压缩最保真格式,最消耗内存和空间的格式。RGBA16 BIT/ 格式为无压缩16位格式,比32位节省一半的空间和内存,与Automatic16 相同。RGBA Compressed PVRTC 4bits格式为PVRTC 图片格式, 它相当于把图片更改了压缩方式新生成了一个图片来替换原来的图片格式贴图格式: 3D
25、游戏中贴图的的分类: UI贴图 ui是按照 1280*853比例出的图 3D场景贴图 主要是因为多重采样的缘故。3D游戏一般来说都是受摄像机远近大小改变而采取不同的采样大小,如果不设置多重采样的话,在远处有非常多的白色噪点。 2D游戏 所有都不需要勾选多重采样,具有3D性质的贴图,我们都需要勾选上GENERATE MIP MAPS,这样会使贴图大小增加25%这样。 正方贴图与非正方贴图也要区分:非正方贴图只有16位的压缩(相当于真彩色减半),所以最好游戏中都是正方的贴图。正方贴图:IOS: 普通不透明: RGB PVRTC 4 BITS 普通透明: RGBA PVRTC 4 BITS (256
26、*256 32kb)Androis: 普通不透明: RGB ETC 4 BITS (256*256 32kb) 普通透明:因为没有通用的兼容模式,所以一般情况是用RGBA 16 BITS 或是针对不同的GPU选择 DXT5/ATC8 BITS/ETC2 8BITS。如果技术支持,可以采用 RGB ETC 4 BITS 加一张Alpha 8的贴图来实现透明效果。 非正方形: 一般采用16位压缩,16位色会带来颜色损失,如果本来美术就按16位色画的话,就不会带来损失。日本的很多2D游戏都是采用那个16位来画的。少渐变 和艳色。 不透明贴图: RGB 16 BIT(256*256 128KB) 透明
27、贴图: RGBA 16 BIT (256*256 128KB)高清不压缩贴图: RGBA 32 BIT (256*256 256kb)对于不重要的贴图,模糊度低的贴图,建议不仅要采取像素压缩,还要直接压缩其大小。如光照贴图压到512或256。如背景原本1024的图直接压到256。玩家不注意到就可以了。注意: U3D所有图片的压缩格式都会以另一种方式存储,不会以你给的方式存储,只有你指定了某种格式,他才会转换成你要的格式。而且在Andriod 里的并不一定有效,因为Andriod的机型多,GPU的渲染方式也不一样,RGBA16 适应于所有机型l GameObject数量 场景中GameObjec
28、t的数量也是衡量性能的重要指标,频繁的创建 和销毁GameObjec 是非常耗时,场景中存在的GameObject会占用内存,如果GameObject上挂有物体的话,每个GameOject的脚本都需要实力化。 l 整理图集 整理图集的主要目的是节省运行时内存(虽然有时也能起到合并DrawCall的作用)。从这个角度讲,显示一个界面时送进显存的图集尺寸之和是越小越好。一般有如下方法可以帮助我们做到这点:1) 在界面设计上,尽量让美术控件设计成九宫格拉伸,即UiSprite的类型是Sliced.这样美术素材可以切出一张很小的图片在unity中做拉伸。当然一个九宫格也就意味着其定点数量会从4个增加到
29、16个(如果九宫格的中心格子采用Tiled做平铺类型的话,定点数会更多),构建DrawCall的开销会更大。但一般只要DrawCall安排合理就不会出问题2) 同样在界面设计上 尽量设计成对称的形式,这样在切图的时候美术切图的时候就可以只切一部分,我们在Untiy中将完整的图案拼出来,比如一个圆形图案我们可以只切四分之一,不过与上述第一点类似这样会增加定点个数,同时也会增加场景中GameObject的个数,因为GameObject的数量增多时会占用跟多的内存,所以只对尺寸较大的图案采用这种方法。3) 确保不要让不必要的贴图素材驻留内存,更不要在渲染时将无关的贴图素材送进显存。为此需要将图集按照
30、界面分开,按模块划分图集,一个界面中的UISprite也不要使用别的界面的图集。4) 数量庞大 且数量不固定的物品不要使用图集 要采用UITexture5) 减少图集中的空白地方。完全透明的像素和不透明的像素所占用的空间是一样的,因此在像素量不变的情况下,要尽量减少图集中的空白。比如有时一张1024x1024的图集中,素材所占的面积还没超过一半,这时可以考虑将这张图集切成两张512x512的图集。(可能有人会问为什么不能做成一张1024x512的图集,这是因为iOS平台似乎要求送进显存的贴图一定是方形。)当然,两张不同图集的DrawCall是无法合并的,但这并不是什么问题。l 根据各个UI控件
31、的设计安放Panle,隔开DrawCall在合并DrawCall时需要注意,如果将会移动变化的UI控件和一个静止不变的UI控件的DrawCall合在一起,当其中一个UI控件(UiWeight)的位置 大小 或颜色等属性发生变化时,UIPanle就需要重建这个Panle上的所有DrawCall。有时重建一个DrawCall会消耗不少的CPU,它需要计算这个DrawCall上的所有顶点信息,包括顶点位置 UV和颜色等,如果很多的控件都集中在同一个DrawCall上,那么其中一个控件上有一点点变化 ,这个DrawCall上的所有的顶点就都需要遍历一遍。而如果我们的UI又大量的采用九宫格拉伸,使控件的
32、顶点数量就会变得更多。因此重建一个DrawCall 的开销会更大。因此需要将UI控件分组,将一段时间内会发生变化的控件-比如怪物头顶的血条和伤害跳字放在同一个Panle上,并且这些Panle上只有这些控件,其余基本不变的控件放在别的控件Panle上,这样两类控件就隔开到不同的DrawCall在不同的Panle中,当一个控件发生变化而导致DrawCall重建时,就不需要遍历那些没有变化的控件。因为在美术设计上,一段时间内在变化的控件总是少数,所以优化效果十分明显,节省的CPU占用率能达到25%。l 优化锚点内部逻辑,使其只在必要时更新 在上一点优化了Panel的DrawCall重建效率之后,我们
33、发现NGUI锚点自身的更新逻辑也会消耗不少CPU开销。即使是在控件静止不动的情况下,控件的锚点也会每帧更新(见UIWidget.OnUpdate函数),而且它的更新是递归式的,使CPU占用率更高。因此我们修改了NGUI的内部代码,使锚点只在必要时更新。一般只在控件初始化和屏幕大小发生变化时更新即可。不过这个优化的代价是控件的顶点位置发生变化的时候(比如控件在运动,或控件大小改变等),上层逻辑需要自己负责更新锚点。l 降低贴图分辨率这一招说白了其实就是减小贴图素材的尺寸。比如对一张在原画里尺寸是100x80的贴图,我们将它导入Unity后会把它缩小到50x40,即缩小两倍。游戏实际使用的是缩小后
34、的贴图。不过这一招是必然会显著降低美术品质的,美术立马会发现画面变得更模糊,因此一般不到程序撑不住的时候不会采用。l 界面的延迟加载和定时卸载如果一些界面的重要性较低,并且不常被使用,可以等到界面需要打开显示的时候才从bundle加载资源,并且在关闭时将自己卸载出内存,或者等过一段时间再卸载。不过这个方法有两个代价:一是会影响体验,玩家要求打开界面时,界面的显示会有延迟;二是更容易出bug,上层写逻辑时要考虑异步情况,当程序员要访问一个界面时,这个界面未必会在内存里。因此目前为止我们仍未实施该方案。目前只是进入一个新场景时,卸载上一个场景用到但新场景不会用到的界面。l 避免频繁调用GameOb
35、ject.SetActive我们游戏的某些逻辑会在一帧内频繁调用GameObject.SetActive,显示或隐藏一些对象,数量达到一百多次之多。这类操作的CPU开销很大(尤其是NGUI的UIWidget在激活的时候会做很多初始化工作),而且会触发大量GC。后来我们改变了显示和隐藏对象的方法让对象一直保持激活状态(activeInHierarchy为true),而原来的SetActive(false)改为将对象移到屏幕外,SetActive(true)改为将对象移回屏幕内。这样性能就好多了。l NGUI性能消耗点汇总 使用LinkedList 代替BetterList。 BetterList
36、是一个数组 LinkedList是一个链表。对链表的操作要快于对数组的操作。 NGUI节点查找非常耗时。 l 代码中非必要的堆分配1) 我们应避免使用foreach循环 :一般建议是避免使用foreach循环,尽量使用for或者while循环,我在Unity论坛遇到很多人提到这个建议。这背后的原因咋一看似乎是合理的,foreach只是语法封装,因为编译器处理代码的流程大体是下面这样: foreach (SomeType s in someList)s.DoSomething();转换为:using (SomeType.Enumerator enumerator = this.someList.
37、GetEnumerator()while (enumerator.MoveNext()SomeType s = (SomeType)enumerator.Current;s.DoSomething();每次使用foreach时 都会创建一个enumerator 对象,一个System.Collections.IEnumerator的接口实例。但是创建在堆栈上还是堆上是都可能的 几乎所有的System.Collections.Genric(list Dictionary, LinkedList)命名空间中的集合类型都可以从GetEnumerateor函数执行一个结构。2) 应该避免使用闭包和LI
38、NQ吗?匿名方法和lambda表达式会引起内存泄露吗?答案是:这取决于C#编译器,有两种区别很大的方式来处理 int result = 0; void Update()for (int i = 0; i 100; i+)System.Func myFunc = (p)= p * p;result += myFunc(i);如你所见,以上代码似乎每帧要创建100次委托函数myFunc ,每次调用它执行一次计算。但是Mono只在第一次调用Update()方法时分配堆内存(在我的系统上只用了52字节),在之后的帧也没有更多的堆分配操作。这是怎么了?使用代码反编译器(将会在下篇文章解释)可以看见C#编
39、译器只是简单的把myFunc 替换为类的一个静态System.Func 类型字段,包括 Update()函数也是。这个字段的名字很奇怪但是也有一些意义:f_am$cache1(不同系统上可能会有差别),也就是说,托管方法只分配一次,然后就被缓存了 现在我们在托管定义方式上做一些小小的改变: System.Func myFunc = (p) = p * i+;通过把“p”替换为“i+”,我们已经局部定义方法转变成一个真的闭包(闭包是函数编程的一大支柱。它把数据和函数联系到一起,更准确的说是非局部变量在函数之外定义。)在myFunc中,p是一个局部变量但i是一个非局部变量,属于Update()方法
40、。C#编译器现在不得不将myFunc转换成可访问、甚至是可修改,包含非局部变量的方法。它通过声明一个全新的类表示myFunc创建的引用来实现这一功能。For循环每次执行都要分配一个类的实例,瞬间产生大量的内存泄露(在我的电脑上每帧26KB)闭包概念在C#3.0 的时候被引入主要原因是LINQ。如果闭包回引起内存泄露3) 协程 通过StartCoroutine()运行一个协程,会隐式分配Unity Coroutine类(系统上占用21字节)和Enumerator(占用16个字节),所以在游戏运行时尽量少用StartCoroutine()4) 字符串C#和Unity内存问题必须会提到字符串。从内存
41、角度,字符串很奇怪,因为,他们是堆分配且不变的。当你连接两个字符串时(无论是变量还是常量):运行时不得不分配至少一个新的字符串对象存储新的结果。String.Concat()有效地通过调用FastAllocateString()分配新对象,但是必定会产生多余的堆分配(上面例子在我的系统上占用40字节)。如果你需要在运行时修改或者连接字符串,最好使用 System.Text.StringBuilder5) 装箱 尽量减少装箱 6) 库方法各种库方法也会隐式分配内存。捕捉它们最好的方法是分析。我之前已经写过的 foreach-循环,大多数标准泛型集合不会导致堆分配。Dictionary 也是。然而
42、,有点神秘地,Dictionary .KeyCollection和Dictionary .ValueCollection是类,不是结构体,这意味着foreach(K key in myDict.Keys).会分配16个字节。l 查找堆分配的两种方式1. 使用Unity profiler2. 反编译自己的代码 在CIL中查找内存分配CIL代码的优势在于堆分配代码不会被隐藏。相反,完全可以在反编译的代码中找到堆分配的三种指令。1) newobj :通过构造函数创建一个指定类型的未初始化对象。如果对象是值类型(结构体等),则在堆栈创建。如果是引用类型(类等)则在堆上分配。可以从CIL代码知道对象类型
43、,所以,可以很容易知道在哪分配。2) newarr :在堆上创建一个数组。元素类型在参数中指定。3) box :装箱(传递数据)专用指令,在第一部分已经介绍过。l Unity脚本执行顺序和编译顺序1) 脚本执行顺序 Unity 在后台会把每个脚本的 Awake 、Start、Update、LateUpdate、FixedUpdate 等等,所有的方法合并到一起。然后按照 代码的执行顺序进行执行。 编译顺序 首先从脚本语言类型来看,Unity3d支持3种脚本语言,都会被编译成CLI的DLL如果项目中包含有C#脚本,那么Unity3d会产生以Assembly-CSharp为前缀的工程,名字中包含”
44、vs”的是产生给Vistual Studio使用的,不包含”vs”的是产生给MonoDevelop使用的。 对于每一种脚本语言,根据脚本放置的位置(其实也部分根据脚本的作用,比如编辑器扩展脚本,就必须放在Editor文件夹下),Unity会生成4中后缀的工程。其中的firstpass表示先编译,Editor表示放在Editor文件夹下的脚本。Assembly-CSharp-filepass-vs.csprojAssembly-CSharp-Editor-filepass-vs.csprojAssembly-CSharp-vs.csprojAssembly-CSharp-Editor-vs.cs
45、proj根据官方的解释,它们的编译顺序如下:(1)所有在Standard Assets、Pro Standard Assets或者Plugins文件夹中的脚本会产生一个Assembly-CSharp-filepass-vs.csproj文件,并且先编译;(2)所有在Standard Assets/Editor、Pro Standard Assets/Editor或者Plugins/Editor文件夹中的脚本产生Assembly-CSharp-Editor-filepass-vs.csproj工程文件,接着编译;(3)所有在Assets/Editor外面的,并且不在(1),(2)中的脚本文件(一
46、般这些脚本就是我们自己写的非编辑器扩展脚本)会产生Assembly-CSharp-vs.csproj工程文件,被编译;(4)所有在Assets/Editor中的脚本产生一个Assembly-CSharp-Editor-vs.csproj工程文件,被编译。之所以按照这样建立工程并按此顺序编译,也是因为DLL间存在的依赖关系所决定的。好了,到此为止,我们可以很容易地判断出上面举的实例中,脚本的编译顺序(实际上,我已经把顺序写在了脚本的文件名中了)一个Unity3d的工程中,最多可以产生多少个工程文件呢? 4*3*2=24 4个文件 3中编译器 2种平台 1 个工程 l IOS平台崩溃的几种情况。1
47、 在协程中要判断对象是不是为空(list GameObject) 在每次调用的时候都学要调用(因 为协程不能保证即使调用如果对象已销毁但是依然使用时会卡死)。2 Delegate的判断(IOS 尽量使Event)3 如果引用静态的static Compent的组件会出现卡死4 Unity中的OnEnable Start Awake Disable Destory() 都回延迟一帧5 Unity在Ios对反射的支持不完整。6 Awake 和Start函数执行时机, Awake 函数在这个脚本在场景中加载时就会调用,需要注意的是,Start函数也不是一定立即执行的,它是在该脚本第一次调用Updat
48、e函数之前调用的,也就是说,如果这个脚本一开始的状态是disable的,那么直到它变成enable状态,在Update函数第一次执行前,才会执行Start函数。7 Linq TO XML untiy中配置文件需要注意的点: pc端不区分文件大小写。 Andriod不区分大小写 Ios移动端区分文件大小写。 需要注意文件名称大小写。l 资源优化(内存优化):1 代码中申请的内存,一般是New 或者Instantiate操作 Instantiate中也是调用了New2 及时设置释放标识符 变量设置为Null 超出变量作用域 Destory()3 及时Gc释放内存 需要注意 Gc时会造成游戏短时间的
49、卡顿,影响游戏体验 需要选择合适的时间Gc4 尽可能的重用资源(贴图 材质 网格 )5 一般图片资源占用的内存最大,优化效果最明显(降低贴图分辨率,在效果和大小之间找一个平横点)6 程序中的内存池 对象池 必要时主动释放内存7 做AssetBundle资源的关系以来打包 动态加载 卸载8 C#中Struct数据在栈上 Class在堆上 局部数据尽量用Struct 减少Gc的频率9 按C+ 的思想来管理内存,比如使用的内存池,对象池 手工卸载 主动Gc 注意在内存和帧率之间做好平衡。10 www 下载时需要注意变量声明和要注意申请的内存堆上的无用内存11l Unity资源(加载/更新)1. Resources- 打包集成到.asset文件里面
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年新生儿窒息诊断流程
- 护理礼仪的教育与培训
- 护理质量改进方法:获奖课件分享
- 山东省济宁市济宁学院附中2025-2026学年下学期初四道德与法治期中考试题(含答案)
- 6.1《老子》四章课件(共56张) 2024-2025学年统编版高中语文选择性必修上册
- 巧克力原料处理工操作技能测试考核试卷含答案
- 轮轴装修工安全培训竞赛考核试卷含答案
- 磨料制造工安全知识竞赛水平考核试卷含答案
- 2026年新科教版高中高一地理下册第一单元人口迁移影响因素卷含答案
- 制帽工诚信竞赛考核试卷含答案
- 女人气血养生法(升级版)
- 高中政治面试试讲真题(2套)
- 全国实验室安全知识竞赛试题库(附含答案)
- 材料腐蚀与防护课件
- 考前女生心理和生理调适课件
- 2022年中国动漫集团有限公司招聘笔试试题及答案解析
- (完整)常用网络拓扑图图标
- 翰威特-绩效管理
- 仰斜式路堑墙施工方案
- 项目建设单位内控管理办法
- 高中生社会实践证明
评论
0/150
提交评论