U3D开发性能优化笔记(待增长版本.x)

http://blog.csdn.net/kaitiren/article/details/45071997
javascript

此总结由本身经验及网上收集整理优化内容 包括:
.代码方面;
.函数使用方面;
.ui注意方面;
.数学运算方面;
.内存方面;
.垃圾回收方面 等等...
本着相互交流 共同进步的原则

html


U3D开发性能优化笔记:
java

.NGUI: Atlas优化;ios

.poolmanager使用;程序员

.控制同屏drawcall次数;
算法

.SHADER优化顶点和运算;数组

.合批与动态剔除;缓存

.逻辑部分优化;(如看到不到的物件不要作公告板位置运算,不要播放animation)
性能优化

.物理帧UPDATE下降;服务器

.关闭垂直同步,下降图片采样,声音预加载 方案 等等 。。;

.模型骨骼不要超过32根;

.贴图不要太大,建议512 *512 如下;

.少用 CUTOFF和 aplha混合;

.3D游戏效率基本原则就是费内存省CPU 和GPU;

.NGUI ANIMATION ANIMTOR 碰撞检测 特效 渲染 这些都是性能消耗的大头;

.Occlusion Culling使用;

​.只用一个mesh renderer, 少用多materials, 最多3个;

.每一个角色尽可能使用一个 Skinned Mesh Renderer;

.面数问题每一个模型不要超过300~1500;

.模型尽可能不要分开若是多个模块会屡次调用dc;

.通常角色应该没有 IK 结点.

.尽可能不用像素光(pixels Lights).

.不用 软阴影(soft shadow), 或者不用阴影(No Shadows)

.灯光能不用就不用阴影能够用面来代替.

.少用实时灯光尽可能使用lightmap(强大的Unity内置了一个强大的光照图烘焙工具Beast,这个东东是Autodesk公司的产品);

.transformOnGUI (运算上的优化远比不上 绘制效率上的优化,少个dc可能就比得上这些了)

.少用transform, 多用 myCachedTransform.

.动态物体的相关优化 
优化主要分为两个方向,一个是资源相关优化和引擎相关的优化。资源相关的优化,大概分为动态物体、静态物体、纹理数据、音频数据、程序包数据。对于动态物体好比NPC、怪物等,须要对面片数量的控制,大概在3002000面。1500面就能够体现人物细节,但若是是人物比较多,可能要下降面数,不要低于300。另外,一方面是控制Skinned Mesh Renderer的数量;另外一方面是控制材质数量在13种。人物最好用小于30根骨骼,若是你用的骨骼越多,耗费的CPU就更多,因此在移动平台上尽可能少于30根。如今咱们看其余动态物体,利用Dynamic Batching进行合批。这个下雨特效并非系统作的,是包含不少雨点的网格进行重复拷贝,而后错乱移动实现的。每个雨点并非一个粒子,这样能减小不少CPU的消耗,每个总体网格都会有一个顶点的控制,经过控制顶点数量,对系统实现雨点效果来讲,这是一个至关省时省力的方法。


.静态物体的相关优化 
下面咱们来看静态物体,静态物体也是要控制面数和顶点数,顶点数少于500个。static是不会进行移动缩放、旋转的,把它标记为static,固然他们的材质是同样的。不要添加animation组建,对于静态物体来讲,这个组件毫无心义,能把他丢掉就丢掉,由于这对CPU的消耗是很是客观的。 


.音频程序的优化 
关于音频时间的播放,好比背景音乐,建议使用MP3压缩格式,好比音效,要求数据尽快加载,这些数据比较小就能够,使用WAVAIF未压缩音频格式。关于程序包的优化,不少开发者会埋怨说打出来的包太大,如今介绍减小程序包的方法,首先使用压缩格式的纹理,以显卡的压缩格式保存,使用压缩网格和动画数据。网格压缩是先采用量化处理,固然这个压缩是保证在包里面的数据小,但运行时占用的内存没有减小,由于咱们并无把顶点删除,可是对动画数据来讲,动画数据通过压缩处理后下降,能够减小游戏列层。


.代码尽可能不要使用System.xml,咱们建议使用Mono.xml。启用Stripping来减小库的大小,使用剥离方式。


.引擎相关优化和物理相关优化 
下来是引擎相关的优化,例如光照设置、相继设置、粒子特效、物理特效等。那拿光照设置来讲,光源所有的实时光照这是很恐怖的,每一次实施光照表明着每一次使用消耗,怎么优化?有人使用LightMapping来制做静态场景,他的好处是不须要用多张实施光照,而给场景很好的光照效果。有人使用Light Probes代替实时光照,好处是彻底不用怎么消耗,并且运做性能也很是高。在有些时候使用Light Probes代替光照,他能跟场景很好的融合,在一个角落里,这个任务会被阴影打得暗一些。若是说场景中确实须要一些实时光源,那么确定是须要作过优化设置的实时光源,控制important的光源个数。若是说光源有些地方产生了交叉光,这个时候你能够经过设置Pxel Light,控制每个光源都只接受一个动态光照,数目大概是12个。对于关闭光源的实时阴影,并非全部平台都支持实时阴影,消耗也很是大,不建议你们使用。关于相机方面的设置,平面越近,渲染越少。咱们更建议使用分层,好比远处的建筑,对于建筑物的裁减平面远一些,若是是花草,就可使用平面就近一些。如今看一下粒子特效,粒子也是游戏中须要优化的东西,建议屏幕中最大的粒子数不要超过200,同时每一个发射器发射的最大粒子数不要超过50。粒子尺寸也要尽量小,最终在屏幕有多少像素。他们中间的像素可能会被渲染不少次,至少四五次,这时发现粒子系统仅像素就填充了更多屏幕,这时候对游戏来讲很是耗费,对游戏的其余功能性能也有所影响。另一方面,对于很是小的粒子,尽可能不要开启粒子碰撞功能。


