unity烘焙问题的小问题,这一句是不是多此一举

本文为《effective c#》的读书笔记,此书类似于大名鼎鼎的《effective c++》,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针对.net framework,而unity中的mono平台只支持.net framework 2.0,所以有很多原则在unity中并不适用,本文总结了其中在unity中也适用的一些原则。整理后,一共20多个原则仍然适用于unity,将分为两篇文章来记录。 1 使用属性,不使用可访问的数据成员 属性更加灵活,有了新的需求时便于修改。例如定义name属性,后来我们想到名字不应为空,那么只需要修改一处就可以,如果使用公有成员变量,则要在每处使用的地方修改。还可以使用所引器来访问。 public class MyClass{
private string[] namelist = new string[<span style="color: #];
private string
public string Username
get { return }
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Username can't be blank!");
username =
//简单索引器
public string this[int idx]
if (idx & namelist.Length && idx &= <span style="color: #)
return namelist[idx];
return string.E
if (idx &= <span style="color: # && idx & namelist.Length)
namelist[idx] =
public void UseIndexor()
this[<span style="color: #] = "first name";//效果等同于namelist[0]="first name"
C#建议总是使用property,但是在Unity中有很多局限性:
1.如果在monobehavior脚本类中使用property,那么在编辑器的inspector中无法显示出property。2.性能问题,.net4.0以上会将简单的property编译成内敛函数,因此性能影响很小,但是mono不支持,性能会稍差一些。建议:在类自己中避免使用property,直接使用成员变量;小型的私有类中避免使用;避免在简单的仅仅用来存储数据的类中使用;避免在性能很敏感的部分使用;在monobehavior脚本中,如果需要在编辑器中修改成员,也应该避免使用。除此以外的其他情况,可以使用property。
2 使用运行时常量readonly而不是编译时常量const
const性能有优势但是微乎其微,且只能应用于基本数据类型,readonly更加灵活,可以应用于所有类型。readonly在构造函数或者初始化器中初始化。在编译器一定要得到确定的数值时必须使用const,如attribute的参数和枚举的定义等。其他情况,除非对性能非常敏感,否则通常应该使用readonly。
3 使用as 或 is,不用强制类型转换
使用as后需要检查是否为null ,对于值类型不能使用as,应该使用is。总之,能用as用as,不能则用is判断后用强制类型转换。
public class Test
internal class MyType
internal class MyFactory
public static MyType GetObject()
return new MyType();
void asAndIs()
object o = MyFactory.GetObject();
MyType t = o as MyT
if (t != null)
//t is a MyType
// failed,throw exception
object o2 = MyFactory.GetObject();
//int i = o2//编译错误
//应该使用is
if (o2 is int)
int i2 = (int) o2;
// failed,throw exception
4 使用Conditional attribute而不是#if 条件编译
Conditional应用于方法,因此强制我们把条件代码拆分成不同的方法,这比使用#if结构更加清晰。使用#if条件编译,当不符合条件时,方法仍然被编译,和调用,只是方法内容为空,而使用Conditional,方法虽然也会被c#编译器编译,但是不会被jit编译器编译,也不会被调用,不会被加载到内存。多个特性默认为or运算,如果有其他需要则需自定义。例子如下:
public class UseConditional
[Conditional("DEBUG")]
void NewDebugFunc()
//do something
void MainFunc()
NewDebugFunc();
//do something
//多个特性默认为or运算,如果有其他需要则需自定义
[Conditional("DEBUG"), Conditional("REALESE")]
void OrConditionFunc()
//如果定义了DEBUG或者REALESE,会执行这个方法
//如果要多个条件执行and运算,需要自定义
#if (VAR1 && VAR2)
#define BOTH
[Conditional("BOTH")]
void AndConditionFunc()
//dosomething
5 等同性判断
4种方法判断等同性两种静态方法object.Equals(a,b),object.ReferenceEquals(a,b),加上Equals()实例方法和operator==()object.Equals(a,b)值相等,object.ReferenceEquals(a,b)引用相等(指向相同的实例),这两种静态方法永远不应该去重新定义对于引用类型来说,这两个方法一样,对于值类型则不同。对于值类型,我们应该总是覆写Equals()实例方法和operator==()对于引用类型,当我们认为相等的含义并不是对象引用相等时,应该覆写Equals()实例方法,并实现IEquatable&T&接口(为了可以在泛型集合中使用)
注意,覆写Equals()实例方法的同时也要覆写GetHashCode()方法
//引用类型实现值相等性
class TwoDPoint : IEquatable&TwoDPoint&
public int X { get; set; }
public int Y { get; set; }
public TwoDPoint(int x, int y)
public override bool Equals(object obj)
return this.Equals(obj as TwoDPoint);
public bool Equals(TwoDPoint other)
if (object.ReferenceEquals(other, null))
return false;
if (object.ReferenceEquals(this, other))
return true;
if (this.GetType() != other.GetType())
return false;
return (X == other.X) && (Y == other.Y);
public override int GetHashCode()
return X*<span style="color: #x + Y;
public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
if (object.ReferenceEquals(lhs, null))
if (object.ReferenceEquals(rhs, null))
return true; //null==null = true
return false;
return lhs.Equals(rhs);
public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
return !(lhs == rhs);
//值类型实现相等性,默认使用反射方法验证相等性,性能差,所以总是应该自己实现
struct TwoDPointStruct : IEquatable&TwoDPointStruct&
public int X { get; set; }
public int Y { get; set; }
public TwoDPointStruct(int x, int y) : this()
public override bool Equals(object obj)
if (obj is TwoDPointStruct)
return this.Equals((TwoDPointStruct) obj);
return false;
public bool Equals(TwoDPointStruct other)
return (X == other.X) && (Y == other.Y);
public override int GetHashCode()
return X ^ Y;
public static bool operator ==(TwoDPointStruct lhs, TwoDPointStruct rhs)
return lhs.Equals(rhs);
public static bool operator !=(TwoDPointStruct lhs, TwoDPointStruct rhs)
return !(lhs == rhs);
6 GetHashCode的陷阱
GetHashCode()仅在一个地方用到,为基于散列的集合定义键的散列值时,如HashSet&T&,Dictionary&K,V&容器等。如果我们定义的类型,不会在容器中作为键使用,那么默认的GetHashCode()方法通常没什么问题,虽然对于引用类型来说性能不高,对于值类型来说(值类型应该是不可变的)也可以正常工作。对于我们创建的大多数类型来说,最好完全避免实现GetHashCode()对于值类型,默认的方法会基于第一个字段的GetHashCode()方法,如果这个字段是可变的,则会破坏整体的GetHashCode()方法。如果要自己实现,通常的做法的所有字段调用GetHashCode再对结果进行^ 异或运算(如果是字段是可变量则排除)。
7 理解短小方法的优势
JIT编译器在运行时把C#编译器生成的IL代码翻译成机器码,这个过程不是在启动一开始就进行的,而是按照函数的粒度来进行,没有被调用的方法不会被JIT编译因此把逻辑分散在小函数中会比把所有逻辑放在大型函数中更好如下:第一次调用LargeFunc时,两个分支都将被jit编译
public void LargeFunc(bool flag)
//do something
//do other things
//如下函数则只会编译一个分支。另外分支用到时才编译
public void LargeFuncWithShortFunc(bool flag)
DoSomeThing();
DoOtherThings();
private void DoOtherThings()
//do something
private void DoSomeThing()
//do other things
函数简单短小,也让jit更好的决定哪些变量放到寄存器上,以及哪些函数编译为内联函数。总之,使用简单短小的函数,有助于jit进行更好的优化。
8 推荐使用成员初始化器,而不是赋值语句
随着时间的推移,通常来说构造函数越来愈多,预防这种情况最好的方法就是在变量声明时就进行初始化,而不是在每个构造函数中进行。初始化器在构造函数前执行,应该充分利用初始化器的语法&&& private List&string& labels = new List&string&(); //声明同时初始化
有如下三种情况,应该避免使用初始化器1需要初始化为0或者null的情况,系统默认的初始化工作会在执行所有代码前,把一切设置为0或者null,而且是很底层性能很高的操作自己在设置一次多此一举,下面第二种方法实际上调用了initobj这条IL指令,导致了变量的装箱拆箱,造成了额外的时间消耗。&&& MyValueType myValue1; //默认0&&& MyValueType myValue2 = new MyValueType(); //也是0
2当需要对一个变量进行不同的初始化方法时,不应使用初始化器&&& public P8Init()&&& {&&& }
这个构造函数使用不同的方法初始化labels,相当于构造函数之前初始化一次,这里又初始化一次,无谓的消耗应该避免。&&& public P8Init(int size)&&& {&&&&&&& labels = new List&string&(size);&&& }
3最后一种情况是,初始化器无法进行异常处理,当需要进行异常处理时,把初始化工作放到构造函数内。
总上,初始化器是保证成员变量都被初始化的最简单方便的方法,若是所有的构造函数都要将某个成员初始化为同一个值,那么就应该使用初始化器。
9 正确的初始化静态成员变量
使用静态初始化器和静态构造函数来初始化静态成员变量静态初始化器常用于单例模式&&& private static readonly MyClass instance = new MyClass();
&&& private MyClass ()&&& {&&& }
&&& public static MyClass Instance&&& {&&&&&&& get { }&&& }
如果需要更复杂的初始化,或者异常处理,可以使用静态构造函数,静态构造函数在所有实例成员变量初始化器以及实例构造函数前调用。
private static readonly MyC
&&& private MyClass ()&&& {&&& }
&&& public static MyClass Instance&&& {&&&&&&& get { }&&& }
&&& static MyClass ()&&& {&&&&&&& try&&&&&&& {&&&&&&&&&&& instance = new MyClass ();&&&&&&& }&&&&&&& catch (Exception)&&&&&&& {&&&&&&&&&&& //handle exception&&&&&&&&&&&&&&&&&& }&&& }
10 尽量减少重复的初始化逻辑
把多个构造函数中的重复逻辑提取到一个公共构造函数中,其他构造函数可以通过构造函数初始化器调用公共构造函数如下前两个构造函数都调用了第三个构造函数。
private List&object&
private string
public MyClass():this(<span style="color: #,string.Empty)
public MyClass(int initCount) : this(initCount, string.Empty)
public MyClass(int initCount, string name)
data = initCount & <span style="color: # ? new List&object&(initCount) : new List&object&();
this.name =
11 避免创建非必要的对象
创建对象会在堆上分配内存,带来GC的工作量,有一些规则可以让你尽量降低GC的工作量,关于Unity中的gc优化,请参考
未完待续……
阅读(...) 评论()unity3d(59)
原博客说的很详细了,补充几点 &原博客链接-----&http://blog.csdn.net/qq_/article/details/
1、微信开发平台填的Bundle ID要和unity3d发布设置里的Bundle
ID对应得上。而且区分大小写,刚就因为大小写错误而分享不成功。
2、发布应用记得用应用前面发布(也就是keystore)&#20284;乎每次发布都要输入下密码否则发布后就没法分享了。
3、微信申请应用appkey申请的时候信息没法更改,但是申请过后都可以更改了,曾经因为应用签名填错而重新申请了个真是多此一举。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:111731次
积分:1816
积分:1816
排名:千里之外
原创:66篇
转载:11篇
评论:67条
(1)(1)(1)(1)(1)(1)(4)(2)(2)(6)(4)(7)(6)(5)(2)(2)(2)(1)(5)(4)(2)(1)(3)(5)(2)(6)性能优化(4)
Hello,我是。
以下文章整理的不对。还请见谅。
看看优化需要从哪里着手?
印象里遇到的童靴,提Unity3D项目优化则必提DrawCall,这自然没错,但也有很不好影响。因为这会给人一个错误的认识:所谓的优化就是把DrawCall弄的比较低就对了。
对优化有这种第一印象的人不在少数,drawcall的确是一个很重要的指标,但绝非全部。为了让各位和匹夫能达成尽可能多的共识,匹夫首先介绍一下本文可能会涉及到的几个概念,之后会提出优化所涉及的三大方面:
drawcall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。所以,是谁去调用这些接口呢?CPU。fragment是啥?经常有人说vf啥的,vertex我们都知道是顶点,那fragment是啥呢?说它之前需要先说一下像素,像素各位应该都知道吧?像素是构成数码影像的基本单元呀。那fragment呢?是有可能成为像素的东西。啥叫有可能?就是最终会不会被画出来不一定,是潜在的像素。这会涉及到谁呢?GPU。batching是啥?都知道批处理是干嘛的吧?没错,将批处理之前需要很多次调用(drawcall)的物体合并,之后只需要调用一次底层图形程序的接口就行。听上去这简直就是优化的终极方案啊!但是,理想是美好的,世界是残酷的,一些不足之后我们再细聊。内存的分配:记住,除了Unity3D自己的内存损耗。我们可是还带着Mono呢啊,还有托管的那一套东西呢。更别说你一激动,又引入了自己的几个dll。这些都是内存开销上需要考虑到的。
好啦,文中的几个概念提前讲清楚了,其实各位也能看的出来匹夫接下来要说的匹夫关注的优化时需要注意的方面:
CPU方面GPU方面内存方面
所以,这篇文章也会按照CPU—-&GPU—-&内存的顺序进行。
CPU的方面的优化:
上文中说了,drawcall影响的是CPU的效率,而且也是最知名的一个优化点。但是除了drawcall之外,还有哪些因素也会影响到CPU的效率呢?让我们一一列出暂时能想得到的:
DrawCalls物理组件(Physics)GC(什么?GC不是处理内存问题的嘛?匹夫你不要骗我啊!不过,匹夫也要提醒一句,GC是用来处理内存的,但是是谁使用GC去处理内存的呢?)当然,还有代码质量
DrawCalls:
前面说过了,DrawCall是CPU调用底层图形接口。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的。所以对DrawCall的优化,主要就是为了尽量解放CPU在调用图形接口上的开销。所以针对drawcall我们主要的思路就是每个物体尽量减少渲染次数,多个物体最好一起渲染。所以,按照这个思路就有了以下几个方案:
使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。通过把纹理打包成图集来尽量减少材质的使用。尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。
Draw Call Batching
首先我们要先理解为何2个没有使用相同材质的物体即使使用批处理,也无法实现Draw Call数量的下降和性能上的提升。
因为被“批处理”的2个物体的网&#26684;模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因而保证材质相同,是为了保证被渲染的纹理相同。
因此,为了将2个纹理不同的材质合二为一,我们就需要进行上面列出的第二步,将纹理打包成图集。具体到合二为一这种情况,就是将2个纹理合成一个纹理。这样我们就可以只用一个材质来代替之前的2个材质了。
而Draw Call Batching本身,也还会细分为2种。
Static Batching 静态批处理
看名字,猜使用的情景。
静态?那就是不动的咯。还有呢?额,听上去状态也不会改变,没有“生命”,比如山山石石,楼房校舍啥的。那和什么比较类&#20284;呢?嗯,聪明的各位一定觉得和场景的属性很像吧!所以我们的场景&#20284;乎就可以采用这种方式来减少draw call了。
那么写个定义:只要这些物体不移动,并且拥有相同的材质,静态批处理就允许引擎对任意大小的几何物体进行批处理操作来降低描绘调用。
那要如何使用静态批来减少Draw Call呢?你只需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器(Inspector)中将Static复选框打勾即可,如下图所示:
至于效果如何呢?
举个例子:新建4个物体,分别是Cube,Sphere, Capsule, Cylinder,它们有不同的网&#26684;模型,但是也有相同的材质(Default-Diffuse)。
首先,我们不指定它们是static的。Draw Call的次数是4次,如图:
我们现在将它们4个物体都设为static,在来运行一下:
如图,Draw Call的次数变成了1,而Saved by batching的次数变成了3。
静态批处理的好处很多,其中之一就是与下面要说的动态批处理相比,约束要少很多。所以一般推荐的是draw call的静态批处理来减少draw call的次数。那么接下来,我们就继续聊聊draw call的动态批处理。
Dynamic Batching 动态批处理
有阴就有阳,有静就有动,所以聊完了静态批处理,肯定跟着就要说说动态批处理了。首先要明确一点,Unity3D的draw call动态批处理机制是引擎自动进行的,无需像静态批处理那样手动设置static。我们举一个动态实例化prefab的例子,如果动态物体共享相同的材质,则引擎会自动对draw call优化,也就是使用批处理。首先,我们将一个cube做成prefab,然后再实例化500次,看看draw call的数量。
for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameOb
draw call的数量:
可以看到draw call的数量为1,而 saved by batching的数量是499。而这个过程中,我们除了实例化创建物体之外什么都没做。不错,unity3d引擎为我们自动处理了这种情况。
但是有很多童靴也遇到这种情况,就是我也是从prefab实例化创建的物体,为何我的draw call依然很高呢?这就是匹夫上文说的,draw call的动态批处理存在着很多约束。下面匹夫就演示一下,针对cube这样一个简单的物体的创建,如果稍有不慎就会造成draw call飞涨的情况吧。
我们同样是创建500个物体,不同的是其中的100个物体,每个物体的大小都不同,也就是Scale不同。
for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
if(i / 100 == 0)
cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
&lt;500;i&#43;&#43;)
&&&&GameObjectcube;
GameObject.Instantiate(prefab)as
GameObject;
&&&&&&&&cube.transform.localScale=
newVector3(2&#43;
draw call的数量:
我们看到draw call的数量上升到了101次,而saved by batching的数量也下降到了399。各位看官可以看到,仅仅是一个简单的cube的创建,如果scale不同,竟然也不会去做批处理优化。这仅仅是动态批处理机制的一种约束,那我们总结一下动态批处理的约束,各位也许也能从中找到为何动态批处理在自己的项目中不起作用的原因:
批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网&#26684;物体。如果你的着色器使用顶点位置,法线和UV&#20540;三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理。统一缩放的物体不会与非统一缩放的物体进行批处理。使用缩放尺度(1,1,1) 和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1) 和(1,3,1)的两个物体将可以进行批处理。使用不同材质的实例化物体(instance)将会导致批处理失败。拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。预设体的实例会自动地使用相同的网&#26684;模型和材质。
所以,尽量使用静态的批处理。
曾几何时,匹夫在做一个策略类游戏的时候需要在单元&#26684;上排兵布阵,而要侦测到哪个兵站在哪个&#26684;子匹夫选择使用了射线,由于士兵单位很多,而且为了精确每一帧都会执行检测,那时候CPU的负担叫一个惨不忍睹。后来匹夫果断放弃了这种做法,并且对物理组件产生了心理的阴影。
这里匹夫只提2点匹夫感觉比较重要的优化措施:
1.设置一个合适的Fixed Timestep。设置的位置如图:
那何谓“合适”呢?首先我们要搞明白Fixed Timestep和物理组件的关系。物理组件,或者说游戏中模拟各种物理效果的组件,最重要的是什么呢?计算啊。对,需要通过计算才能将真实的物理效果展现在虚拟的游戏中。那么Fixed Timestep这货就是和物理计算有关的啦。所以,若计算的频率太高,自然会影响到CPU的开销。同时,若计算频率达不到游戏设计时的要求,有会影响到功能的实现,所以如何抉择需要各位具体分析,选择一个合适的&#20540;。
2.就是不要使用网&#26684;碰撞器(mesh collider):为啥?因为实在是太复杂了。网&#26684;碰撞器利用一个网&#26684;资源并在其上构建碰撞器。对于复杂网状模型上的碰撞检测,它要比应用原型碰撞器精确的多。标记为凸起的(Convex )的网&#26684;碰撞器才能够和其他网&#26684;碰撞器发生碰撞。各位上网搜一下mesh collider的图片,自然就会明白了。我们的手机游戏自然无需这种性价比不高的东西。
当然,从性能优化的角度考虑,物理组件能少用还是少用为好。
处理内存,却让CPU受伤的GC
在CPU的部分聊GC,感觉是不是怪怪的?其实小匹夫不这么觉得,虽然GC是用来处理内存的,但的确增加的是CPU的开销。因此它的确能达到释放内存的效果,但代价更加沉重,会加重CPU的负担,因此对于GC的优化目标就是尽量少的触发GC。
首先我们要明确所谓的GC是Mono运行时的机制,而非Unity3D游戏引擎的机制,所以GC也主要是针对Mono的对象来说的,而它管理的也是Mono的托管堆。 搞清楚这一点,你也就明白了GC不是用来处理引擎的assets(纹理啦,音效啦等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一起使用所谓的托管堆。
其次我们要搞清楚什么东西会被分配到托管堆上?不错咯,就是引用类型咯。比如类的实例,字符串,数组等等。而作为int,float,包括结构体struct其实都是&#20540;类型,它们会被分配在堆栈上而非堆上。所以我们关注的对象无外乎就是类实例,字符串,数组这些了。
那么GC什么时候会触发呢?两种情况:
首先当然是我们的堆的内存不足时,会自动调用GC。其次呢,作为编程人员,我们自己也可以手动的调用GC。
所以为了达到优化CPU的目的,我们就不能频繁的触发GC。而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,所以GC的优化说白了也就是代码的优化。那么匹夫觉得有以下几点是需要注意的:
字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。使用“池”,以实现空间的重复利用。最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。
代码?脚本?
聊到代码这个话题,也许有人会觉得匹夫多此一举。因为代码质量因人而异,很难像上面提到的几点,有一个明确的评判标准。也是,公写公有理,婆写婆有理。但是匹夫这里要提到的所谓代码质量是基于一个前提的:Unity3D是用C&#43;&#43;写的,而我们的代码是用C#作为脚本来写的,那么问题就来了~脚本和底层的交互开销是否需要考虑呢?也就是说,我们用Unity3D写游戏的“游戏脚本语言”,也就是C#是由mono运行时托管的。而功能是底层引擎的C&#43;&#43;实现的,“游戏脚本”中的功能实现都离不开对底层代码的调用。那么这部分的开销,我们应该如何优化呢?
1.以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过方法GetComponent&Transform&()获取Transform组件, 通过MonoBehavor的transform属性去取,以及保留引用之后再去访问所需要的时间:
GetComponent = 619msMonobehaviour = 60msCachedMB = 8msManual Cache = 3ms
2.如上所述,最好不要频繁使用GetComponent,尤其是在循环中。
3.善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销。
4.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);
5.对于方法的参数的优化:善于使用ref关键字。&#20540;类型的参数,是通过将实参的&#20540;复制到形参,来实现按&#20540;传递到方法,也就是我们通常说的按&#20540;传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的&#20540;类型,如果直接复制一份新的,反而不如将&#20540;类型的引用传递给方法作为参数。
好啦,CPU的部分匹夫觉得到此就介绍的差不多了。下面就简单聊聊其实匹夫并不是十分熟悉的部分,GPU的优化。
GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:
填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。像素的复杂度,比如动态阴影,光照,复杂的shader等等几何体的复杂度(顶点数量)当然还有GPU的显存带宽
那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。
减少顶点数量,简化计算复杂度。压缩图片,以适应显存带宽。
减少绘制的数目
那么第一个方面的优化也就是减少顶点数量,简化复杂度,具体的举措就总结如下了:
保持材质的数目尽可能少。这使得Unity更容易进行批处理。使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial&来代替Renderer.material&。使用光照纹理(lightmap)而非实时灯光。使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。遮挡剔除(Occlusion culling)使用mobile版的shader。因为简单。
优化显存带宽
第二个方向呢?压缩图片,减小显存带宽的压力。
OpenGL ES 2.0使用ETC1&#26684;式压缩等等,在打包设置那里都有。使用mipmap。
这里匹夫要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。
上面是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图
是不是很一目了然呢?Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。
内存的优化
既然要聊Unity3D运行时候的内存优化,那我们自然首先要知道Unity3D游戏引擎是如何分配内存的。大概可以分成三大部分:
Unity3D内部的内存Mono的托管内存若干我们自己引入的DLL或者第三方DLL所需要的内存。
第3类不是我们关注的重点,所以接下来我们会分别来看一下Unity3D内部内存和Mono托管内存,最后还将分析一个官网上Assetbundle的案例来说明内存的管理。
Unity3D内部内存
Unity3D的内部内存都会存放一些什么呢?各位想一想,除了用代码来驱动逻辑,一个游戏还需要什么呢?对,各种资源。所以简单总结一下Unity3D内部内存存放的东西吧:
资源:纹理、网&#26684;、音频等等GameObject和各种组件。引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等
Mono托管内存
因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的运行时内存的分配了:
&#20540;类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。
而Mono托管堆中的那些封装的对象,除了在在Mono托管堆上分配封装类实例化之后所需要的内存之外,还会牵扯到其背后对应的游戏引擎内部控件在Unity3D内部内存上的分配。
举一个例子:
一个在.cs脚本中声明的WWW类型的对象www,Mono会在Mono托管堆上为www分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。
一个WWW实例背后的资源:
压缩的文件解压缩所需的缓存解压缩之后的文件
那么下面就举一个AssetBundle的例子:
Assetbundle的内存处理
以下载Assetbundle为例子,聊一下内存的分配。匹夫从官网的手册上找到了一个使用Assetbundle的情景如下:
IEnumerator DownloadAndCache (){
// Wait for the Caching system to be ready
while (!Caching.ready)
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
//WWW是第1部分
if (www.error != null)
throw new Exception(&WWW download had an error:& + www.error);
AssetBundle bundle = www.assetB//AssetBundle是第2部分
if (AssetName == &&)
Instantiate(bundle.mainAsset);//实例化是第3部分
Instantiate(bundle.Load(AssetName));
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload(false);
} // memory is freed from the web stream (www.Dispose() gets called implicitly)
IEnumeratorDownloadAndCache
&&&&&&&&// Wait for the Caching system to be ready
&&&&&&&&while(!Caching.ready)
&&&&&&&&&&&&yield returnnull;
&&&&&&&&// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
&&&&&&&&using(WWWwww
=WWW.LoadFromCacheOrDownload(BundleURL,version)){
&&&&&&&&&&&&yield returnwww;//WWW是第1部分
&&&&&&&&&&&&if(www.error!=
&&&&&&&&&&&&&&&&thrownew
Exception(&quot;WWWdownload
had anerror:&quot;&#43;
www.error);
&&&&&&&&&&&&AssetBundlebundle
=www.assetBundle;//AssetBundle是第2部分
&&&&&&&&&&&&if(AssetName==
&quot;&quot;)
&&&&&&&&&&&&&&&&Instantiate(bundle.mainAsset);//实例化是第3部分
&&&&&&&&&&&&else
&&&&&&&&&&&&&&&&Instantiate(bundle.Load(AssetName));
&&&&&&&&&&&&&&&&&&&&// Unload the AssetBundles compressed contents to conserve memory
&&&&&&&&&&&&&&&&&&&&bundle.Unload(false);
&&&&&&&&}// memory is freed from the web stream (www.Dispose() gets called implicitly)
内存分配的三个部分匹夫已经在代码中标识了出来:
Web Stream:包括了压缩的文件,解压所需的缓存,以及解压后的文件。AssetBundle:Web Stream中的文件的映射,或者说引用。实例化之后的对象:就是引擎的各种资源文件了,会在内存中创建出来。
那就分别解析一下:
WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
=WWW.LoadFromCacheOrDownload(BundleURL,version)
将压缩的文件读入内存中创建解压所需的缓存将文件解压,解压后的文件进入内存关闭掉为解压创建的缓存
AssetBundle bundle = www.assetB
AssetBundlebundle
=www.assetBundle;
AssetBundle此时相当于一个桥梁,从Web Stream解压后的文件到最后实例化创建的对象之间的桥梁。所以AssetBundle实质上是Web Stream解压后的文件中各个对象的映射。而非真实的对象。实际的资源还存在Web Stream中,所以此时要保留Web Stream。
Instantiate(bundle.mainAsset);
Instantiate(bundle.mainAsset);
通过AssetBundle获取资源,实例化对象
最后各位可能看到了官网中的这个例子使用了:
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
using(WWWwww
=WWW.LoadFromCacheOrDownload(BundleURL,version)){
这种using的用法。这种用法其实就是为了在使用完Web Stream之后,将内存释放掉的。因为WWW也继承了idispose的接口,所以可以使用using的这种用法。其实相当于最后执行了:
//删除Web Stream
www.Dispose();
//删除Web Stream
www.Dispose();
OK,Web Stream被删除掉了。那还有谁呢?对Assetbundle。那么使用
//删除AssetBundle
bundle.Unload(false);
//删除AssetBundle
bundle.Unload(false);
ok,写到这里就先打住啦。写的有点超了。有点赶也有点临时,日后在补充编辑。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:35418次
排名:千里之外
原创:22篇
转载:24篇
评论:17条
(3)(7)(6)(8)(8)(5)(3)(2)(10)

我要回帖

更多关于 unity 发热问题 的文章

 

随机推荐