2017-05-23 14:16:14 m0_37283423 阅读数 1320

前段时间总结了下GPU方面的优化,现在来谈谈优化部分Memory,其主要会包括以下方面,之后会不断完善

Unity3D内部的内存

Mono的托管内存

引入的DLL或者第三方DLL所需要的内存

     . . . . . 

进入主题

       上面只是给出了Memory一些方重要的面的优化,之后还会不断对其进行补充。首先我们要讲的是Unity3d的内部内存的优化!
        其主要包括以下方面:
        1.Texture
        2.Mesh
        3.Mode
        4.Audio
        5.Particle
        6.Physics
        7.NGUI
       
        Texture:
         首先当你导进资源时不需要设置压缩模式,把那个勾去掉就OK,做法如图所示:
       
      然后你也可以去掉那个生成MipMaps,虽然它对GPU的显存带宽有所优化,但是会约增加33%的内存开销,一般来说2D游戏的话,所有都不需要勾上,具有3D性质的贴图,我们都需要勾选上那个生成MipMaps具体图和选择看自己的游戏需求!
      最后根据不同平台来选择不同压缩格式的纹理!笔者在这里就介绍Phone端,PC端以及Web,这里主要关注所占内存的大小!
      对于Android选择ETC压缩格式的纹理更好,好在可以节省内存和读取速度快,但是会有所降低图像质量。
       android设备就没有那么好的运气了。由于硬件平台不统一,每个厂商的GPU可能使用不同的纹理压缩格式。所以还是老老实实用PNG比较好。大部分的                Android设备都支持ETC1格式,它也是受OpenGL ES 2.0标准支持的。ETC1不支持Alpha通道,但可以采用一些方法来处理:ETC 纹理压缩和 Alpha 通道          处理 « Mali 开发人员中心 。ETC2支持Alpha通道,但需要 OpenGL ES 3.0标准 支持。但由于硬件平台不统一,每个厂商的GPU可能使用不同的纹理压缩         格式,极少数的GPU甚至对ETC格式支持有问题。所以在Android设备上,目前可以采用ETC1或者PNG/JPEG格式。
       对于IOS选择使用 PVRTC压缩格式的 纹理可以节省大量内存和读取速度快,但是会有所降低图像的质量。
        pvr是iOS设备的图形芯片 PowerVR 图形 支持的专用压缩纹理格式。
      对于PC选择使用DXT压缩格式的纹理可以节省大量内存和读取速度快,但是会有所降低图像的质量。
      对于Web端跟上面一样!
      具体详情可以看下Unity圣典,连接地址:http://game.ceeger.com/Manual/Textures.html.
      

       Mesh:
      这里就讲讲合并网格和压缩网格吧,下面给出代码:
    网格合并
[csharp] view plain copy
  1. <span style="font-family:Comic Sans MS;font-size:12px;color:#660000;">using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class CombineMeshes : MonoBehaviour  
  5. {  
  6.     void Start()  
  7.     {  
  8.         MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();  
  9.         CombineInstance[] combine = new CombineInstance[meshFilters.Length];  
  10.         int i = 0;  
  11.         while (i < meshFilters.Length)  
  12.         {  
  13.             combine[i].mesh = meshFilters[i].sharedMesh;  
  14.             combine[i].transform = meshFilters[i].transform.localToWorldMatrix;  
  15.             meshFilters[i].gameObject.SetActive(false);  
  16.             i++;  
  17.         }  
  18.   
  19.         transform.GetComponent<MeshFilter>().mesh = new Mesh();  
  20.         transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine);  
  21.         transform.gameObject.SetActive(true);  
  22.     }  
  23. } </span>  
