Unity 面试篇|(四)Unity性能优化篇 【全面总结 | 持续更新】

1. 什么是DrawCall?DrawCall高了有什么影响?如何降低DrawCall?

  • Unity中,CPU准备好需要绘制的元素,对底层图形程序接口进行调用的过程,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。
  • 降低DrawCall的方法:
    • 动态合批
    • 静态合批
    • 降低shader的等级特性
    • 场景优化策略——遮挡技术。
    • rectMask2D替代Mask

2. UI优化小知识

  • UI动静分离
    以canvas为节点,设置动态canvas和静态canvas,实际项目静态元素较多,动态元素较少,动静分离后,CPU在重绘和合并时消耗就会减少。
  • 拆分过重的UI
    将界面中隐藏的独立界面做一次拆分
    对二次显示内容,如部分动效图标,小窗口等做二次拆分。
  • UI预加载
    UI实例化到场景中的过程:网格合并,组件初始化,渲染初始化,图片加载,界面逻辑调用等,消耗大量CPU
    预加载:把资源加载到内存、UI实例化和UI初始化的CPU消耗放在loading等待时间线上
  • UI图集Alpha分离
    主要是针对NGUI方案,Unity内部已经完成了Alpha分离
    首先:TexturePacker打图集时候,改成打一张RGB888的PNG图和一张Alpha8的PNG图
    其次:修改NGUI的原始着色器,绑定主图和绑定Alpha图
    然后:将NGUI的着色器shader中相应修改为新的颜色通道和透明通道
    最后:NGUI工具类也要相应修改编辑几个类
    最终:主图和Alpha图合成新图片替换原来的图片
  • UI字体拆分
    提取常用字体
    使用TMP,同样会生成纹理和图集,相比TEXT优势是,TMP是矢量字算法,MESH顶点数少,字体同源,各语言能同屏显示
  • ScrollView优化
    不停滚动会导致合批网格重构、渲染裁剪
  • 使用对象池进行优化
  • 网格重构优化
  • UI展示与关闭优化
  • 对象池运用
    当程序中有重复实例化兵不断摧毁的对象时需要使用对象池进行优化
    每个需要使用对象池的对象都需要继承对象池的基类对象
    销毁操作是通过对象池接口提供的回收接口
    场景结束时要及时销毁整个对象池
  • UI贴图设置优化
  • 高低端机型画质优化
    使用两套UI贴图,高清,低清,两套图,两套Prefab,NGUI和UGUI高清HD和SD切换的流程可以通过编写脚本程序一键搞定。
    模型和特效使用不同质量(三角面数)的预制体,预制体命名后缀做加载区分,区分等级
    阴影根据使用情况进行区分
    整体贴图渲染质量进行区别对待
    使用QuailtySetting的API来对阴影和贴图渲染质量做操作
    通过程序来区分机型,ios通过机型就能判断UnityEngine.IOS.Device.generation== XXXX.Iphone6;安卓通过CPU型号,内存大小,系统,平均帧率等进行综合判断
  • UI图集拼接优化
    充分利用图集空间
    图集大小控制1024*1024
    图片的拼接归类

3. 层消隐距离技术

  • 如果场景中存在大量小"物件”,则可以使用"层消隐距离"来优化场景;"层消隐距离"就是在比较远的距离将小物体剔除以减少绘图调用的数量(比如:可以一个大型场景中,高大型的物体任然可见,但是一些小装饰内容(小狗、车子之类的则可以隐藏))

4. LOD是什么,优缺点是什么?

  • LOD(Level of detail)多层次细节,是最常用的游 戏优化技术。
    它按照模型的位置和重要程度决定 物体渲染的资源分配,降低非重要物体的面数和 细节度,从而获得高效率的渲染运算。
    这就是说,根据摄像机与模型的距离,来决定显示哪一个模型,一般距离近的时候显示高精度多细节模型,距离远的时候显示低精度低细节模型,来加快整体场景的渲染速度。
    • 作用 : 优化GPU
    • 缺点 : 同一模型要准备多个模型,消耗内存。
    • 特点 : 以内存做消耗来优化GPU

5. 合批

  • 一次Draw Call中批量处理多个物体。只要物体的变换和材质引用相同,GPU就可以按完全相同的方式进行处理,即可以把它们放在一个Draw Call中。
  • 注意:简单来说在一个Canvas下,需要相同的材质,相同的纹理以及相同的Z值。
    例如Ul上的字体Texture使用的是字体的图集,往往和我们自己的UI图集不一样,因此无法合批。还有UI的动态更新会影响网格的重绘,因此需要动静分离。