.物理相关优化,物理尽量使用Sphere CoilliderBox Coillider等,尽可能避免使用Meh Colllider等。渲染设置,避免使用Alpha Test,由于很是耗时,性价比很低。关于Sttic Batching,对静态物体进行Batch,对几何数据的大小没有限制。物体被合并后会带来一些内存消耗,好比说有控制网格的物体,用Batch会合并成大物体。Dynamic Batching目前仅支持小于900顶点的网格物体。如何理解900呢,其实就至关于900个顶点数据大小的物体,若是说使用PositionNormalUV三种属性,那么你只能Batch300个顶点。总体缩放的物体不能被Batch,除非他们的缩放值相同。以前有一个客户作特效,使用Batch机制把面片合并,最终让全部面片共享一个纹理,这时候发现这些面片没有被Batch出来,致使运行游戏时大概放三个技能就10多个招套。对于非总体用户体,他们的Batch是须要很好利用到。


.纹理合并优化 
如今来看纹理合并,纹理合并就是为了特到Batch数量,合并物体首先须要合并工具,还要修改使用纹理的网格的UV,使他们使用纹理。合并纹理主要是参照Batch,提升渲染性能。但在合并材质后须要注意的是脚本访问Renderer被拷贝。/*安挡剔除,建议使用PVS技术。建议你们使用自定义shader,例如高光效果,高光效果可能不须要作一些入射线的检测,只是简单把他的值放大也能够模拟高光效果,从而减小一些消耗。 
另一个是用profiler,经过他给的数据进行针对性的优化。以上是跟你们介绍优化的内容,如何做出良好优化,必定要作好良好的规划,到后期就不会很麻烦,若是规划没有作好有可能会给程序带来很大压力,结果可能很不乐观。*/最后,要不断实验不断总结才能达到本身满意的效果。


.下降Drawcal的话,有以下两点小建议 
1)不要用Unity自带UI或者iGUI, NUI 或者EZ GUI 
(2)建立好的GameObject不用了就最好及时 删除 设置activefalse/移出屏幕 。 这几种方法均可以去掉该物体致使增长的Drawcall.



最近一段时间一直在作Unity IOS设备上的资源优化,结合Unity的官方文档以及本身遇到的实际问题,我把本身认为一些重要的信息罗列在下面,并尽量对将其量化,以方便更多须要作优化的朋友。 
1、 角色 
每一个角色尽可能使用一个 Skinned Mesh Renderer 
这是由于当角色仅有一个 Skinned Mesh Renderer 时, Unity 会 使用可见性裁剪和包围体更新的方法来优化角色的运动,而这种优化只有在角色仅含有一个 Skinned Mesh Renderer 时才会启动。 
角色 Material 数量 
2-3  
骨骼数量 
小于 30  
面片数量 
300-1500 
通常角色应该没有 IK 结点 
这是由于角色的动做大多数都是事先设定好的,并不须要通过 IK 操做来进行实时计算( Rogdoll 除外),因此在模型导入时,不要将 IK 结点一块儿导入。 
2、 静态实体 
不要附加 Animation Component 
在静态实体上附加 Animation 部件虽然对结果没有影响,但却会增长必定的 CPU 开销来调用这一组件,因此尽可能去掉该组件。 
网格顶点数 
小于 500 
UV 值范围尽可能不要超过( 0, 1 )区间 
尽可能保证 UV 值不越界,这对于未来的纹理拼合优化颇有帮助。 
3、 地形 
地形的分辨率大小 
长宽均尽可能小于 257 。这是由于地形太大,会形成大量顶点数据,给你的内存带宽形成必定的影响,在目前的 ios 设备中,内存带宽是很是有限的,须要尽可能节省。同时,若是用 Unity 自带的地形,必定也要使用 Occlusion Culling ,由于 Unity 的刷地形工具虽然方便,但倒是 framekiller ,刷过以后,你会发现 drawcall 增长的很是多。 
混合纹理数量 
不要超过 。地形的混合操做是很耗时的,应该尽可能避免。能合并的纹理尽可能合并。 
4、 纹理 
纹理格式 
建议 png 或 tga 。不用转成 ios 硬件支持的 PVRTC 格式,由于 Unity 在发布时会帮你自动转的。 
纹理尺寸 
长宽小于 1024 。同时应该尽量地小,够用就好,以保证纹理对内存带宽的影响达到最小。 
支持 Mipmap 
建议生成 Mipmap 。虽然这种作法会增长一些应用程序的大小,但在游戏运行时,系统会根据需求应用 Mipmap 来渲染,从而减小内存带宽。 
检查 Alpha  
若是纹理的 alpha 通道均为 ,则用 RGB 的 24 位纹理来代替 RGBA 的 32 位纹理。(听说 Unity 内部会进行自动检测) 
5、 光源 
光源“ Important ”个数 
建议 个,通常为方向光。“ Important ”个数应该越小越少。个数越多, drawcall 越多。 
Pixel Light 数目 
1-2 个。 
6、 粒子特效 
屏幕上的最大粒子数 
建议小于 200 个粒子。 
每一个粒子发射器发射的最大粒子数 
建议不超过 50 个。 
粒子大小 
若是能够的话,粒子的 size 应该尽量地小。由于 Unity 的粒子系统的 shader 不管是 alpha test 仍是 alpha blending 都是一笔不小的开销。同时,对于很是小的粒子,建议粒子纹理去掉 alpha 通道。 
尽可能不要开启粒子的碰撞功能。 
很是耗时。 
7、 音频 
游戏中播放时间较长的音乐(如背景音乐) 
使用 .ogg 或 .mp3 的压缩格式。 
较短音乐(如枪声) 
使用 .wav 和 .aif 的未压缩音频格式。 
8、 相机 
裁剪平面 
将远平面设置成合适的距离。远平面过大会将一些没必要要的物体加入渲染,下降效率。 
根据不一样的物体设置不一样的远裁剪平面 
Unity 提供了能够根据不一样的 layer 来设置不一样的 view distance ,因此咱们能够实现将物体进行分层,大物体层设置的可视距离大些,而小物体层能够设置地小些,另外,一些开销比较大的实体(如粒子系统)能够设置得更小些等等。 
9、 碰撞 
尽可能不用 MeshCollider 
若是能够的话,尽可能不用 MeshCollider ,以节省没必要要的开销。若是不能避免的话,尽可能用减小 Mesh 的面片数,或用较少面片的代理体来代替。 
10、 其余 
Drawcall 
尽量地减小 Drawcall 的数量。 
iOS 设备上建议不超过 100  
减小的方法主要有以下几种: Frustum Culling , Occlusion Culling , Texture Packing  
Frustum Culling 是 Unity 内建的,咱们须要作的就是寻求一个合适的远裁剪平面; Occlusion Culling ,遮挡剔除, Unity 内嵌了 Umbra ,一个很是好 OC 库。但 Occlusion Culling 也并非放之四海而皆准的,有时候进行 OC 反而比不进行还要慢,建议在OC 以前先肯定本身的场景是否适合利用 OC 来优化; 
Texture Packing ,或者叫 Texture Atlasing ,是将同种 shader 的纹理进行拼合,根据 Unity 的 static batching 的特性来减小draw call 。建议使用,但也有弊端,那就是必定要将场景中距离相近的实体纹理进行拼合,不然,拼合后极可能会增长每帧渲染所需的纹理大小,加大内存带宽的负担。 
这也就是为何会出现“ DrawCall 降了,渲染速度也变慢了”的缘由。 

