简述delegate和event关键字
delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。与其它的类不同,delegate类能够拥有一个签名(signature),并且它只能持有与它的签名相匹配的方法的引用。实现一个delegate只需要三步1.声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。2.创建delegate对象,并将你想要传递的函数作为参数传入。3.在要实现异步调用的地方,通过上一步创建的对象来调用方法。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber)类。事件使用 发布-订阅(publisher-subscriber)模型。
发布器(publisher)是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber)是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
三、事件和委托的区别
1.委托允许直接通过委托去访问相应的处理函数,而事件只能通过公布的回调函数去调用
2.事件只能通过“+=”,“-=”方式注册和取消订户处理函数,而委托除此之外还可以使用“=”直接赋值处理函数。
Unity3D脚本生命周期的几个重要方法,请列举:
Awake () {} 脚本唤醒,此方法为系统执行的第一个方法,用于脚本的初始化,在脚本的生命周期中只执行一次。
Start () {} 此方法在Awake()方法之后、Update()方法之前执行,并且只执行一次。
Update () {} 正常更新,用于更新逻辑。此方法每帧都会由系统自动调用一次。
FixedUpdate () {} 固定更新。固定更新常用于移动模型等操作。
LateUpdate () {} 推迟更新,此方法在Update() 方法执行完后调用,同样每一帧都调用。
OnGUI () {} 绘制界面,每一帧都调用,用来绘制界面的。
OnDestroy () {} 当前脚本销毁时调用。
什么是AssetBundle? 谈谈你对AssetBundle内存分配情况的理解
Assetbundle 是Unity
Pro提供提供的功能,它可以把多个游戏对象或者资源二进制文件封装到Assetbundle中,提供了封装与解包的方法使用起来很便利。
内存:
我们从AssetBundle中加载资源一般会经过三个步骤:
1.www、LoadFromFile、LoadFromMemory等接口加载AssetBundle本身。
2.通过AssetBundle.LoadAsset()等接口从AssetBundle中加载资源。
3.对于GameObject类资源,还需通过GameObject.Instantiate()创建clone。
www类本身占用内存,通过www接口加载AssetBundle才会有这部分内存,www对象保留了一份对WebStream数据(粉色部分)的引用。使用www=null 或者www.dispose()释放。其www.dispose()会立即释放,而www =null会等待垃圾回收。释放www后WebStream的引用计数会相应减一。
WebStream数据,是数据真正的存储区域。当AssetBundle被加载进来后,这部分内存就被分配了。它包含3个内容:压缩后的AssetBundle本身、解压后的资源以及一个解压缓冲区。无论www)还是后面会提到的AssetBundle对象,都只是有一个结构指向了WebStream数据,从而能对外部提供操作真正资源数据的方法。而当www对象和AssetBundle对象释放时,WebStream数据的引用计数也会相应减1。当WebStream数据引用计数为0时,系统会自动释放。但为了不频繁地开辟和销毁解压Buffer,其中绿色Decompression解压缓冲区Unity会至少保留一份。例如同时加载3个AssetBundle时,系统会生成3个Decompression Buffer,当解压完成后,系统会销毁两个。
AssetBundle对象,引用了WebStream数据部分,并提供了从WebStream数据中加载资源的接口。通过AssetBundle.Unload(boolunloadAllLoadedObjects)释放。如果调AssetBundle.Unload(false),将释放AssetBundle对象本身,其对WebStream引用也将减少,从而可能引起WebStream释放,我们也就无法再通过接口或依赖关系从该AssetBundle加载资源。但已加载的资源还可以正常使用。如果调用的是AssetBundle.Unload(true),不仅会释放WebStream部分,所有被加载出来的资源将被释放。无论true或false,AssetBundle.Unload()都将销毁AssetBundle,销毁后调用该AssetBundle对象的任何方法都不会生效或产生报错,也就是说这个接口只能被调用一次,不能先调用unload(false)再调用unload(true)。
也就是就是我们通过Instantiate()创建的GameObject所包含的资源。这些包含的资源又根据类型,与AssetBundle原始资源(WebStream资源部分)有不同的关系。有些如Texture、shader资源,我们通常只是使用,并不会对其做出改动,所以仅仅是引用关系;而每个GameObject都是特殊的,所以是完全复制了一份;至于Mesh和Material,则是引用+复制的关系。
什么是Draw Call ,有什么方法可以减少DrawCall
DrawCall: CPU通过调用绘制命令来告诉GPU开始进行一个渲染过程(称为一次Draw Call)。一个Draw Call命令会指向本次绘制需要渲染的信息,这些信息包括:顶点数据、纹理数据、shader参数(光照模型、法线方向、光照方向等)等
CPU方面:
1. DrawCalls
使用Draw Call Batching,Dynamic Batching 动态批处理自动进行的,1.批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。2. 所有统一缩放的物体不会进行批处理3. 预设体的实例会自动地使用相同的网格模型和材质,可以动态批处理通过把纹理打包成图集来尽量减少材质的使用。尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。
2.物理组件
设置一个合适的Fixed Timestep就是不要使用网格碰撞器(mesh collider)
3.GC垃圾回收
大量的或频繁的字符串连接操作时,就一定要用StringBuilder某些可能的情况下,可以使用结构(struct)来代替类(class)使用“对象池”,以实现空间的重复利用尽量不要使用foreach,而是使用for不要直接访问gameobject的tag属性
4.代码优化
最好不要频繁使用GetComponent,应该只访问一次之后就将它的引用保留善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0)对于方法的参数的优化:善于使用ref关键字
1、关闭所有在update类中执行log的打印操作(Unity中一次log打印有时长达7ms,Profiler数据)。2、不在update类方法调用Getcomponent、SendMessage、FindWithTag这几个耗时较长的方法。3、不在update类方法中使用临时变量。
GPU方面:
1. 减少绘制的数目
使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。
保持材质的数目尽可能少。这使得Unity更容易进行批处理。
如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。
使用光照纹理(lightmap)而非实时灯光。
使用LOD,LOD技术有点类似于Mipmap技术,不同的是,LOD是对模型建立了一个模型金字塔,根据摄像机距离对象的远近,选择使用不同精度的模型。它的好处是可以在适当的时候大量减少需要绘制的顶点数目。它的缺点同样是需要占用更多的内存,而且如果没有调整好距离的话,可能会造成模拟的突变。遮挡剔除(Occlusion culling)。
使用mobile版的shader。因为简单。
优化基本几何体:尽可能地减少顶点数、背面删减
2. 优化现存带宽
压缩图片,减小显存带宽的压力。OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。使用Mipmap,Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。
3. 内存的优化
mesh合并会加大内存
静态批处理会加大内存
Mipmap加大内存
纹理格式加大内存:对于IOS选择使用 PVRTC压缩格式的,对于Android选择ETC压缩格式的,纹理可以节省大量内存和读取速度快,但是会有所降低图像的质量。
bundle.Unload(false); ab的卸载
www.Dispose(); :删除Web Stream 。WebStream的大小则是AssetBundle原始文件大小 + 解压后的数据大小 + DecompressionBuffer(0.5MB)
LOD
谈谈你对U3D渲染管线的理解
渲染管线(Rendering Pipeline)其实就是GPU渲染。流程渲染管线流程,它可以细分为:顶点处理,面处理,光栅化,像素处理。
顶点处理,就是通过一系列的坐标系转换,将模型的顶点在摄像机前进行位移,并最终将模型投影到摄像机的屏幕上。(本地坐标系-世界坐标系-观察坐标系-投影坐标系),这一系列变换会涉及到各个坐标系中的矩阵变换,包括顶点的坐标变换、逐顶点雾化、材质属性和光照属性处理。
面处理主要包括:面的组装,面截取,面剔除。
光栅阶段:(1)消除遮挡面;(2)Texture operation,纹理操作,根据像素的纹理坐标,查询对应的纹理值;(3)Blending,通常称为alphablending,根据目前已经画好的颜色,与正在计算的颜色的alpha值混合,形成新的颜色。(4)Filtering,将正在计算的颜色经过某种滤镜后输出。该阶段之后,像素的颜色值被写入帧缓存中。
像素处理主要包括:对每个像素区域进行着色,对像素贴上贴图,最后形成最终的画面。
什么是material,什么是shader,二者有什么关系
材质系统是定义了如何渲染物件表面的信息 里面包含的贴图 贴图的平铺信息 颜色信息 等等信息。Shader里面使用材质信息加上自身操作,最终呈现物件的渲染。
Shader是Material的一部分。程序上是叫着色器,是根据计算即时演算生成贴图的程序。常常用来处理那些无法用固定贴图表现的模型,比如玻璃,水面等等。实际上是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。
Materail是模型的材质。是顶端的了,包含贴图,shader,凹凸等消息。
Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输入的贴图或者颜色等,加上对应的Shader,以及对Shader的特定的参数设置,将这些内容(Shader及输入参数)打包存储在一起,得到的就是一个Material(材质)。之后,我们便可以将材质赋予合适的renderer(渲染器)来进行渲染(输出)了。
简述单例模式与观察者模式,并用伪代码实现其中之一
写一个可以对所有可比较类排序的函数
策划档案给出一个需求:从0到100,每帧顺序取出一个数字,判断此数字是否是质数
int flag = 1;
int i=-1;
update(){
if(i<=100){
i++;
for (int j = 2; j <= Math.Sqrt(i); j++)
{
if(i % j == 0)
{
flag = 0;
break;
}
if (flag == 1) Console.WriteLine("{0}", i);
flag = 1 ;
}
}