6. 静态合批

  • 将static的静态物体(永远不会移动、旋转和缩放) ,如果相同材质球,面数在一定范围之内。unity会自动合并成一个batch送往GPU处理。
  • 原理:在开始阶段把需要静态批处理的GameObject进行一次网格合并操作,然后把这个合并之后的大网格保存起来,后续都是用这个网格而不需要再进行合并。
    在预处理阶段,把一些材质相同的模型的顶点统一变换到世界空间坐标下,并且新构建一个大的VB把数据保存下来,在绘制时,就会把这个大的VB提交上去,只需要设置一次渲染状态,再进行多次drawcall绘画出每个子模型。 所以Static Batching是不会减少drawcall的,但由于只修改了一次渲染状态依然可以减少CPU的消耗。而且在渲染前,也可以进行视锥体剔除,减少顶点着色器对不可见的顶点的处理次数,提交GPU的效率。
  • 操作方法:把要进行静态批处理的GameObject在Inspector面板右上角的Static勾选(实际上只需要勾选Batching Static即可)
    • 优点:因为只需要进行一次,所以性能会比动态批处理要好。
    • 缺点: 使用静态合批需要额外的内存开销来存储合并后的几何数据。
      因为需要额外维护多一份数据,所以包体会变大,占用的内存也会变多(不能有超级大量的相同模型(如:森林里的树))

7. 动态合批

  • 如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要进行额外的操作。
  • 原理:Unity会检测哪些GameObject使用了同一个共享材质,然后去合并这些使用了同一个共享材质的网格顶点数据,形成一个新的大网格,然后传给显存,直接渲染这个大网格就相当于渲染了所有的被合并的小网格,而这只需要一次DrawCall。
    在每一帧运行时,计算相同材质的模型,把他合并批次进行渲染。动态合批只需要设置一次渲染状态,且能减少drawcall次数。
  • 优点: 不用自己做任何事情,Unity会在游戏中自动进行动态批处理,只要满足下述条件。
    • 顶点属性要小于900。例如,如果shader中需要使用顶点位置、法线和纹理坐标这三个顶点属性,那么要想让模型能够被动态批处理,它的顶点数目不能超过300。因此,优化策略就是shader的优化,少使用顶点属性,或者模型顶点数要尽可能少。(这个是《UnityShader入门精要》这本书上说到的,同时书上也说了不一定是900,可能不同版本的Unity会有所区别,这个可以自己在Unity中去手动验证得出)
    • 多Pass的shader会中断批处理。
    • 使用LightingMap的物体需要小心处理。为了让这些物体可以被动态批处理,需要保证它们指向LightingMap中的同一位置。

8. 动态合批跟静态合批的区别

  • 动态批处理一切都是自动的,不需要做任何操 作,而且物体是可以移动的,但是限制很多。
  • 静态批 处理自由度很高,限制很少,缺点可能会占用更多 的内存,而且经过静态批处理后的所有物体都不可以 再移动了。
  • 静态合批发生在加载场景的时候。
  • 动态合批发生在游戏运行的时候。

9. 如何优化内存?

  • 有很多种方式,例如
    • 压缩自带类库;
    • 将暂时不用的以后还需要使用的物体隐藏起来而不 是直接Destroy掉;
    • 释放AssetBundle占用的资源;
    • 降低模型的片面数,降低模型的⻣骼数量,降低贴 图的大小;
    • 使用光照贴图,使用多层次细节(LOD),使用着色 器(Shader),使用预设(Prefab)。

10. mask和rectmask2d的区别

  • Mask使用模板缓冲来实现区域切除逻辑(Stencil),会占用两个DC。它实现最初设置模板缓存会给Mask添加一个特殊的材质,并且以像素为单位存储是否需要显示最后还原模板缓存,这两次操作各增加一次DC。它可以和其他Mask子物体进行合批,如果两个mask重叠了,那就不能进行合批,会产生额外的dc。
  • rectmask2d继承自IClipper接口,内部主要实现的就是一个方法来实现了区域的切除逻辑,本身是不占用DC的,完全遮住的情况下不会绘制顶点和面,不参与深度运算不占用DC(和mask的最大区别)。 缺点:它无法和RectMask的子物体进行合批,只能和自身的子物体进行合批(注:如果本身带了Image组件的话是可以进行合批的)
  • mask2d只能矩形,要不同形状的遮罩还是得mask,所以RectMask2D并不一定完全好,他在特定情况下无法合批。

