Unity 项目经验之谈

C#

浏览数:61

2019-10-11

1、美术篇

·美术规范:主要包括材质数量、模型三角面、纹理尺寸、粒子数量、动画帧频。

a、对于导入unity的模型

 需要注意:若勾选上图中”Read/Write Enabled”将会使该模型在内存中存在一份拷贝,用于在脚本中对模型进行修改。若该模型不会在脚本中进行修改,则不勾选。Optimize Mesh 必须勾选,便于引擎底层对Mesh进行优化处理。Normals & Tangents 中的Normals选项若设置为非Import项,可以降低Memory,并减少ipa/apk包体容量。

b、对于unity贴图

Unity支持目前主流的各种图片格式,包括PSD,TGA,PNG,GIF,BMP,JPG,TIFF,PICT等。若图片尺寸不是2的指数,其所占用内存的大小略大于原始尺寸,并且GPU对其进行读取的速度也有可能会略微偏慢。若勾选Generate Mip Maps,改贴图所占用内存会变大33%,但对整体性能有很大提升。

c、对于光照

移动平台推荐使用Baked或 Mixed,避免使用RealTime模式的光照。Render Mode 不推荐使用Important。使用Baked模式时,在菜单Window->Lighting的Object标签页,可通过设置Scale In Lightmap的值,以变更lightmap中有多少像素值用于该GameObject的光照计算。

d、对于音效

Unity支持主流的各种音乐格式,包括mp3,ogg,wav,aif等。Load Type 中的Decompress On Load 选项不推荐用于大文件。大文件推荐使用 Compressed In Memory选项。Load In Background 如果勾选表示该音频文件将在后台加载,而不会造成主线程阻塞。

2、程序篇

a、逻辑架构

设计时应该遵循各功能模块的独立性和封装性,并满足即插即用。比如,整个游戏逻辑可分为;场景模块,角色模块,UI模块,网络通信模块,战斗模块等。模块化设计不仅游戏逻辑树简单易懂,便于代码审查,而且也有益于项目后期做性能优化以及Bug检查。比如,调试Bug时,如果禁用某一个模块,游戏正常运行,并且Bug不再出现,则Bug由该模块引擎的概率很高。同理,在做优化处理是,如果禁用某一个模块,发现CPU负载下降很多,则该模块造成性能瓶颈的概率也很高。

b、脚本应用

Unity的脚本使用托管机制,如果某脚本需要挂载到场景GameObject上,则该脚本需要继承于MonoBehaviour。在菜单Edit-> Project Settings -> Script Execution下,可更改各类脚本的执行顺序。同时,MonoBehaviour的执行并非使用系统反射机制,而是基于队列存储形式。

为了减少代码托管开销,在设计具体脚本时可以做一些优化处理。比如,有一个类ClassA,继承于MonoBehaviour,在其Update函数里会执行具体逻辑。若该类有30个实例同时存在,则会存在30次托管开销,遇到此类情况,建议删除ClassA中的Update函数,转而由自定义的UpdateEx函数替代。同时,额外编写一个ClassAManager类,其缓存一个ClassA的数组或者队列,每次Update时遍历该队列,执行每个实例的UpdateEx函数。(这个还真没注意,以后要试试)

c、Asset管理

Unity中的Asset序列化支持二进制、文本模式、混合模式三种,具体可以在菜单Editor Settings –> Asset Serialization 下进行设置,文本模式采用YAML格式,增加可读性。其中每一个资源文件都可以生成一个对应的meta文件。(其实至今不知道这个序列化到底有什么好处,有哪位读者大大知道,指点一下小菜鸟吧!!!)

在制作场景时,建议把场景中的具体GameObject制作成Prefab,而不是直接使用FBX格式,这样便于引擎底层做资源管理。

Unity引擎中各种美术资源都可以编译成AssetBundle,包括纹理,模型,Prefab,AnimationClip,AudioClip等。同时,AssetBundle支持压缩与非压缩格式,开发者可根据项目实际情况进行设置。

d、动态更新

通常动态更新包括美术资源与脚本的更新,美术资源建议使用AssetBundle,脚本更新在Android平台上可使用dll反射实现(仅限Anadroid平台),或者通过其它第三方非官方模式。(这部分目前还没接触到,自己做的东西还没用过这个,所以还在学习。。。。)

3、优化篇

a、图形优化 – Graphics

要实现图形优化,首先需要熟悉图形学整个渲染管线流程,在此对其作简要介绍:

应用程序 (Application) -> 几何体 (Geometry) -> 光栅化 (Rasterizer)。

其中,几何体阶段具体包括:模型变换(Model & View Transform) -> 顶点着色(Vertex Shading) -> 投影变换(Projection) -> 裁切(Clipping) -> 屏幕映射(Screen Mapping)。

光栅化阶段包括: 三角设置(Triangle Setup) -> 三角遍历(Triangle Traversal) -> 像素作色(Pixel Shading) – > 合并(Merging)。

进行图形优化时,首要步骤即为定位瓶颈在何处,CPU还是GPU?

如果渲染瓶颈发生在CPU,通常对CPU进行的图形相关优化主要涉及以下几点:

合并模型(美术人员手动合并或者使用引擎的Batching技术)

在合并模型时,如果被合并的模型并未使用同一材质,那么该合并操作并不会提升性能。同理,如果被合并的模型使用了多重材质,而并不共用贴图,合并操作也不会提升性能。

减少材质的使用数量,尽量材质共用

纹理拼接,把多张小尺寸纹理拼接到同一张大尺寸纹理中