[csharp] view plain copy
  1. <span style="font-family:Comic Sans MS;font-size:12px;color:#660000;">using UnityEngine;  
  2. using System.Collections;  
  3. using UnityEditor;  
  4.   
  5. public class EditorTools : MonoBehaviour  
  6. {  
  7.   
  8.     [MenuItem("Tools/Save Combine Mesh")]  
  9.     public static void SaveMesh()  
  10.     {  
  11.         Mesh m = Selection.activeGameObject.GetComponent<MeshFilter>().sharedMesh;  
  12.         AssetDatabase.CreateAsset(m, "Assets/tmp/cmbMesh.asset");  
  13.         AssetDatabase.SaveAssets();  
  14.     }  
  15. }  
  16. </span>  
      压缩网格:
      网格和导入的动画剪辑可以被压缩因此它们占去少的空间在你的游戏文件里。压缩可以开启在网格导入设置里,网格和动画压缩使用量化,这意味着它占用少的空间但压缩可能引入一些不准确度。得看你的模型实现什么样的压缩级别是可以接受的。请注意,网格压缩只处理小型数据文件,并不在运行时使用较少的内存。动画关键帧还原生成较小的数据文件和在运行时使用较少的内存,通常你应该总是使关键帧减少。如图所示:
 

网格和动画压缩使用量化,这意味着它占用少的空间但压缩可能引入一些不准确度。为

你的模型实验什么样的压

缩级别是可以接受的。

网格和动画压缩使用量化,这意味着它占用少的空间但压缩可能引入一些不准确度。为

你的模型实验什么样的压

缩级别是可以接受的。


    Mode:
    1.合并使用同贴图的材质球,合并使用相同材质球的Mesh;
  2、角色的贴图和材质球只要一个,若必须多个则将模型离分离为多个部分;
  3、当使用多角色时,将动画单独分离出来;
  4、使用层距离来控制模型的显示距离;
  5、阴影其实包含两方面阴暗和影子,建议使用实时影子时把阴暗效果烘焙出来,不要使用灯光来调节光线阴暗。
  6、少用像素灯和使用像素灯的Shader;
  7、如果硬阴影可以解决问题就不要用软阴影,并且使用不影响效果的低分辨率阴影;
  8、实时阴影很耗性能,尽量减小产生阴影的距离;
  9、允许的话在大场景中使用线性雾,这样可以使远距离对象或阴影不易察觉,因此可以通过减小相机和阴影距离来提高性能;
  10、使用圆滑组来尽量减少模型的面数;
  11、项目中如果没有灯光或对象在移动那么就不要使用实时灯光;
  12、水面、镜子等实时反射/折射的效果单独放在Water图层中,并且根据其实时反射/折射的范围来调整;
  13、碰撞对效率的影响很小,但碰撞还是建议使用Box、Sphere碰撞体;
  14、建材质球时尽量考虑使用Substance;
  15、尽量将所有的实时反射/折射(如水面、镜子、地板等等)都集合成一个面;
  16、假反射/折射没有必要使用过大分辨率,一般64*64就可以,不建议超过256*256;
  17、需要更改的材质球,建议实例化一个,而不是使用公共的材质球;
  18、将不须射线或碰撞事件的对象置于IgnoreRaycast图层;
  19、将水面或类似效果置于Water图层
  20、将透明通道的对象置于TransparentFX图层;
  21、养成良好的标签(Tags)、层次(Hieratchy)和图层(Layer)的条理化习惯,将不同的对象置于不同的标签或图层,三者有效的结合将很方便的按名称、类别和属性来查找;
  22、通过Stats和Profile查看对效率影响最大的方面或对象,或者使用禁用部分模型的方式查看问题到底在哪儿;
  23、使用遮挡剔除(Occlusion Culling)处理大场景,一种较原生的类LOD技术,并且能够“分割”作为整体的一个模型。
      24、骨骼系统不要使用太多;

2017-09-11 19:21:53 pursue16 阅读数 235

比如在跑酷游戏当中,人跑到前面去了,后面的人物就要销毁掉,这样就能够保证内存的稳定。所以要动态生成物体。所以得用代码来生成。


通过prefab的门、窗户、地面、墙,将这些搭建起一座房子,将房子保存为prefab。用代码生成多个房子。

void Start () {
		for (int i=0; i<10; i++) {
			GameObject house = (GameObject)GameObject.Instantiate (housePrefab);//这样就克隆了一个房子  用for循环就可以克隆很多个
			//要把prefab房子放到houseprefab里面去
			house.transform.position = new Vector3(-100+i*20,-2,100);
			house.transform.eulerAngles = new Vector3(0,i*10,0);
		}
	}


然后将代码放到main camera里面之后,把prefab物体放到上面,就形成了绑定了



2014-02-05 22:13:42 iboxdb 阅读数 19
[b]NoSQL数据库[/b] iBoxDB 支持 Java Android C# .NET Mono Unity3D Xamarin Nashorn Windows Phone