11. 贴图透明通道分离,压缩格式设为ETC/PVRTC

  • 最初我们使用了DXT5作为贴图压缩格式,希望能减小贴图的内存占用,但很快发现移动平台的显卡是不支持的。因此对于一张1024x1024大小的RGBA32贴图,虽然DXT5可将它从4MB压缩到1MB,但系统将它送进显卡之前,会先用CPU在内存里将它解压成4MB的RGBA32格式(软件解压),然后再将这4MB送进显存。于是在这段时间里,这张贴图就占用了5MB内存和4MB显存;而移动平台往往没有独立显存,需要从内存里抠一块作为显存,于是原以为只占1MB内存的贴图实际却占了9MB!
  • 所有不支持硬件解压的压缩格式都有这个问题。经过一番调研,我们发现安卓上硬件支持最广泛的格式是ETC,苹果上则是PVRTC。但这两种格式都是不带透明(Alpha)通道的。因此我们将每张原始贴图的透明通道都分离了出来,写进另一张贴图的红色通道里。这两张贴图都采用ETC/PVRTC压缩。渲染的时候,将两张贴图都送进显存。同时我们修改了NGUI的shader,在渲染时将第二张贴图的红色通道写到第一张贴图的透明通道里,恢复原来的颜色:
fixed4 frag (v2f i) : COLOR  
    fixed4 col;  
    col.rgb = tex2D(_MainTex, i.texcoord).rgb;  
    col.a = tex2D(_AlphaTex, i.texcoord).r;  
    return col * i.color;  
fixed4 frag (v2f i) : COLOR
{
    
    
    fixed4 col;
    col.rgb = tex2D(_MainTex, i.texcoord).rgb;
    col.a = tex2D(_AlphaTex, i.texcoord).r;
    return col * i.color;
}

12. 关闭贴图的读写选项

  • Unity中导入的每张贴图都有一个启用可读可写(Read/Write Enabled)的开关,对应的程序参数是TextureImporter.isReadable。
  • 选中贴图后可在Import Setting选项卡中看到这个开关。只有打开这个开关,才可以对贴图使用Texture2D.GetPixel,读取或改写贴图资源的像素,但这就需要系统在内存里保留一份贴图的拷贝,以供CPU访问。
  • 一般游戏运行时不会有这样的需求,因此我们对所有贴图都关闭了这个开关,只在编辑中做贴图导入后处理(比如对原始贴图分离透明通道)时打开它。
  • 这样,上文提到的1024x1024大小的贴图,其运行时的2MB内存占用又可以少一半,减小到1MB。

13. Unity 在移动设备上的⼀些优化资源的方法

  • 使⽤assetbundle,实现资源分离和共享,将内存控
    制到200m之内,同时也可以实现资源的在线更新
  • 顶点数对渲染⽆论是cpu还是gpu都是压⼒最⼤的贡
    献者,降低顶点数到8万以下,fps稳定到了30帧左右
  • 只使⽤⼀盏动态光,不是⽤阴影,不使⽤光照探头
    粒⼦系统是cpu上的⼤头
  • 剪裁粒⼦系统
    合并同时出现的粒⼦系统
  • ⾃⼰实现轻量级的粒⼦系统
    animator也是⼀个效率奇差的地⽅
  • 把不需要跟⻣骼动画和动作过渡的地⽅全部使⽤
    animation,控制⻣骼数ᰁ在30根以下
    animator出视ᰀ不更新
  • 删除⽆意义的animator
    animator的初始化很耗时(粒⼦上能不能尽ᰁ不⽤
    animator)
  • 除主⻆外都不要跟⻣骼运动apply root motion
  • 绝对禁⽌掉那些不带刚体带包围盒的物体(static
    collider )运动
    NUGI的代码效率很差,基本上runtime的时候对cpu的
    贡献和render不相上下
  • 每帧递归的计算finalalpha改为只有初始化和变动时计算
  • 去掉法线计算
  • 不要每帧计算viewsize 和windowsize
    filldrawcall时构建顶点缓存使⽤array.copy
  • 代码剪裁:使⽤strip level ,使⽤.net2.0 subset
  • 尽量减少smooth group
  • 给美术定⼀个严格的经过科学验证的美术标准,并在U3D⾥⾯配以相应的检查⼯具