避免使用多重渲染,比如反射,阴影,像素光照等

动画优化,包括减少骨骼数量,降低动画帧率等

CPU在图形渲染中所承载的计算量没有GPU那么高(前提是设备同时具备GPU和CPU),所以在进行图形优化时,更多是针对GPU端,其具体包括:

模型对象

调整模型三角面是优化美术资源的基本步骤,如果一个角色模型1500面能达到要求,那为何要使用1600面呢。

当场景中某些对象被标记为Static时,禁止在脚本中对其进行位置,朝向,缩放更改,也就是如果需要更改一个对象的Transform属性值,则改对象不应该被标记为Static。

在设计模型时,尽量减少UV映射缝隙和硬边的数量。

光照设计

在移动设备上,尽量使用light map代替实时光照,即便美术人员需要很炫的光照效果,也可以预先由美术人员调节好实时光照效果后再进行light map烘焙。

在某些特定情况下,美术人员可能需要一些特定对象呈现出酷炫的效果,建议使用Shader实现,而非采用增加额外的光源。

减少像素光的数量(像素光是指的什么?求指点),不仅可以降低CPU负载,也可以减少GPU消耗。如果场景中某像素光照作用的两个模型对象距离相隔较远,建议不要对该模型对象进行合并操作。

Shader性能

Unity引擎为开发者提供了大量的内置Shader,基本满足开发者的项目需求。如果某些特殊效果需要自定义Shader,在编写Shader程序时,需要注意一些具体细节。

在Shader程序中减少使用或者不使用条件语句。GPU在硬件层面上与CPU有着极大差异,GPU以ALU(逻辑运算单元)著称,而CPU则存在着大量的控制器。

定义变量时,应考虑变量需要的精度位宽(Float 为32位,half为16位,fixed为10位),比如定义的变量用于UV坐标,则类型通常选择half即可,如果选择float,则会造成带宽浪费以及计算消耗增加。

避免使用复杂的数学计算函数,比如sin,tan,pow,exp,log等,如果实在需要,建议单个Shader程序里该类复杂函数的使用次数不超过一次。

移动平台避免使用Alpha Test和Alpha Blend指令,如果不可避免,建议使用Alpha Blend,而非Alpha Test。

纹理压缩

压缩纹理不仅可以节省内存,同时也节省运算带宽。同时,建议3D模型贴图都应生成对应的Mipmaps,如果选择生成Mipmaps,则该纹理对应的内存大小相对原先会变大33%左右,但如果不选择生成Mipmaps,则在整体性能上会有很大损失。

LOD使用

Unity中关于LOD的使用有LOD Group和Camera.layerCullDistances两种方式。其中LOD Group主要用于大型模型对象,而Camera.layerCullDistances主要用于碎片化的模型对象。

b、物理优化

Unity引擎中物理计算更新在FixedUpdate中完成,根据具体的游戏项目,如果游戏物理更新频率不需要太高,可以在菜单Project Settings -> Time 下更改Fixed TimeStep的值。

使用物理碰撞体时,在满足设计要求前提下,建议使用Sphere Collider 或者 Box Collider代替Mesh Collider。Mesh Collider和wheel Collider其计算较为复杂,能避免使用则避免之。

被标记为Static的碰撞体,禁止对其移动位置。

如果对象不需要Rigidbody组件,一律删除。

在脚本中如果通过Physics.RaycastAll类似的接口获取固定对象,缓存所得到的对象,禁止通过程序中每帧调用该类接口去获取一个固定的对象。

c、程序优化

程序优化的根本在于设计,在于程序员在编写程序时是否有细心思考。尽管Unity引擎本身由C++编写,但上层逻辑脚本基于Mono体系,内存管理使用GC机制。

GC机制在内存管理存在实时性欠缺,而Mono内存基于内存池,即Mono所申请内存不会得到释放(只有在内存池中内存不够使用时,Mono才会申请新的内存),只会返回内存池。

基于以上机制限制,应禁止频繁申请内存,尽量使用对象池管理对象,比如在Update函数里每帧都new 一个数组或者队列,这样极有可能造成GC还未回收先前的内存,内存池中内存不足,又重复申请新的内存,最终Mono内存池越来越大,这是很多开发者都曾遇到的问题。

在Unity引擎层面,可以通过使用IL2CPP技术,使脚本运行速度更快,同时,也可以在Script Call Optimization中设置忽略异常处理。针对具体脚本中负载瓶颈的定位,建议多使用引擎的Profiler工具。

(ps:对于程序优化这部分,基本不理解,还是基础差。。。。哪位能解释一下,或者提供些相关资料。谢谢)

d、文件优化

如果要降低整体文件大小,首先得知道整个项目中每类资源文件具体大小。在Unity完成相关编译后,选择Console -> Open Editor Log选项,会得到如下图所示的信息:

(ps,然而我在windows下打开log文件并没有所有资源的大小描述。。。。纠正一下,在buliding的时候勾选这几个就可以看见了。)

其包括了具体各类资源的总大小,开发者可按照文件大小建立优化顺序。其中,具体在设计到脚本大小优化时,根据项目组具体情况,可以考虑选择.Net 2.0 Subset,在iOS平台也可使用Stripping方法。同时,建议把Resources文件夹下不曾使用的资源删除,需要使用的资源用AssetBundle代替。Resources文件夹下文件太多会严重影响程序的启动时间。

文章摘自Unity官方微信,如有侵权请联系本人下架。

作者:湛月