非运动物体尽可能打上 Static 标签 
Unity 在运行时会对 static 物体进行自动优化处理,因此应该尽量将非运行实体勾上 static 标签。 

场景中尽量地使用 prefab 
尽量地使用 prefab 的实例化物体,以下降内存带宽的负担。检查实体的 PrefabType ,尽可能将其变成 PrefabInstance ,而不是ModelPrefabInstance 


来自其余开发者的笔记:

总的来讲,Unity没有啥天坑。只要肯研究,后期都能改进,也都不会影响到上线。


小坑太多,说不完。Unity上手容易坑太多,基本事件机制,生存周期,场景和资源管理,mono虚拟机的gc机制都是坑。


要说的话,真正影响到架构的是(排序)

1. 是否要用lua

2. (对于需操做的游戏)客户端游戏如何作战斗验证



公司的话,推荐:

参加Unity年会

购买Unity的官方支持问答平台,人有源代码,还能找总部



下面列举小坑吧。不建议都绕开,毕竟没有那么多时间作前期调研的。

对应版本Unity4.x


1. 客户端程序层面


总的来讲C#超级给力的,不过别玩脱了


1) mono虚拟机gc


Unity的mono虚拟机使用不分代的gc算法,临时对象积攒起来,致使重量级GC游戏频繁卡顿。


Unity官方:认真review每帧20B以上,以及一次2K以上的GC Alloc的行为。传闻:Unity5会改进。


评价:请像C++同样精确了解各类行为的gc,foreach 都不要随便用。严重,但游戏是能够卡巴卡巴上线的。后期一位核心开发人员修2~3周。


2) 苹果aot编译问题:模板问题


mono在苹果上采用aot将C#编译为静态代码。首先,依赖于动态代码生成的复杂模板容易运行时崩溃;其次,mono会将客户端生成一个库。模板代码实例化容易膨胀致使该库超过40M而没法连接。


实战:碰到了改写法吧。不过我本人是静态类型检查派的。


3) 少用coroutine


yield只支持try--finally,与异常体系兼容性极差;难以提供返回值;异步自己是非线性的,很难保证逻辑完备。


实战:复杂异步逻辑用状态机。不致命,多修bug也能抗过。


4) 自行处理配置数据序列化


严重影响配置读取速度。C#自带的xml序列化很慢,自带的二进制序列化也不够快。


实战:打包配置考虑protobuf或者代码生成器。中后期一周左右。


5) 反射


手机上jit状况下,第一次反射一个类很慢。乱用足够影响启动速度。


6) 本地化


若是公司习惯于作海外市场,一开始就能够考虑全套本地化方案。后期改须要一我的1~2个月工做量。


2 资源优化


Unity资源优化,一个靠谱的TA很重要。


1) 资源内存占用