14. CPU端性能优化小知识点

  • 逻辑和表现尽可能分离开,这样逻辑层的更新频率可以适当降低些.
  • 对于一些热点函数,如mmo的实体更新、实例化,使用分帧处理,分摊单帧时间消耗.
  • 做好同屏实体数量、特效数量、距离显隐等优化.
  • 完善日志输出,避免没必要的日志输出,同时警惕日志字符串拼接.
  • 使用骨骼烘焙 + GPUSkinning + Instance 降低CPU蒙皮骨骼消耗和drawcall.
  • 开启模型的Optimize GameObjects减少节点数量和蒙皮更新消耗.
  • UI拼预制做好动静分离,对于像血条名字这种频繁变动的ui,做好适当的分组.
  • 减少C#和lua的频繁交互,尽量精简两者传递的参数结构.
  • 使用stringbuilder优化字符串拼接的gc问题.
  • 删除非必要的脚本功能函数,特别是Update/LateUpdate类高频执行函数,因为会产生C++到C#层的调用开销. 对于Update里需要用到的组件、节点等提前Cache好.
  • 场景里频繁使用的资源或数据结构做好资源复用和对象池.
  • 对于频繁显示隐藏的UI,可以先移出到屏幕外,如果长时间不显示再进行Deactive.
  • 合理拆分UI图集,区分共用图集和非共用图集,共用图集可以常驻内存,非共用图集优先按功能分类,避免资源冗余.
  • 使用IL2CPP, 编译成C++版本能极大的提升整体性能.
  • 避免直接使用Material.Setxxx/Getxxx 等调用,这些调用会触发材质实例化消耗,可以考虑使用SharedMaterial / MaterialPropertyBlock代替.
  • 合并Shader里的Uniform变量.

15. 内存优化小知识点

  • 压缩自带类库;
  • 将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
  • 释放AssetBundle占用的资源;
  • 降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
  • 使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。
  • 警惕配置表内存占用
  • 排查项目冗余的shader
  • 减少容器扩容或者利用string字符串拼接等一系列产生GC的操作
  • 警惕配置表的内存占用.
  • 检查ShaderLab内存占用:
  • 避免使用Standard材质,做好相应的variant skip.
  • 排查项目冗余的Shader.
  • 使用shader_feature替代multi_compile,这样只会收集项目里真正使用的变体组合,避免变体翻倍.
  • 检查纹理资源的尺寸、格式、压缩方式、mipmap、Read & Write选项使用是否合理.
  • 检查Mesh资源的Read & Write选项、顶点属性使用是否合理.
  • 代码级别的检查,如Cache预分配空间、容器的Capacity、GC等.
  • 使用Profiler定位下GC,特别是Update类函数里的. 如:字符串拼接、滥用容器等.
  • 合理控制RenderTexture的尺寸.
  • 优化动画Animation的压缩方式、浮点精度、去除里面的Scale曲线数据.
  • 减少场景GameObject节点的数量,最好支持工具监控.

16. UI图集的作用

  • 图集就是碎图合成大图 降低内存,减少DC。
  • UI图集有合批没有的优点,就是热更新的时候因为小文件变少了,所以会快一些。
  • UI图集就是UI的动态合批。
  • UI图集完成合批的条件:深度 贴图 材质 => 排序好的列表当前这个依次和前面对比是否贴图和材质ID相同决定是否合批。

17. 请简述GC(垃圾回收)产生的原因,并描述如何避免?

  • GC垃圾回收机制,避免堆内存溢出,定期回收那些没有有效引用的对象内存
    GC优化,就是优化堆内存,减少堆内存,即时回收堆内存
    GC归属于CLR
  • 避免
    • 减少new的次数
    • 字符串拼接使用stringbuilder,字符串比较先定义一个变量存储,防止产生无效内存
      list,new时候,规定内存大小
    • 如果要射线检测,应该使用避免GC的方法XXXXNoAlloc函数
    • foreach迭代器容易导致GC(目前Unity5.5已修复),使用For循环
    • 使用静态变量,GC不会回收存在的对象,但静态变量的引用对象可能被回收
    • 使用枚举替代字符串变量
    • 调用gameobject.tag=="XXX"就会产生内存垃圾;那么采用GameObject.CompareTag()可以避免内存垃圾的产生:
    • 不要在频繁调用的函数中反复进行堆内存分配,比如OnTriggerXXX,Update等函数
    • 在Update函数中,运行有规律的但不需要每一帧执行的代码,可以使用计时器,比如1秒执行一次某些代码!!!