特性: CRUD, 索引, 复合索引, SQL-like查询语言, 事务管理, 并发管理, 内存管理, 数据库热复制, 文件及内存数据库支持, 非结构化数据支持, NoSQL ORM, 高性能, 零配置, 复制运行

[url=http://www.iboxdb.com]iBoxDB for Java Android C# .NET Nashorn Mono Unity3D Xamarin Windows Phone[/url]
2018-01-06 16:36:58 qq_33632356 阅读数 646

Unity3d笔试题大全
1. [C#语言基础]请简述拆箱和装箱。
答:
装箱操作:
值类型隐式转换为object类型或由此值类型实现的任何接口类型的过程。
1.在堆中开辟内存空间。
2.将值类型的数据复制到堆中。
3.返回堆中新分配对象的地址。
拆箱操作:
object类型显示转换为值类型或从接口类型到实现该接口值类型的过程。
1.判断给定类型是否是装箱时的类型。
2.返回已装箱实例中属于原值类型字段的地址。
2. [.NET(C#)] attribute,property,markup,tap翻译
答:attribute翻译成特性,用来标识类,方法 。
property翻译为属性,性质用于存取类的字段 。
markup翻译成标记。
tag翻译成标签。
3. [.NET(C#)] 程序集的重要特性
答:程序集的一个重要特性是它们包含的元数据描述了对应代码中定义的类型和方法。
4. [.NET(C#)] ref与out关键字
答:ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。
5. [.NET(C#)] new 关键字用法。
答:1) new 运算符 :用于创建对象和调用构造函数。
2) new 修饰符 :用于向基类成员隐藏继承成员。
3) new 约束 :用于在泛型声明中约束可能用作类型参数的参数的类型。
6. [.NET(C#)] C#中,string str = null 与 string str = “”,说明区别。
答:string str = “” 初始化对象分配空间。
string str = null 表示一个空引用,没有占用空间。//实际情况是,string为null时依然为”“,但该题常考。
7. [.NET(C#)]什么是序列化?
答:序列化是将对象状态转换为可保持或传输的格式的过程。 与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
8. [.NET(C#)] sealed 修饰符有什么特点
答:
1) sealed 修饰符可以应用于类、实例方法和属性。密封类不能被继承。密封方法会重写基类中的方法,但其本身不能在任何派生类中进一步重写。当应用于方法或属性时,sealed 修饰符必须始终与 override一起使用。
2) 将密封类用作基类或将 abstract 修饰符与密封类一起使用是错误的。
3) 结构是隐式密封的;因此它们不能被继承。
9. [.NET(C#)] 详述.NET里class和struct的异同。
答:
相同点:
1) 语法类似。
不同点:
1) class是引用类型,继承自System.Object类; struct是值类型,继承自System.ValueType类,因此不具多态性。但是注意,System.ValueType是个引用类型。
2) 从职能观点来看,class表现为行为; 而struct常用于存储数据。
3) class支持继承,可以继承自类和接口; 而struct没有继承性,struct不能从class继承,也不能作为class的基类,但struct支持接口继承。
4) 实例化时,class要使用new关键字; 而struct可以不使用new关键字,struct在声明时就进行了初始化过程,所有的成员变量均默认为0或null。
10. [.NET(C#)]如何选择结构还是类。
答:1) 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
2) 结构表示如点、矩形和颜色这样的轻量对象。例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
3) 在表现抽象和多级别的对象层次时,类是最好的选择。
4) 大多数情况下该类型只是一些数据时,结构时最佳的选择。
11. [.NET(C#)]抽象类(abstract class)和接口(interface)的区别。
答:
抽象类:
1) 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法。
2) 抽象类不能被实例化。
3) 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类。
4) 具体派生类必须覆盖基类的抽象方法。
5) 抽象派生类可以覆盖基类的抽象方法,也可以不覆盖。如果不覆盖,则其具体派生类必须覆盖它们。
接口:
1) 接口不能被实例化。
2) 接口只能包含方法声明。
3) 接口的成员包括方法、属性、索引器、事件。
4) 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员。
5) 接口中的所有成员默认为public,因此接口中不能有private修饰符。
6) 派生类必须实现接口的所有成员。
7) 一个类可以直接实现多个接口,接口之间用逗号隔开。
8) 一个接口可以有多个父接口,实现该接口的类必须实现所有父接口中的所有成员。
抽象类和接口的异同:
相同点:
1) 都可以被继承。
2) 都不能被实例化。
3) 都可以包含方法声明。
4) 派生类必须实现未实现的方法。
区 别:
1) 抽象基类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。
2) 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范。微软的自定义接口总是后带able字段,证明其是表述一类“我能做。。。”。
3) 接口可以被多重实现,抽象类只能被单一继承。
4) 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中。
5) 抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性。
6) 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法。
7) 接口可以用于支持回调,而继承并不具备这个特点。
8) 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的。
9) 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法。
12. [.NET(C#)]什么叫应用程序域?
答:1) 操作系统和运行库环境通常会在应用程序间提供某种形式的隔离。
2) 应用程序域为安全性、可靠性、版本控制以及卸载程序集提供了隔离边界。
3) 应用程序域可以理解为一种轻量级进程。起到安全的作用。占用资源小。
13. [.NET(C#)]什么是强类型?
答:为所有变量指定数据类型称为“强类型”。C#是强类型语言。
14. [.NET(C#)]托管代码。
答:使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,例如:跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析服务等。
15. [.NET(C#)]什么是CTS?
答:CTS:通用系统类型 Common Type System。所有.NET语言共享这一类型系统,实现它们之间无缝的互操作。该方案还提供了语言之间的继承性。
16. [.NET(C#)]什么是CLR?
答:CLR:公共语言运行库 Common Language Runtime。是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。
17. [.NET(C#)]什么是CLS?
答:CLS:公共语言规范 Common Language Specification。可以保证C#组件与其他语言组件间的互操作性。
18. [.NET(C#)]什么是委托?
答:1) 委托是一种引用方法的类型。
2) 委托类似于 C++ 函数指针,但它是类型安全的。
3) 委托允许将方法作为参数进行传递。
4) 委托可用于定义回调方法。
19. [.NET(C#)]活动目录的作用。
答:1) Active Directory存储了有关网络对象的信息,并且让管理员和用户能够轻松地查找和使用这些信息。
2) Active Directory使用了一种结构化的数据存储方式,并以此作为基础对目录信息进行合乎逻辑的分层组织。
20. [.NET(C#)]在.NET中,配件的意思。
答:程序集(中间语言,源数据,资源,装配清单)。
21. [.NET(C#)]值类型和引用类型的区别。
答:1) 值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高。
2) 引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例。
3) 一个是值COPY,一个是地址COPY。
值类型
引用类型
内存分配地点
分配在栈中
分配在堆中
效率
效率高,不需要地址转换
效率低,需要进行地址转换
内存回收
使用完后,立即回收
使用完后,不是立即回收,等待GC回收
赋值操作
进行复制,创建一个同值新对象
只是对原有对象的引用
函数参数与返回值
是对象的复制
是原有对象的引用,并不产生新的对象
类型扩展
不易扩展
容易扩展,方便与类型扩展
22. [.NET(C#)].NET中的垃圾回收机制。
答:.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每次使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。
23. [.NET(C#)]什么是索引器?如何使用索引器?
答:索引器允许类或结构的实例按照与数组相同的方式进行索引。索引器类似于属性,不同之处在于它们的访问器采用参数。可以用任意类型索引。
1) 索引器使得对象可按照与数组相似的方法进行索引。
2) get 访问器返回值。set 访问器分配值。
3) this 关键字用于定义索引器。
4) value 关键字用于定义由 set 索引器分配的值。
5) 索引器不必根据整数值进行索引,由您决定如何定义特定的查找机制。
6) 索引器可被重载。
7) 索引器可以有多个形参,例如当访问二维数组时。
24. [.NET(C#)]进程和线程的区别。
答:1) 进程是系统进行资源分配和调度的单位。
2) 线程是CPU调度和分派的单位。
3) 一个进程可以有多个线程,这些线程共享这个进程的资源。

[.NET(C#)]启动一个线程是用run()还是start()?
答:启动一个线程是调用start()方法,导致操作系统将当前实例的状态更改为ThreadState.Running。
[.NET(C#)]构造器Constructor是否可被override(重写)?
答:构造器Constructor不能被继承,因此不能重写override,但可以被重载Overloade。
[.NET(C#)]静态成员和抽象类
答:静态成员override、virtual 或 abstract。
抽象类不能是密封的sealed或静态的static。
[.NET(C#)]在C#中using和new这两个关键字有什么意义。
答:using 关键字有两个主要用途:
1) 作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。
2) 作为语句,用于定义一个范围,在此范围的末尾将释放对象。
new 关键字:新建实例或者隐藏父类方法
[.NET(C#)].NET的错误处理机制。
答:.NET错误处理机制采用try->catch->finally结构。 throw
发生错误时,层层上抛,直到找到匹配的catch为止。
[.NET(C#)]error和exception有什么区别。
答:error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。
不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。
也就是说,它表示如果程序运行正常,从不会发生的情况。
[.NET(C#)]UDP连接和TCP连接的异同。
答:1) TCP(Transmission Control Protocol)传输控制协议:一种面向连接的、可靠的、基于字节流的运输层通信协议,三次握手。
2) UDP(User Datagram Protocol)用户数据报协议:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点。缺点是易丢失数据包。
[.NET(C#)]System.String 和System.StringBuilder有什么区别?
答:1) System.String是不可变的字符串。
2) System.StringBuilder存放了一个可变的字符串,并提供一些对这个字符串修改的方法。
3) String类在执行字符串拼接的操作上,用“+”会产生新的对象,占用内存。
4) StringBuilder类只是修改字符串的内容,不建立新的对象。
[.NET(C#)]const和readonly有什么区别?
答:1) const 字段只能在该字段的声明中初始化。
2) 不允许在常数声明中使用 static 修饰符。
3) readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。
4) 另外,const 字段是编译时常数,而 readonly 字段可用于运行时常数。
[.NET(C#)]C#中的委托是什么?事件是不是一种委托?
答:委托可以把一个方法作为参数代入另一个方法。
委托可以理解为指向一个函数的引用。
事件是一种特殊的委托。
[.NET(C#)]什么是受管制的代码?
答:在类型或成员的声明中使用 unsafe 修饰符。因此,类型或成员的整个正文范围均被视为不安全上下文,无法由 CLR 进行验证的代码。
[.NET(C#)]面向对象语言三大特性。
答:封装、继承、多态。
[.NET(C#)]能用foreach遍历访问的对象需要实现什么接口或声明什么方法的类型。
答:声明IEnumerable接口或实现GetEnumerator()方法。
[.NET(C#)]接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?
答:接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。
[.NET(C#)]是否可以继承String类?
答:String类是sealed类故不可以继承。
[.NET(C#)]数组有没有length()这个方法? String有没有length()这个方法?
答:数组、String类都没有Length()方法,它们只有Length属性。
[.NET(C#)]成员变量和成员函数前加static的作用。
答:即使没有创建类的实例,也可以调用该类中的静态方法、字段、属性或事件。如果创建了该类的任何实例,不能使用实例来访问静态成员。静态成员通常用于表示不会随对象状态而变化的数据或计算。
[.NET(C#)]什么是虚函数?什么是抽象函数?
答:1) 虚函数:没有实现的,可由子类继承并重写的函数。
2) 抽象函数:规定其非虚子类必须实现的函数,必须被重写。
[.NET(C#)]C#中的三元运算符是?
答:?:。格式如下:ndition ? first_expression : second_expression。
[.NET(C#)]当整数a赋值给一个object对象时,整数a将会被?
答:装箱。
[.NET(C#)]委托声明的关键字是?
答:delegate.
[.NET(C#)]在.Net中所有可序列化的类都被标记为?
答:[Serializable]
[.NET(C#)]利用operator声明且仅声明了==,有什么错误么?
答:要同时修改Equale和GetHash() ? 重载了”==” 就必须重载 “!=”。
[智力题] 62-63=1 等式不成立,请移动一个数字(不可以移动减号和等于号),使得等式成立,如何移动?
答:62移动成2的6次方,26。
[.NET(C#)]C#可否对内存进行直接的操作?
答:可以使用指针。也就是非安全代码,就是不在 CLR 完全控制下执行的代码,它有可能会导致一些问题,因此他们必须用 “unsafe” 进行表明。
[.NET(C#)] &和&&的区别。
&是位运算符,表示按位与运算。
&&是逻辑运算符,表示逻辑与(and)
[.NET(C#)]重载的方法是否可以改变返回值的类型?
可以。
[.NET(C#)]如何把一个Array复制到ArrayList里?
(1) 实现1
string[] s ={ “111”, “22222” };
ArrayList list = new ArrayList();
list.AddRange(s);
(2)实现2
string[] s ={ “111”, “22222” };
ArrayList list = new ArrayList(s);
[.NET(C#)]什么是强名?
强名是由程序集的标识加上公钥和数字签名组成的,其中,程序集的标识包括简单文本名称、版本号和区域性信息(如果提供的话)。它使用对应的私钥从程序集文件中生成。(程序集文件包含程序集清单,其中包含组成程序集的所有文件的名称和哈希。)
[.NET(C#)]C#中几种循环的方法,并指出他们的不同:
for:使用于确定次数的循环。
foreach:使用于遍历的元素是只读的。//Unity中此处是个坑,建议不要用foreach
while:次数不确定,条件随机变化。
do…while:次数不确定,条件随机变化,但至少要保证能被执行一次。
[.NET(C#)]请简述一下用Socket进行同步通讯编程的详细步骤
在应用程序和远程设备中使用协议和网络地址初始化套接字。
在应用程序中通过指定端口和地址建立监听。
远程设备发出连接请求。
应用程序接受连接产生通信scoket。
应用程序和远程设备开始通讯(在通讯中应用程序将挂起直到通讯结束)。
通讯结束,关闭应用程序和远程设备的Socket回收资源。
[.NET(C#)] try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?
答:会执行,在return前执行。
[.NET(C#)] 写出程序的输出结果:
public class A
{
  public virtual void Fun1( int i )
  {
    Console.WriteLine( i );
  }
  public void Fun2( A a )
  {
  a.Fun1( 1 );
  Fun1( 5 );
  }
}

public class B : A
{
  public override void Fun1( int i )
  {
    base.Fun1( i + 1 );
  }

  public static void Main()
  {
    B b = new B();
    A a = new A();
    a.Fun2( b );
    b.Fun2( a );
  }
}

答案:
2
5
1
6

[.NET(C#)]求以下表达式的值,写出您想到的一种或几种实现方法:1 - 2 + 3 - 4 +……+ m
答案:
public static void Main()
{
  int input = int.Parse( Console.ReadLine() );
  int sum = 0;
  for( int i = 0; i <= input; i++ )
  {
    if( ( i % 2 ) == 1 )
    {
      sum += i;
    }
    else
    {
      sum = sum - i;
    }
  }
  Console.WriteLine( sum );
}

[.NET(C#)]两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
答:不对,有相同的hash code。
[.NET(C#)] swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
答案:可以作用在byte和long上,也可以作用在string上。
[.NET(C#)] short s1 = 1; s1 = s1 + 1;有什么错?short s1 = 1; s1 += 1;有什么错?
答: 1. short s1 = 1; s1 = s1 + 1;有错,s1是short型,s1+1是int型,不能隐式转化为short型。可修改为s1 =(short)(s1 + 1) 。
short s1 = 1; s1 += 1正确。
[.NET(C#)] 字符串处理问题
题目:需要实现对一个字符串的处理,首先将该字符串首尾的空格去掉,如果字符串中间还有连续空格的话,仅保留一个空格,即允许字符串中间有多个空格,但连续的空格数不可超过一个。
答案:
string inputStr = ” xx xx “;
inputStr = Regex.Replace( inputStr.Trim(), @”/s+”, ” ” );
[.NET(C#)]下面这段代码输出什么?为什么?
int i = 5;
int j = 5;
if( Object.ReferenceEquals( i, j ) )
Console.WriteLine( “Equal” );
else
Console.WriteLine( “Not Equal” );
答案:Not Equal。因为比较的是对象
[.NET(C#)] public static const int A=1;这段代码有错误么?是什么?
答:const不能用static修饰。
[.NET(C#)]下面的代码中有什么错误吗?
class A

{

public virtual void F()

{

Console.WriteLine( “A.F” );

}

}

abstract class B : A

{

public abstract override void F();//abstract与override关键字可以同时使用

}

答案:没有错误!可以通过编译器。
注意:网上有资料说abstract与override关键字不可以同时使用,这种说法是错误的 !
66. [.NET(C#)] 以下是一些C#中的枚举型的定义,其中错误的用法有?
A. public enum var1{ Mike = 100, Nike = 102, Jike }
B. public enum var1{ Mike = “1”, Nike, Jike }
C. public enum var1{ Mike=-1 , Nike, Jike }
D. public enum var1{ Mike , Nike , Jike }
答案B
67. [.NET(C#)] 下面的例子中产生的输出结果是什么?
class A
{
public static int X;
static A()
{
X = B.Y + 1;
}
}
class B
{
public static int Y = A.X + 1;
static B()
{
}
static void Main()
{
Console.WriteLine( “X={0},Y={1}”, A.X, B.Y );
}
}
答:x=1,y=2
68. [.NET(C#)] 写出程序的输出结果
class Class1
{
private string str = “Class1.str”;
private int i = 0;
static void StringConvert( string str )
{
str = “string being converted.”;
}
static void StringConvert( Class1 c )
{
c.str = “string being converted.”;
}
static void Add( int i )
{
i++;
}
static void AddWithRef( ref int i )
{
i++;
}
static void Main()
{
int i1 = 10;
int i2 = 20;
string str = “str”;
Class1 c = new Class1();
Add( i1 );
AddWithRef( ref i2 );
Add( c.i );
StringConvert( str );
StringConvert( c );
Console.WriteLine( i1 );
Console.WriteLine( i2 );
Console.WriteLine( c.i );
Console.WriteLine( str );
Console.WriteLine( c.str );
}
}
答案:10, 21, 0, str, string being converted
注意:此处加逗号“,”是为了答案看起来清晰,实际结果是纵向排列的,因为调用了Console.WriteLine()。
69. [.NET(C#)] 写出程序的输出结果
public abstract class A
{
public A()
{
Console.WriteLine( ‘A’ );
}
public virtual void Fun()
{
Console.WriteLine( “A.Fun()” );
}
}
public class B : A
{
public B()
{
Console.WriteLine( ‘B’ );
}
public new void Fun()
{
Console.WriteLine( “B.Fun()” );
}
public static void Main()
{
A a = new B();
a.Fun();
}
}
答案:
A
B
A.Fun()

[C#语言基础]简述string与StringBuilder相比的优点。
答:
string的字符串为常量不可修改。
1.可以满足字符串常量池的需要。即对于创建相同文本时,string为同一对象。
2.允许string对象缓存HashCode。字符串不变形保证了hash码的唯一性。
3.安全性,string被许多的类库用来当做参数。
StringBuilder实际是一个char类型的集合,完成了组装拼接等工作。
String更适合做程序的参数,StringBuilder用于做String类型的组装。
[C#语言基础]C#StringBuilder类型比string类型的优势是什么?
答:
StringBuilder的优势在于拼接字符串。
//String的优势在于对字符串做一些处理,在使用过程中看具体的需求。
[C#语言基础]编写程序判断整数46234的每位相加和。
答:
int number = 46234;
string strNumber=number.ToString();
int length = strNumber.Length;
方法1:
int result=0;
for (int i = 0; i < length; i++)
{
result += number % 10;
number \= 10;
}
方法2:
for (int i = 0; i < length; i++)
{
result += number / (int)Math.Pow(10, i) % 10;
}
方法3:
for (int i = 0; i < strNumber.Length; i++)
{
result += int.Parse(strNumber[i].ToString());
}
注:
[算法]请使用冒泡排序,对一维数组进行降序排列。
答:
冒泡排序:它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换。
冒泡排序算法的运作如下:
l 比较相邻元素。如果第一个比第二个大,就交换它们。
l 对每一对相邻元素做同样的工作,从开始第一队到结尾的最后一对。在这一点,最后的元素会使最大的数。
l 针对所有的元素重复以上的步骤,除了最后一个。
l 持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
写法1
public void Bubble1(int[] a)
{
bool b;
int tmp;
for (int i = 0; i < a.Length; i++)
{
b = false;
for (int j = i+1; j < a.Length ; j++)
{
if (a[j] > a[i])
{
tmp = a[j];
a[j] = a[i];
a[i] = tmp;
b = true;
}
}
if(!b)break;
}
}
写法2
void Bubble2(int[] a)
{
bool b=false;
int tmp;
for(int i=0;i

2014-10-22 10:43:47 u010249782 阅读数 505

此文章是在网上看到的,我也是才开始学习unity,所以这些优化问题值得收藏学习

此总结由自己经验及网上收集整理优化内容 包括:

1.代码方面;
2.函数使用方面;
3.ngui注意方面;
4.数学运算方面;
5.内存方面;
6.垃圾回收方面 等等...
本着相互交流 共同进步的原则

好了,总结如下:
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的内存,如果你做了101024的图集,你的界面上刚好都只用了每个图集里面的一张小图,那么再次抱歉你的内存直接飙40M。意思是任何一个4096的图片,不管是图集还是texture,他都占用4*4=16M


1、在使用数组或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++) 
   
}


2、少使用临时变量,特别是在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; 
}
  
3、如果可能,将GameObject上不必要的脚本disable掉。
如果你有一个大的场景在你的游戏中,并且敌方的位置在数千米意外,
这时你可以disable你的敌方AI脚本直到它们接近摄像机为止。
一个好的途径来开启或关闭GameObject是使用SetActiveRecursively(false),并且球形或盒型碰撞器设为trigger。

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

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

6、协同是一个好方法。
可以使用协同程序来代替不必每帧都执行的方法。(还有InvokeRepeating方法也是一个好的取代Update的方法)。
7、尽可能不要再Update或FixedUpdate中使用搜索方法(例如GameObject.Find()),你可以像前面那样在Start方法里获得它。
8、不要使用SendMessage之类的方法,他比直接调用方法慢了100倍,你可以直接调用或通过C#的委托来实现。
9、使用javascript或Boo语言时,你最好确定变量的类型,不要使用动态类型,这样会降低效率,
你可以在脚本开头使用#pragmastrict 来检查,这样当你编译你的游戏时就不会出现莫名其妙的错误了。
====================================================================分割线=====================================================
1、顶点性能     
一般来说,如果您想在iPhone 3GS或更新的设备上每帧渲染不超过40,000可见点,
那么对于一些配备 MBX GPU的旧设备(比如,原始的 iPhone,如 iPhone 3g和 iPod Touch第1和第2代)来说,你应该保证每帧的渲染顶点在10000以下。
2、光照性能     
像素的动态光照将对每个受影响的像素增加显著的计算开销,并可能导致物体会被渲染多次。
为了避免这种情况的发生,您应该避免对于任何单个物体都使用多个像素光照,并尽可能地使用方向光。
需要注意的是像素光源是一个渲染模式(Render Mode)设置为重要(Important)的光源。
像素的动态光照将对顶点变换增加显著的开销。所以,应该尽量避免任何给定的物体被多个光源同时照亮的情况。
对于静态物体,采用烘焙光照方法则是更为有效的方法。
3、角色     
每个角色尽量使用一个Skinned Mesh Renderer,这是因为当角色仅有一个 Skinned Mesh Renderer 时,
Unity 会使用可见性裁剪和包围体更新的方法来优化角色的运动,而这种优化只有在角色仅含有一个 Skinned Mesh Renderer时才会启动。
角色的面数一般不要超过1500,骨骼数量少于30就好,角色Material数量一般1~2个为最佳。
4、静态物体     
对于静态物体定点数要求少于500,UV的取值范围不要超过(0,1)区间,这对于纹理的拼合优化很有帮助。
不要在静态物体上附加Animation组件,虽然加了对结果没什么影响,但是会增加CPU开销。
5、摄像机     
将远平面设置成合适的距离,远平面过大会将一些不必要的物体加入渲染,降低效率。
另外我们可以根据不同的物体来设置摄像机的远裁剪平面。Unity 提供了可以根据不同的 layer 来设置不同的 view distance ,
所以我们可以实现将物体进行分层,大物体层设置的可视距离大些,而小物体层可以设置地小些,
另外,一些开销比较大的实体(如粒子系统)可以设置得更小些等等。
6、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的统计结果,而不是假定它已经达到了您想要的效果。

转载来自于:http://www.unitymanual.com/forum.php?mod=viewthread&tid=21597&extra=page%3D1%26filter%3Dtypeid%26typeid%3D143

MaterialPropertyBlock

阅读数 2256

没有更多推荐了,返回首页