512内存机器能用的资源大概只有50~60M。需透彻研究贴图。考虑换皮怪资源复用、UI的图集合理化。没有UI优化经验的话,强烈建议一个核心开发死跟,像抠代码优化同样优化图集总结经验。这个后期很难收场。每一个粒子发射器占用10K内存;有些项目在动画上会有内存问题。


2) 关注资源包大小


最大的是贴图和骨骼动画。贴图关注内存便可。骨骼动画能够占到模型的一半大小,重作的话有各类优化方案。但超标后期也很难收场。


3) 依赖打包


Unity4.x和Unity5彻底不一样。其中Unity4.x机制庞大繁杂容易错,要有心理准备。扯一些要点:


* 必定要搞清其内存占用和生存周期。要实测,特别容易跌眼镜。

* 每一个API都有坑。我我的目前推荐压缩模式、LoadFromCache,此时不能拆太碎。战斗前预加载。

* shader加载慢,应当放入依赖包

* bundle不能重名


4) 场景、drawcall、camera


场景面多了考虑动态batching。不一样材质透明物体(例如粒子)穿插可能引发drawcall暴增。camera是重型对象,越少越好。


5) svn


资源选Text模式、显式保存.meta,便于版本管理。资源分人或者锁了改,规避冲突。


3 Unity


和Flash同样容易学的3D编辑器


1 ) 事件机制


Unity事件机制很很差用。单个对象,Awake,Start,Enable调用时机至关复杂。Unity彻底不保证多个对象的事件执行顺序,致使不少人绕开Start。不恰当的使用事件,很容易致使父子对象不在同一帧出现,画面不干净。


Destroy操做是延迟的,对象会活到帧的结尾,而后一定销毁。库级设计时,必须考虑到这一点(例如对象池/动画库)。


2) 资源管理


只说Unity4.x。合理作法是依赖很卡的UnloadUnusedAssets、LoadScene清理无引用资源(另注意前者是异步的),或者Bundle.Unload(true),这些方案各有限制。试图更细粒度手工清理的困难在于,并不存在系统性文档解释Unity资源的分类和生存周期,且Destroy操做很保守。例如,销毁mesh时,并不会销毁material、texture,更不会清理脚本资源。


此外,特定的普通操做会形成资源克隆。例如访问Renderer.meterial,Animation.AddClip。


4 NGUI


久经验证的掉链子王。新项目也能够尝尝uGUI


1) panel重绘


widget改变后,所在panel须要生成多边形,很慢,坑新人没商量,注意合理分panel。panel中多边形过多会爆(貌似是65535个顶点?)。

uGUI原理相同,就是c代码比C#快很多。


2) panel渲染顺序


搞清楚ui上放置3D物体咋办,ui如何和特效混合排序。


3) 策划/美术ui规范


潜规则不少。Anchor、动画不可做用于同一个物体。widget必须是panel的子节点,否则他就会本身造panel,常常搞出乱子。再加上上面的panel规则等,要策划美术折腾ui可费神了。


项目组自制UI编辑器天然是极好的,不过不必定必要。


4) 建立速度慢


因为序列化字段多,NGUI对象建立可致使卡顿。多状态对象不要靠隐藏-显示,而要动态建立。尤为是状态中包含粒子发生器/Animation,这俩还有内存问题(10K一个)。


5) 与Unity事件机制强耦合


与Unity的事件机制强耦合,不完备,容易有bug。例如,panel绘制依赖于LateUpdate。coroutine中同时关闭旧界面,建立新界面,此时当前帧 LateUpdate 已过,表现为有一帧画面为空白。


代码部分的优化方案:

1.  尽可能避免每帧处理, 能够每隔几帧处理一次
好比:
[C#]  纯文本查看  复制代码
?
1
function Update() { DoSomeThing(); }
可改成每5 帧处理一次:
[C#]  纯文本查看  复制代码
?
1
function Update() { if (Time.frameCount % 5 == 0) { DoSomeThing(); } }
2.  定时重复处理用InvokeRepeating  函数实现

好比,启动0.5 秒后每隔1 秒执行一次 DoSomeThing  函数:
[C#]  纯文本查看  复制代码
?
1
2
function Start() { InvokeRepeating( "DoSomeThing" , 0.5, 1.0); }
  CancelInvoke( "你调用的方法" ); 中止InvokeRepeating
3.  优化 Update,FixedUpdate, LateUpdate  等每帧处理的函数, 函数里面的变量尽可能在头部声明。
好比:
[C#]  纯文本查看  复制代码
?
1
function Update() { var pos: Vector3 = transform.position; }
可改成
[C#]  纯文本查看  复制代码
?
1
private var pos: Vector3; function Update(){ pos = transform.position; }


4.  主动回收垃圾
给某个 GameObject  绑上如下的代码:
[C#]  纯文本查看  复制代码
?
1
function Update() { if (Time.frameCount % 50 == 0) { System.GC.Collect(); } }


5.  运行时尽可能减小 Tris   Draw Calls

预览的时候,可点开 Stats ,查看图形渲染的开销状况。特别注意 Tris   Draw Calls  这两个参数。

通常来讲,要作到:

Tris  保持在 7.5k  如下

Draw Calls  保持在 35  如下

6.  压缩 Mesh

导入 3D  模型以后,在不影响显示效果的前提下,最好打开 Mesh Compression

Off, Low, Medium, High  这几个选项,可酌情选取。 对于单个Mesh最好使用一个材质。

7.  避免大量使用 unity  自带的 Sphere  等内建 Mesh

Unity  内建的 Mesh ,多边形的数量比较大,若是物体不要求特别圆滑,可导入其余的简单3D 模型代替。

8.  优化数学计算
尽可能避免使用float,而使用int,特别是在 手机游戏中,尽可能少用复杂的数学函数,好比sin,cos等函数。改除法/为乘法,例如:使用x*0.5f而不是 x/2.0f 。

9.若是你作了一个图集是1024X1024 的。此时你的界面上只用了图集中的一张很小的图,那么很抱歉1024X1024 这张大图都须要载入你的内存里面,1024 就是4M 的内存,若是你作了10 1024 的图集,你的界面上恰好都只用了每一个图集里面的一张小图,那么再次抱歉你的内存直接飙40M 。意思是任何一个4096 的图片,无论是图集仍是texture ,他都占用4*4=16M

====================================================================分割线=====================================================

一、在使用数组或ArrayList对象时应当注意

本帖隐藏的内容

[C#]  纯文本查看  复制代码
?
1
2
3
4
5
length=myArray.Length; 
for ( int i=0;i<length;i++) 
   
}
避免
[C#]  纯文本查看  复制代码
?
1
2
3
4
for ( int i=0;i<myArray.Length;i++) 
   
}


二、少使用临时变量,特别是在Update OnGUI等实时调用的函数中。
[C#]  纯文本查看  复制代码
?
1
2
3
4
5
void Update() 
    Vector3 pos; 
    pos=transform.position; 
}

能够改成:
[C#]  纯文本查看  复制代码
?
1
2
3
4
5
private Vector3 pos; 
void Update() 
    pos=transform.position; 
}
  
三、若是可能,将GameObject上没必要要的脚本disable掉。
若是你有一个大的场景在你的游戏中,而且敌方的位置在数公里意外,
这时你能够disable你的敌方AI脚本直到它们接近摄像机为止。
一个好的途径来开启或关闭GameObject是使用SetActiveRecursively(false),而且球形或盒型碰撞器设为trigger。

四、删除空的Update方法。
当经过Assets目录建立新的脚本时,脚本里会包括一个Update方法,当你不使用时删除它。

五、引用一个游戏对象的最合乎逻辑的组件。
有人可能会这样写someGameObject.transform,gameObject.rigidbody.transform.gameObject.rigidbody.transform,可是这样作了一些没必要要的工做,
你能够在最开始的地方引用它,像这样:
[C#]  纯文本查看  复制代码
?
1
2
3
4
5
privateTransform myTrans;
void Start()
{
     myTrans=transform;
}

六、协同是一个好方法。
可使用协同程序来代替没必要每帧都执行的方法。(还有InvokeRepeating方法也是一个好的取代Update的方法)。
七、尽量不要再Update或FixedUpdate中使用搜索方法(例如GameObject.Find()),你能够像前面那样在Start方法里得到它。
八、不要使用SendMessage之类的方法,他比直接调用方法慢了100倍,你能够直接调用或经过C#的委托来实现。
九、使用javascript或Boo语言时,你最好肯定变量的类型,不要使用动态类型,这样会下降效率,
你能够在脚本开头使用#pragmastrict 来检查,这样当你编译你的游戏时就不会出现莫名其妙的错误了。
====================================================================分割线=====================================================
一、顶点性能     
通常来讲,若是您想在iPhone 3GS或更新的设备上每帧渲染不超过40,000可见点,
那么对于一些配备 MBX GPU的旧设备(好比,原始的 iPhone,如 iPhone 3g和 iPod Touch第1和第2代)来讲,你应该保证每帧的渲染顶点在10000如下。
二、光照性能     
像素的动态光照将对每一个受影响的像素增长显著的计算开销,并可能致使物体会被渲染屡次。
为了不这种状况的发生,您应该避免对于任何单个物体都使用多个像素光照,并尽量地使用方向光。
须要注意的是像素光源是一个渲染模式(Render Mode)设置为重要(Important)的光源。
像素的动态光照将对顶点变换增长显著的开销。因此,应该尽可能避免任何给定的物体被多个光源同时照亮的状况。
对于静态物体,采用烘焙光照方法则是更为有效的方法。
三、角色     
每一个角色尽可能使用一个Skinned Mesh Renderer,这是由于当角色仅有一个 Skinned Mesh Renderer 时,
Unity 会使用可见性裁剪和包围体更新的方法来优化角色的运动,而这种优化只有在角色仅含有一个 Skinned Mesh Renderer时才会启动。
角色的面数通常不要超过1500,骨骼数量少于30就好,角色Material数量通常1~2个为最佳。
四、静态物体     
对于静态物体定点数要求少于500,UV的取值范围不要超过(0,1)区间,这对于纹理的拼合优化颇有帮助。
不要在静态物体上附加Animation组件,虽然加了对结果没什么影响,可是会增长CPU开销。
五、摄像机     
将远平面设置成合适的距离,远平面过大会将一些没必要要的物体加入渲染,下降效率。
另外咱们能够根据不一样的物体来设置摄像机的远裁剪平面。Unity 提供了能够根据不一样的 layer 来设置不一样的 view distance ,
因此咱们能够实现将物体进行分层,大物体层设置的可视距离大些,而小物体层能够设置地小些,
另外,一些开销比较大的实体(如粒子系统)能够设置得更小些等等。
六、DrawCall      
尽量地减小 Drawcall 的数量。 IOS 设备上建议不超过 100 。
减小的方法主要有以下几种: Frustum Culling ,Occlusion Culling , Texture Packing 。 Frustum Culling 是 Unity 内建的,咱们须要作的就是寻求一个合适的远裁剪平面;
Occlusion Culling ,遮挡剔除, Unity 内嵌了 Umbra ,一个很是好 OC 库。
但 Occlusion Culling 也并非放之四海而皆准的,有时候进行 OC 反而比不进行还要慢,
建议在 OC 以前先肯定本身的场景是否适合利用 OC 来优化; Texture Packing ,或者叫 Texture Atlasing ,
是将同种 shader 的纹理进行拼合,根据 Unity 的 static batching 的特性来减小 draw call 。
建议使用,但也有弊端,那就是必定要将场景中距离相近的实体纹理进行拼合,不然,拼合后极可能会增长每帧渲染所需的纹理大小,
加大内存带宽的负担。这也就是为何会出现“ DrawCall 降了,渲染速度也变慢了”的缘由。
===========================================分割线==========================
1.粒子系统运行在iPhone上时很慢,怎么办?
答:iPhone拥有相对较低的fillrate 。
若是您的粒子效果覆盖大部分的屏幕,并且是multiple layers的,这样即便最简单的shader,也能让iPhone傻眼。
咱们建议把您的粒子效果baking成纹理序列图。
而后在运行时可使用1-2个粒子,经过动画纹理来显示它们。这种方式能够取得很好的效果,以最小的代价。  

===========================================分割线==============================
1.操做transform.localPosition的时候请当心
移动GameObject是很是日常的一件事情,如下代码看起来很简单:
[C#]  纯文本查看  复制代码
?
1
transform.localPosition += new Vector3 ( 10.0f * Time.deltaTime, 0.0f, 0.0f );


可是当心了,假设上面这个GameObject有一个parent, 而且这个parent GameObject的localScale是(2.0f,2.0f,2.0f)。你的GameObject将会移动20.0个单位/秒。
由于该 GameObject的world position等于:
[C#]  纯文本查看  复制代码
?
1
Vector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x, my.localPosition.y * parent.lossyScale.y, my.localPosition.z * parent.lossyScale.z );Vector3 worldPosition = parent.position + parent.rotation * offset;


换句话说,上面这种直接操做localPosition的方式是在没有考虑scale计算的时候进行的,为了解决这个 问题unity3d提供了Translate函数,
因此正确的作法应该是: 
[C#]  纯文本查看  复制代码
?
1
transform.Translate ( 10.0f * Time.deltaTime, 0.0f, 0.0f );


曝出在Inspector的变量一样的也能被Animation View Editor所使用
有时候咱们会想用Unity3D自带的Animation View Editor来作一些简单的动画操做。而Animation Editor不只能够操做Unity3D自身的component,
还能够操做咱们自定义的MonoBehavior中的各个Property。因此加入 你有个float值须要用曲线操做,你能够简单的将它曝出到成能够serialize的类型,如:
[C#]  纯文本查看  复制代码
?
1
public float foobar = 1.0f;

这样,这个变量不只会在Inspector中出现,还能够在animation view中进行操做,生成AnimationClip供咱们经过AnimationComponent调用。
范例:
[C#]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class TestCurve : MonoBehaviour
{
public float foobar = 0.0f;
IEnumerator Start ()
{
yield return new WaitForSeconds (2.0f);
animation.Play( "foobar_op" );
InvokeRepeating ( "LogFoobar" , 0.0f, 0.2f );
yield return new WaitForSeconds (animation[ "foobar_op" ].length);
CancelInvoke ( "LogFoobar" );
}
void LogFoobar ()
{
Debug.Log( "foobar = " + foobar); }}



2.GetComopnent<T> 能够取父类类型
Unity3D 容许咱们对MonoBehavior作派生,因此你可能有如下代码:

[C#]  纯文本查看  复制代码
?
1
public class foo : MonoBehaviour { ...} public class bar : foo { ...}


假设咱们如今有A,B两个GameObject, A包含foo, B包含bar, 当咱们写

[C#]  纯文本查看  复制代码
?
1
foo comp1 = A.GetComponent<foo>();bar comp2 = B.GetComponent<bar>();


能够看到comp1, comp2都获得了应得的Component。那若是咱们对B的操做改为:

[C#]  纯文本查看  复制代码
?
1
foo comp2 = B.GetComponent<foo>();


答案是comp2仍是会返回bar Component而且转换为foo类型。你一样能够用向下转换获得有效变量:

bar comp2_bar = comp2 as bar;
合理利用GetComponent<base_type>()可让咱们设计Component的时候耦合性更低。

3.Invoke, yield 等函数会受 Time.timeScale 影响
Unity3D提供了一个十分方便的调节时间的函数Time.timeScale。对于初次使用Unity3D的使用者,
会误导性的认为Time.timeScale一样能够适用于游戏中的暂停(Pause)和开始(Resume)。
因此不少人有习惯写:
[C#]  纯文本查看  复制代码
?
1
Time.timeScale = 0.0f


对于游戏的暂停/开始,是游戏系统设计的一部分,而Time.timeScale不不是用于这个部分的操做。
正确的作法应该是搜集须要暂停的脚本或 GameObject,
经过设置他们的enabled = false 来中止他们的脚本活动或者经过特定函数来设置这些物件暂停时须要关闭那些操做。

Time.timeScale 更多的是用于游戏中慢镜头的播放等操做,在服务器端主导的游戏中更应该避免此类操做。
值得一提的是,Unity3D的许多时间相关的函数都和 timeScale挂钩,而timeScale = 0.0f将使这些函数或动画处于彻底中止的状态,这也是为何它不适合作暂停操做的主要缘由。

这里列出受timeScale影响的一些主要函数和Component:
MonoBehaviour.Invoke(…)
MonoBehaviour.InvokeRepeating(…)
yield WaitForSeconds(…)
GameObject.Destroy(…)
Animation Component
Time.time, Time.deltaTime

4.Coroutine 和 IEnumerator的关系
初写Unity3D C#脚本的时候,咱们常常会犯的错误是调用Coroutine函数忘记使用StartCoroutine的方式。如:

TestCoroutine.cs
[C#]  纯文本查看  复制代码
?
1
IEnumerator CoLog () { yield return new WaitForSeconds (2.0f); Debug.Log( "hello foobar" );}


当咱们用如下代码去调用上述函数:
[C#]  纯文本查看  复制代码
?
1
TestCoroutine testCo = GetComponent<TestCoroutine>();testCo.CoLog ();testCo.StartCoroutine ( "CoLog" );


那么testCo.CoLog()的调用将不会起任何做用。

5.StartCoroutine, InvokeRepeating 和其调用者关联
一般咱们只在一份GameObject中去调用StartCoroutine或者InvokeRepeating,
咱们写:
[C#]  纯文本查看  复制代码
?
1
StartCoroutine ( Foobar() );InvokeRepeating ( "Foobar" , 0.0f, 0.1f );


因此若是这个GameObject被disable或者destroy了,这些coroutine和invokes将会被取消。就比如咱们手动调用:
[C#]  纯文本查看  复制代码
?
1
StopAllCoroutines ();CancelInvoke ();


这看上去很美妙,对于AI来讲,这就像告诉一个NPC你已经死了,你本身的那些小动做就都听下来吧。

可是注意了,假如这样的代码用在了一个Manager类型的控制AI上,他有可能去控制其余的AI, 也有可能经过Invoke, Coroutine去作一些微线程的操做,这个时候就要明确StartCoroutine或者InvokeRepeating的调用者的设计。讨论以前我 们先要理解,StartCoroutine或InvokeRepeating的调用会在该MonoBehavior中开启一份Thread State, 并将须要操做的函数,变量以及计时器放入这份Stack中经过并在引擎每帧Update的最后,Renderer渲染以前统一作处理。因此若是这个 MonoBehavior被Destroy了,那么这份Thread State也就随之消失,那么全部他存储的调用也就失效了。

若是有两份GameObject A和B, 他们互相知道对方,假如A中经过StartCoroutine或InvokeRepeating去调用B的函数从而控制B,这个时候Thread State是存放在A里,当A被disable或者destroy了,这些可能须要一段时间的控制函数也就失效了,这个时候B明明还没死,也不会动了。更 好的作法是让在A的函数中经过B.StartCoroutine ( … ) 让这份Thread State存放于B中。

[C#]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// class TestCortouine
public class TestCoroutine : MonoBehaviour
{
public IEnumerator CoLog ( string _name )
{
Debug.Log(_name + " hello foobar 01" );
yield return new WaitForSeconds (2.0f);
Debug.Log(_name + " hello foobar 02" ); }}
// component attached on GameObject A
public class A: MonoBehaviour
{
public GameObject B;  void Start ()
{ TestCoroutine compB = B.GetComponent<TestCoroutine>(); 
// GOOD, thread state in B
// same as: comp
B.StartCoroutine ( "CoLog" , "B" );
compB.StartCoroutine ( compB.CoLog( "B" ) );
// BAD, thread state in A
StartCoroutine ( compB.CoLog( "A" ) );
Debug.Log( "Bye bye A, we'll miss you" );
Destroy(gameObject);
// T_T I don't want to die... }}


以上代码,获得的结果将会是:
B hello foobar 01A hello foobar 01Bye bye A, we'll miss youB hello foobar 02
如不须要Start, Update, LateUpdate函数,请去掉他们
当你的脚本里没有任何Start, Update, LateUpdate函数的时候,Unity3D将不会将它们加入到他的Update List中,有利于脚本总体效率的提高。

咱们能够从这两个脚本中看到区别:

Update_01.cs
[C#]  纯文本查看  复制代码
?
1
public class Update_01 : MonoBehaviour { void Start () {} void Update () {}}


Update_02.cs
[C#]  纯文本查看  复制代码
?
1
2
public class Update_02 : MonoBehaviour {
}



===========================================分割线==============
1.减小固定增量时间
将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您能够经过Edit->Project Settings->Time来改变这个值。这样作下降了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。若是您使用了较低的固定增量时间,而且在主角身上使用了刚体部件,那么您能够启用插值办法来平滑刚体组件。


2.减小GetComponent的调用使用 GetComponent或内置组件访问器会产生明显的开销。您能够经过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。
例如,若是您使用以下的代码:
[JavaScript]  纯文本查看  复制代码
?
1
2
3
4
function Update ()
{
transform.Translate(0, 1, 0);
}


经过下面的更改您将得到更好的性能:
[JavaScript]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
var myTransform : Transform;
 
function Awake () {
 
myTransform = transform;
 
}
 
function Update () {
 
myTransform.Translate(0, 1, 0);
 
}


3.避免分配内存
您应该避免分配新对象,除非你真的须要,由于他们再也不在使用时,会增长垃圾回收系统的开销。
您能够常常重复使用数组和其余对象,而不是分配新的数组或对象。这样作好处则是尽可能减小垃圾的回收工做。
同时,在某些可能的状况下,您也可使用结构(struct)来代替类(class)。
这是由于,结构变量主要存放在栈区而非堆区。由于栈的分配较快,而且不调用垃圾回收操做,因此当结构变量比较小时能够提高程序的运行性能。
可是当结构体较大时,虽然它仍可避免分配/回收的开销,而它因为"传值"操做也会致使单独的开销,实际上它可能比等效对象类的效率还要低。

4.最小化GUI
使用GUILayout 函数能够很方便地将GUI元素进行自动布局。然而,这种自动化天然也附带着必定的处理开销。
您能够经过手动的GUI功能布局来避免这种开销。
此外,您也能够设置一个脚本的useGUILayout变量为 false来彻底禁用GUI布局:
[JavaScript]  纯文本查看  复制代码
?
1
2
3
function Awake () {
useGUILayout = false ;
}


5.使用iOS脚本调用优化功能
UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。
从Mono的脚本调用 C/C++函数也存在着必定的性能开销。
您可使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。
此设置的选项有:
Slow and Safe – Mono内部默认的处理异常的调用
Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。
不过,它并不支持异常,所以应谨慎使用。
它对于不须要显式地处理异常(也不须要对异常进行处理)的应用程序来讲,是一个理想的候选项。

6.优化垃圾回收
如上文所述,您应该尽可能避免分配操做。
可是,考虑到它们是不能彻底杜绝的,因此咱们提供两种方法来让您尽可能减小它们在游戏运行时的使用:
若是堆比较小,则进行快速而频繁的垃圾回收

这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。
像这样的游戏一般会频繁地分配小块内存,但这些小块内存只是暂时地被使用。
若是在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,
垃圾回收操做将耗时大约 5毫秒。若是堆大小增长到1 MB时,该回收操做将耗时大约 7ms。
所以,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。
一般,这种作法会让回收操做执行的更加频繁(有些回收操做并非严格必须进行的),
但它们能够快速处理而且对游戏的影响很小:
[C#]  纯文本查看  复制代码
?
1
2
3
4
if (Time.frameCount % 30 == 0)
{
System.GC.Collect();
}


可是,您应该当心地使用这种技术,而且经过检查Profiler来确保这种操做确实能够下降您游戏的垃圾回收时间
若是堆比较大,则进行缓慢且不频繁的垃圾回收

这一策略适合于那些内存分配 (和回收)相对不频繁,而且能够在游戏停顿期间进行处理的游戏。
若是堆足够大,但尚未大到被系统关掉的话,这种方法是比较适用的。
可是,Mono运行时会尽量地避免堆的自动扩大。
所以,您须要经过在启动过程当中预分配一些空间来手动扩展堆(ie,你实例化一个纯粹影响内存管理器分配的"无用"对象):
[JavaScript]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
9
function Start()
{
var tmp = new System.Object[1024];
// make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
for ( var i : int = 0; i < 1024; i++)
tmp = new byte[1024];
// release reference
tmp = null ;
}

游戏中的暂停是用来对堆内存进行回收,而一个足够大的堆应该不会在游戏的暂停与暂停之间被彻底占满。因此,当这种游戏暂停发生时,您能够显式请求一次垃圾回收:
System.GC.Collect();
另外,您应该谨慎地使用这一策略并时刻关注Profiler的统计结果,而不是假定它已经达到了您想要的效果。



U3D开发中的一些多余组件问题:

在美术制做场景中,都会带上 MeshCollider, Animation, Animator 等组件,包括材质中的shader部分也会使用mobile以外的diffuse,因此有一句话:“若是相信美术,母猪都会上树”, 程序员们仍是本身动手来干吧:

[ MenuItem ( "Tools/删除场景没用的组件" ) ]
static public void Remove ( )
{
   //获取当前场景里的全部游戏对象
GameObject [ ] rootObjects = ( GameObject [ ] ) UnityEngine . Object . FindObjectsOfType ( typeof ( GameObject ) ) ;
//遍历游戏对象
foreach ( GameObject go in rootObjects )
{
   //若是发现Render的shader是Diffuse而且颜色是白色,那么将它的shader修改为Mobile/Diffuse
if ( go != null && go . transform . parent != null )
{
Renderer render = go . GetComponent < Renderer > ( ) ;
   if ( render != null && render . sharedMaterial != null && render . sharedMaterial . shader . name == "Diffuse" && render . sharedMaterial . color == Color . white )
{
   render . sharedMaterial . shader = Shader . Find ( "Mobile/Diffuse" ) ;
}
}
 
       //删除全部的MeshCollider
foreach ( MeshCollider collider in UnityEngine . Object . FindObjectsOfType ( typeof ( MeshCollider ) ) )
{
DestroyImmediate ( collider ) ;
}
 
//删除没有用的动画组件
foreach ( Animation animation in UnityEngine . Object . FindObjectsOfType ( typeof ( Animation ) ) )
{
if ( animation . clip == null )
DestroyImmediate ( animation ) ;
}
 
       //应该没有人用Animator吧? 避免美术弄错我都所有删除了。
foreach ( Animator animator in UnityEngine . Object . FindObjectsOfType ( typeof ( Animator ) ) )
{
DestroyImmediate ( animator ) ;
}
}
//保存
AssetDatabase . SaveAssets ( ) ;
}





有提出改进需求的童鞋请留言;

相关文章
相关标签/搜索