18. 简述优化脚本的方法

  • 减少GetComponent、find等查找函数在Update等循环函数中的调用、go.CompareTag代替go.tag
  • 减少SendMessage等同步函数调用;减少字符串连接;for代替foreach,5.5以后版本foreach已经优化过了;
  • 少用linq;
  • 大资源改为异步加载
  • 合理处理协程调用
  • 将AI、网络等放在单独线程
  • 发布优化:关闭log、剔除代码伪随机
  • 脚本挂载类改为Manager等全局类实现
  • lua中尽量不实现update、fixedupdate等循环函数,lua和csharp互调用的效率比较低。

19. 简述优化内存管理的方向

  • 按照不同资源、不同设备管理资源生命周期,Resources.Load和Assetbundle统一接口,利用引用计数来管理生命周期,并打印和观察生命周期。保证资源随场景而卸载,不常驻内存,确定哪些是预加载,哪些泄漏。
  • 内存泄漏(减少驻留内存):Container内资源不remove掉用Resources.UnloadUnusedAssets是卸载不掉的;对于这种情况,建议直接通过Profiler Memory中的Take Sample来对其进行检测,通过直接查看WebStream或SerializedFile中的AssetBundle名称,即可判断是否存在“泄露”情况;通过Android PSS/iOS Instrument反馈的App线程内存来查看;
  • 堆内存过大:避免一次性堆内存的过大分配,Mono的堆内存一旦分配,就不会返还给系统,这意味着Mono的堆内存是只升不降的。常见:高频调用new;log输出;
  • CPU占用高:NGui的重建网格导致UIPanel.LateUpdate(按照静止、移动、高频移动来切分);NGUI锚点自身的更新逻辑也会消耗不少CPU开销。即使是在控件静止不动的情况下,控件的锚点也会每帧更新(见UIWidget.OnUpdate函数),而且它的更新是递归式的,使CPU占用率更高。因此我们修改了NGUI的内部代码,使锚点只在必要时更新。一般只在控件初始化和屏幕大小发生变化时更新即可。不过这个优化的代价是控件的顶点位置发生变化的时候(比如控件在运动,或控件大小改变等),上层逻辑需要自己负责更新锚点。 加载用协程; 控制同一个UIPanel中动态UI元素的数量,数量越多,所创建的Mesh越大,从而使得重构的开销显著增加。比如,战斗过程中的HUD血条可能会大量出现,此时,建议研发团队将运动血条分离成不同的UIPanel,每组UIPanel下5~10个动态UI为宜。这种做法,其本质是从概率上尽可能降低单帧中UIPanel的重建开销。
  • 资源冗余:AssetBundle打包打到多份中;动态修改资源导致的Instance拷贝多份(比如动态修改材质,Renderer.meterial,Animation.AddClip)。
  • 磁盘空间换内存:对于占用WebStream较大的AssetBundle文件(如UI Atlas相关的AssetBundle文件等),建议使用LoadFromCacheOrDownLoad或CreateFromFile来进行替换,即将解压后的AssetBundle数据存储于本地Cache中进行使用。这种做法非常适合于内存特别吃紧的项目,即通过本地的磁盘空间来换取内存空间

20. 简述美术优化的方向

  • 建立资源审查规范和审查工具:PBR材质贴图制作规范、场景制作资源控制规范、角色制作规范、特效制作规范;利用AssetPostprocessor建立审查工具。
  • 压缩纹理、优化精灵填充率、压缩动画、压缩声音、压缩UI(九宫格优于拉伸);严格控制模型面数、纹理数、角色骨骼数。
  • 粒子:录制动画代替粒子、减少粒子数量、粒子不要碰撞
  • 角色:启用Optimize Game Objects减少节点,使用(SimpleLOD、Cruncher)优化面数。
  • 模型:导入检查Read/Write only、Optimize Mesh、法线切线、color、禁用Mipmap
  • 压缩纹理问题:压缩可能导致色阶不足;无透明通道用ETC1,现在安卓不支持ETC2已不足5%,建议放弃分离通道办法。
  • UI:尽可能将动态UI元素和静态UI元素分离到不同的UIPanel中(UI的重建以UIPanel为单位),从而尽可能将因为变动的UI元素引起的重构控制在较小的范围内; 尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中; 尽可能让动态UI元素按照同步性进行划分,即运动频率不同的UI元素尽可能分离放在不同的UIPanel中;
  • ugui:可以充分利用canvas来切分不同元素。
  • 大贴图会导致卡顿,可以切分为多个加载。
  • iOS使用mp3压缩、Android使用Vorbis压缩

21. 简述优化物理系统的方法

  • 不需要移动的物体设为Static
  • 不要用Mesh碰撞,角色不用碰撞体
  • 触发器逻辑优化
  • 寻路频率、AI逻辑频率 、Fixed Timestep、降帧到30
  • 出现卡顿的复杂计算,例如寻路、大量资源加载 可以用分帧或者协成异步来处理

22. 简述UI资源如何优化

  • 纹理资源优化
    单个纹理尺寸为2的幂次方,最大尺寸1024*1024(内存优化)
    纹理加载方式:流式纹理加载Texture Streaming
    不通过增加纹理大小来增加细节,而是通过增加细节贴图DetailMap或增加高反差保留
  • 纹理压缩:可以使用ETC1+Alpha(安卓),ETC2(安卓),PVRTC(ios),ASTC 6x6
    ASTC更优,内存大小相同的情况下,纹理效果最好,加载速度最快,包体最小
  • 纹理MipMap:逐级减低分辨率来保存纹理副本,相当于纹理LOD
    内存变大1//3,通过Mipmap开启可以限制不同平台加载不同level层级的贴图
  • UI纹理图集:UI图集最大尺寸为1024*1024
  • 重复利用的公用资源放common图集(drawcall优化)
  • 同一个界面UI资源放一个图集(drawcall优化)
  • 使用九宫格来减少原图大小(内存优化)
  • 提高图集利用率,原图分辨率需要包含在1024*1024尺寸下,如果原图过大需要进行拆分,放入图集中
  • 图集合并提升利用率,如果图集低于1/3 就要合并图集。

23. 简述一下对象池,你觉得在FPS里哪些东西适合使用对象池

  • 对象池就存放需要被反复调用资源的一个空间,当一个对象回大量生成的时候如果每次都销毁创建会很费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果,在FPS游戏中要常被大量复制的对象包括子弹,敌人,粒子等

24. 动态加载资源的方式和区别

  • 通过 Resources 模块,调用它的 load 函数:可以直接 load 并返回某个类型的 Object,前提是要把这 个资源放在 Resource 命名的文件夹下,Unity 不关有没有场景引用,都会将其全部打入到安装包中。 Resources.Load();
  • 通过 bundle 的形式: 即将资源打成 asset bundle 放在服务器或本地磁盘, 然后使用 WWW 模块 get 下来, 然后从这个 bundle 中 load 某个 object。
  • 通过 AssetDatabase.loadasset :这种方式只在 editor 范围内有效,游戏运行时没有这个函数, 它通常 是在开发中调试用【AssetDatabase 资源数据库】
  • 区别
    • Resources 的方式需要把所有资源全部打入安装包,这对游戏的分包发布(微端)和版本升级(patch)是不利的,所以 unity 推荐的方式是不用它, 都用 bundle 的方式替代, 把资源达成几个小 的 bundle, 用哪个就 load 哪个,这样还能分包发布和 patch,但是在开发过程中,不可能没更新一个 资源就打一次 bundle, 所以 editor 环境下可以使用 AssetDatabase 来模拟,这通常需要我们封装一个 dynamic resource 的 loader 模块,在不同的环境下做不同实现。
      动态资源的存放
    • 有时我需要存放一些自己的文件在磁盘上,例如我想把几个 bundle 放在初始的安装里, unity 有一个 streaming asset 的概念,用于提供存储接口的访问。我们需要在编辑器建立一个 StreamingAssets 名字 的文件夹,把需要我们放在客户磁盘上的动态文件放在这个文件夹下面,这样安装后,这些文件会放在用户磁盘的指定位置,这个位置可以通过 Application.streamingAssetsPath 来得到。

25. 背包系统中只有 20 个格子,现在有总共有 100 个物体,除了显示在 视野中的 20 个外,对其他的处理方法?(注:将其他隐藏起来不可行,对象池得有具体的说明)

  • 每个滚动条目都是同一个预设体的实例
  • 做缓存,只要实例化视野范围内滚动条目,往上超出视野部分,自动填补到视野的下面

26. 如何优化Unity游戏中的物理运算性能?

  • 在Unity游戏开发中,物理运算的性能优化是十分重要的,因为物理运算往往对CPU和GPU的性能要求较高。以下是一些优化物理运算性能的方法:

    • 减少物理对象的数量:减少场景中需要计算物理效果的物体数量,例如,不必要的刚体(Rigidbody)组件或者Collider组件。
    • 使用合理的物理引擎参数:根据游戏的需求调整物理引擎的参数,例如,调整重力、摩擦力、恢复系数等参数。避免使用过高的参数导致不必要的计算。
    • 利用静态和动态碰撞体:将场景中的物体分为静态和动态碰撞体,静态物体只需要检测碰撞而不需要计算物理效果,可以减少性能的消耗。
    • 启用游戏对象和物理组件的优化:在Unity编辑器中,有些游戏对象和物理组件可以进行优化设置,例如,关闭不必要的物理模拟,使用预设的物理参数等。
    • 使用帧同步和时间缩放:对于需要精确控制物理效果的游戏,可以使用帧同步和时间缩放来减少物理运算的频率,从而减 少性能的消耗。
    • 使用物理引擎的缓存:在某些情况下,物理引擎的缓存可以提高性能。例如,使用同一个刚体组件的多个物体可以共享同一个物理模拟。
    • 优化碰撞检测:对于复杂的碰撞体,可以使用层次包围盒(Bounding Hierarchies)技术来减少碰撞检测的数量和复杂度。
    • 使用GPU加速的物理运算:在某些情况下,可以使用GPU加速的物理运算来提高性能。例如,使用Unity的GPU Instancing技术可以减少渲染对象的数量。
    • 优化物理模拟的精度:根据游戏的需求,可以调整物理模拟的精度。例如,对于不需要高精度模拟的游戏,可以关闭或者降低物理模拟的精度。
    • 使用多线程和异步运算:对于需要大量计算的游戏,可以使用多线程和异步运算来提高性能。例如,可以将物理运算的任务分配给多个CPU核心同时进行。

27. 什么是热更新和冷更新,它们在Unity游戏优化中的作用是什么?

  • 在Unity游戏开发中,热更新(Hotfix)和冷更新(Cold Update)是两种重要的更新方式,它们在游戏优化中起着不同的作用。
  • 热更新是一种在不需要重新发布应用程序或游戏的情况下,通过远程更新服务器向客户端推送程序代码、资源文件等数据的技术手段。这种更新方式主要用于修复程序漏洞、优化游戏性能、更新游戏内容等。热更新可以提高游戏的灵活性和可维护性,减少玩家的等待时间和游戏更新的成本。在Unity中,热更新通常通过动态加载和替换游戏中的一些非核心代码和资源来实现,比如活动运营和打补丁。这种方式允许开发者在不重新下载游戏客户端的情况下,更新游戏内容,从而提供更好的用户体验。
  • 冷更新则是指在需要更新时,需要用户手动到应用商店或官方网站下载新版本安装包并重新安装的一种更新方式。这种方式主要用于解决一些热更新无法覆盖的问题,如需要进行版本升级、打包、发布等工作。冷更新通常需要耗费一定的时间和人力物力成本,且对于玩家来说,需要重新下载安装包,可能会造成一定的不便。然而,在某些情况下,冷更新是必要的,比如当游戏进行了重大改动或添加了新功能时,需要重新打包发布以确保游戏的稳定性和兼容性。
  • 总的来说,热更新和冷更新在Unity游戏优化中各有优劣。热更新可以快速修复漏洞、更新内容,提高游戏的灵活性和可维护性;而冷更新则可以解决一些热更新无法处理的问题,确保游戏的稳定性和兼容性。在实际项目运营中,开发者需要根据具体需求和情况,灵活选择适合的更新方式以优化游戏体验。

28. 如何处理Unity中的内存泄漏问题?

  • 确认内存泄漏
    使用Unity Profiler的内存分析功能来监控应用程序的内存使用情况。观察在长时间运行或特定操作后,内存占用是否持续增加,而无法被垃圾回收(Garbage Collection, GC)释放。

  • 定位泄漏源

    • 对象引用:检查是否有对象被长时间持有引用,导致它们无法被GC回收。这包括静态字段、单例模式、事件订阅、缓存等。
    • 场景切换:在切换场景时,确保旧场景中的对象被正确销毁,避免跨场景的内存泄漏。
    • 资源加载:使用Resources.LoadAssetBundle.LoadAsset加载资源时,需要确保在不再需要时释放资源。
    • 自定义组件和脚本:检查自定义组件和脚本中的内存管理,确保没有不当的内存分配和持有。
  • 使用分析工具

    • Unity Profiler:使用Profiler的内存模块来查找哪些对象占用了大量内存,并确定它们是否应该被回收。
    • 第三方插件:一些第三方插件,如Heapy、Memory Profiler等,可以提供更详细的内存分析功能。
  • 优化代码

    • 避免不必要的内存分配:减少在频繁调用的方法中分配临时对象,考虑使用对象池(Object Pooling)来重用对象。
    • 及时释放资源:使用Destroy方法销毁不再需要的GameObject和组件,使用Resources.UnloadUnusedAssetsAssetBundle.Unload来卸载不再使用的资源。
    • 清理事件监听器:确保在对象销毁时取消所有事件监听器的订阅,避免空引用和内存泄漏。
  • 测试验证
    在修复了潜在的内存泄漏问题后,重新运行应用程序并进行长时间的测试,以验证内存使用是否保持稳定,并没有持续增长。

29. 在Unity中如何处理内存碎片问题?

  • 在Unity中处理内存碎片问题,可以采取以下几种策略:

    • 使用对象池(Object Pooling):对象池是一种避免频繁创建和销毁对象的策略。通过对象池,你可以预先创建一定数量的对象,并在需要时重复使用这些对象,而不是频繁地创建和销毁新对象。这样可以减少内存分配和释放的次数,从而减少内存碎片的产生。

    • 使用Unity的内置池功能:Unity自带了一个对象池管理器,可以帮助你更方便地实现对象池。使用ObjectPoolManager类,你可以创建一个可重用的对象池,并在需要时请求或归还对象。

    • 避免频繁的内存分配:尽量避免在游戏运行过程中频繁地进行内存分配。如果你需要在游戏运行时动态生成对象,尝试将这些对象的数量限制在一定范围内,并尽可能地重用它们。

    • 合理使用内存管理工具:Unity提供了一些内存管理工具,如GC.Collect()方法、GC.GetTotalMemory()方法等。通过合理使用这些工具,你可以手动触发垃圾回收或检查内存使用情况,从而发现并解决潜在的内存碎片问题。

    • 优化数据结构和算法:有时候,内存碎片问题可能是由于数据结构和算法的设计不当引起的。通过优化数据结构和算法,你可以减少内存碎片的产生。例如,使用紧凑的数据结构、避免不必要的内存分配、优化字符串的使用等。

30. 在Unity中如何优化网络传输性能?

  • 在Unity中优化网络传输性能的方法有很多,以下是一些常见的策略:
    • 减少数据传输量:尽量只传输必要的数据,避免不必要的传输。例如,只发送玩家位置的变化,而不是每帧都发送完整的位置数据。
    • 使用数据压缩技术:在发送数据之前对其进行压缩,接收方再解压。这样可以有效减少网络传输的数据量。Unity自带的UnityWebRequest类支持HTTP压缩。
    • 缓存数据:对于某些不经常变化的数据,可以在客户端缓存起来,减少重复的请求。例如,玩家角色信息可能在游戏开始时下载一次后就被缓存,之后的更新只包含差异数据而不是完整数据。
    • 合理利用协议和格式:根据实际需求选择合适的网络协议和数据格式。例如,对于实时性要求高的数据,可以使用TCP协议;对于小量数据或者对实时性要求不高的场景,可以使用UDP协议。
    • 分批处理数据:如果一次性传输大量数据,可能会导致网络阻塞或延迟。将大量数据分批传输可以有效降低这种情况的发生。
    • 使用合适的同步频率:根据游戏或应用的实际需求,选择合适的同步频率。过高的同步频率可能会导致网络负担过重,而过低的同步频率可能会导致游戏或应用的操作不流畅。
    • 优化网络架构:例如使用中心化的服务器架构、客户端预测和服务器校验等策略来减少网络延迟和抖动。
    • 使用Unity的Networking组件:Unity提供了强大的网络框架,如UNet和Multiplayer组件,这些组件提供了很多预设的解决方案和工具来帮助开发者优化网络传输性能。

猜你喜欢

转载自blog.csdn.net/backlighting2015/article/details/135426996