欢迎光临
我们一直在努力

fsm包是什么牌子Unity全面面试题汇总

Unity新手入门进阶之路(群内各种入门资源资料大神):721559786 (加群下载插件资源学习资料)
1、什么是协同程序

协程称为协同程序,即在主线程运行的同时开启另一段逻辑,来协助当前程序的执行,StartCoroutine是开启协程,StopCoroutine是关闭协程,协程的返回值是 IEnumerator类型,而在协程内部必须有yield return **,IEnumerator用来记录执到yield return的位置,每次执行协程时均是从当前位置向后执行,而不是从协程的最开始位置执行,除非只有一个yield return;在一个主程序中可以开启多个协程。

2、请简述ArrayList和List的主要区别

ArrayList存在不安全类型(ArrayList会把所有插入其中的数据都当做Object来处理)
装箱拆箱的操作,List是泛型,可以指定特定的类型,避免过多的装箱拆箱操作,增加对内存的消耗;

3、MeshRender中material和sharedmaterial的区别

修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代

 

4、简述一下对象池,你觉得在FPS里哪些东西适合使用对象池

(空间获取时间)

对象池就存放需要被反复调用资源的一个空间,当一个对象回大量生成的时候如果每次都销毁创建会很费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果,在FPS游戏中要常被大量复制的对象包括子弹,敌人,粒子等

5、什么叫做链条关节

Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力

6、Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),其用来操作的数据类型有哪三种,请列出保存和读取整形数据的函数

int,float,string
PlayerPrefs.SetInt()  PlayerPrefs.GetInt() 

 

7、Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,请列出系统自带的几个重要的方法

Awake——>OnEnable–>Start——>FixedUpdate——>Update——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy 

 

8、物理更新一般放在哪个系统函数里

FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。

 

9、MipMap是什么,作用

Mipmap技术有点类似于LOD技术,但是不同的是,LOD针对的是模型资源,而Mipmap针对的纹理贴图资源使用Mipmap后,贴图会根据摄像机距离的远近,选择使用不同精度的贴图。缺点:会占用内存,因为mipmap会根据摄像机远近不同而生成对应的八个贴图,所以必然占内存!优点:会优化显存带宽,用来减少渲染,因为可以根据实际情况,会选择适合的贴图来渲染,距离摄像机越远,显示的贴图像素越低,反之,像素越高!MipMap可以用于跑酷类游戏,当角色靠近时,贴图清晰显示,否则模糊显示;

 

10、LOD是什么,优缺点是什么?

LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。缺点是增加了内存。

 

11、结构体和类有何区别

结构体是一种值类型,而类是引用类型。(值类型、引用类型是根据数据存储的角度来分的)就是值类型用于存储数据的值,引用类型用于存储对实际数据的引用。那么结构体就是当成值来使用的,类则通过引用来对实际数据操作

12、ref参数和out参数是什么?有什么区别?

ref和out参数的效果一样,都是通过关键字找到定义在主函数里面的变量的内存地址,并通过方法体内的语法改变它的大小。不同点就是输出参数必须对参数进行初始化。ref必须初始化,out 参数必须在函数里赋值。ref参数是引用,out参数为输出参数。

13、C#的委托是什么?有何用处?

委托类似于一种安全的指针引用,在使用它时是当做类来看待而不是一个方法,相当于对一组方法的列表的引用。用处:使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。

14、如何优化内存?

1.压缩自带类库;
2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
3.释放AssetBundle占用的资源;
4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。
6.代码中少产生临时变量

15、下列代码在运行中会发生什么问题?如何避免?

List<int> ls = new List<int>(new int[] { 1, 2, 3, 4, 5 });
foreach (int item in ls)
{
    Console.WriteLine(item * item);
    ls.Remove(item);
}

产生运行时错误,在 ls.Remove(item)这行,因为foreach是只读的。不能一边遍历一边修改

16、Unity3D Shader分哪几种,有什么区别

表面着色器的抽象层次比较高,它可以轻松地以简洁方式实现复杂着色。表面着色器可同时在前向渲染及延迟渲染模式下正常工作。
    顶点片段着色器可以非常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成

固定功能管线着色器可以作为前两种着色器的备用选择,当硬件无法运行那些酷炫Shader的时,还可以通过固定功能管线着色器来绘制出一些基本的内容.

17CharacterController和Rigidbody的区别

Rigidbody具有完全真实物理的特性,而CharacterController可以说是受限的Rigidbody,具有一定的物理效果但不是完全真实的.

 

18、移动摄像机的动作放在哪个系统函数中,为什么放在这个函数中?

LateUpdate,结束后才调,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有Up在每帧执行完毕调用,它是在所有Updatedate操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现

19、获取、增加、删除组件的命令分别是什么

获取:GetComponent 增加:AddComponent  删除:Destroy

20、请简述如何在不同分辨率下保持UI的一致性

NGUI很好的解决了这一点,屏幕分辨率的自适应性,原理就是计算出屏幕的宽高比跟原来的预设的屏幕分辨率求出一个对比值,然后修改摄像机的size。UGUI通过锚点和中心点和分辨率也解决这个问题

22、请描述Interface与抽象类之间的不同

1. 一个类只能继承一个抽象类,但可以同时继承多个接口;
2. 抽象类里面可以有字段,接口里面不能有字段;
3. 抽象类里面可以有私有成员,接口里面所有的成员都是公有的;
4. 抽象类的成员可以带或者不带访问修饰符,接口里面一定不带访问修饰符;
5. 一个子类继承抽象类时需重写里面的抽象方法,当子类是抽象类时可以不用重写,而继承接口时一定要实现接口里面的成员;

23、向量的点乘,叉乘及归一化的意义

1.点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影
2.叉乘得到的向量垂直于原来的两个向量
3.标准化向量:用在只关心方向,不关心大小的时候

假如 向量a 为(x1, y1),向量b为(x2, y2)

点积结果 x1 * x2 + y1 * y2 = |a||b| cos<a,b>

叉积的模为 x1 * y2 – x2 * y1 = |a||b| sin<a,b>

点乘公式:

对于向量a和向量b:

                

a和b的点积公式为:

 

要求一维向量a和向量b的行列数相同。

点乘几何意义

 

点乘的几何意义是可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影,有公式:

 

 

叉乘公式:

两个向量的叉乘,又叫向量积、外积、叉积,叉乘的运算结果是一个向量而不是一个标量。并且两个向量的叉积与这两个向量组成的坐标平面垂直。

 

对于向量a和向量b:

 

a和b的叉乘公式为:

 

其中:

 

 

根据i、j、k间关系,有:

 

叉乘几何意义

在三维几何中,向量a和向量b的叉乘结果是一个向量,更为熟知的叫法是法向量,该向量垂直于a和b向量构成的平面。

24、请简述private,public,protected,internal的区别

public:对任何类和成员都公开,存取不受限制
private:只有包含该成员的类可以存取
protected:只有包含该成员的类以及派生类可以存取
internal:只有当前工程可以存取
protected internal:protected + internal

 

25Unity3D的协程和C#线程之间的区别是什么?

:多线程程序同时运行多个线程 ,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。 除主线程之外的线程无法访问Unity3D的对象、组件、方法。

Unity3d没有多线程的概念,不过unity也给我们提供了 StartCoroutine (协同程序)和 LoadLevelAsync (异步加载关卡)后台加载场景的方法。

StartCoroutine 的函数体里处理一段代码时,利用 yield 语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。

26、简述 StringBuilder 和 String 的区别?

String 字符串常量

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

String 类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String 对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就 要不断用 String 类型。
StringBuilder 对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。

这一点我们平时使用中也许都知道,连接操作频繁的时候,使用 StringBuilder 对象。

的生成新的对象,所以效率很低,建议在不断更改 String 对象的地方不要使

 

String 字符串常量

StringBuffer 字符串变量(线程安全)

StringBuilder 字符串变量(非线程安全)

 简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:

 String S1 = “This is only a” + “ simple” + “ test”;

 StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

 你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个

 String S1 = “This is only a” + “ simple” + “test”; 其实就是:

 String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:

String S2 = “This is only a”;

String S3 = “ simple”;

String S4 = “ test”;

String S1 = S2 +S3 + S4;

这时候 JVM 会规规矩矩的按照原来的方式去做

在大部分情况下 StringBuffer > String

StringBuffer

Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。

可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。

例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。

 

在大部分情况下 StringBuilder > StringBuffer

java.lang.StringBuilde

java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

28、值类型和引用类型有何区别?

1.值类型的数据存储在内存的栈中;引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。
2.值类型存取速度快,引用类型存取速度慢。
3.值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用
4.值类型继承自System.ValueType,引用类型继承自System.Object
5.栈的内存分配是自动释放;而堆在.NET中会有GC来释放
6.值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。

29、动态加载资源的方式?

1.通过Resources模块,调用它的load函数:可以直接load并返回某个类型的Object,前提是要把这个资源放在以Resource命名的文件夹下,Unity不管有没有场景引用,都会将其全部打入到安装包中。Resources.Load();
2.通过bundle的形式:即将资源打成 asset bundle 放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object。AssetBundle
3.通过AssetDatabase.loadasset :这种方式只在editor范围内有效,游戏运行时没有这个函数,它通常是在开发中调试用的【AssetDatabase 资源数据库】
区别:Resources的方式需要把所有资源全部打入安装包,这对游戏的分包发布(微端)和版本升级(patch)是不利的,所以unity推荐的方式是不用它,都用bundle的方式替代,把资源达成几个小的bundle,用哪个就load哪个,这样还能分包发布和patch,但是在开发过程中,不可能没更新一个资源就打一次bundle,所以editor环境下可以使用AssetDatabase来模拟,这通常需要我们封装一个dynamic resource的loader模块,在不同的环境下做不同实现。

30、在类的构造函数前加上static会报什么错?为什么?

构造函数格式为 public+类名如果加上static会报错(静态构造函数不能有访问修饰符)


原因:静态构造函数不允许访问修饰符,也不接受任何参数; 


无论创建多少类型的对象,静态构造函数只执行一次; 


运行库创建类实例或者首次访问静态成员之前,运行库调用静态构造函数; 


静态构造函数执行先于任何实例级别的构造函数; 


显然也就无法使用this和base来调用构造函数。


 

32、什么是DrawCall?DrawCall高了又什么影响?如何降低DrawCall?

Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。Draw Call是一个命令,由CPU发起,由GPU执行,该命令仅仅指向一个需要被渲染的图元列表DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:
1. Dynamic Batching
2. Static Batching
3. 高级特性Shader降级为统一的低级特性的Shader。

 

33UNITY3d在移动设备上的一些优化资源的方法

1.使用assetbundle,实现资源分离和共享,将内存控制到200m之内,同时也可以实现资源的在线更新
2.顶点数对渲染无论是cpu还是gpu都是压力最大的贡献者,降低顶点数到8万以下,fps稳定到了30帧左右
3.只使用一盏动态光,不是用阴影,不使用光照探头
粒子系统是cpu上的大头
4.剪裁粒子系统
5.合并同时出现的粒子系统
6.自己实现轻量级的粒子系统
animator也是一个效率奇差的地方
7.把不需要跟骨骼动画和动作过渡的地方全部使用animation,控制骨骼数量在30根以下
8.animator出视野不更新
9.删除无意义的animator
10.animator的初始化很耗时(粒子上能不能尽量不用animator)
11.除主角外都不要跟骨骼运动apply root motion
12.绝对禁止掉那些不带刚体带包围盒的物体(static collider )运动
NUGI的代码效率很差,基本上runtime的时候对cpu的贡献和render不相上下
13每帧递归的计算finalalpha改为只有初始化和变动时计算
14去掉法线计算
15不要每帧计算viewsize 和windowsize
16filldrawcall时构建顶点缓存使用array.copy
17.代码剪裁:使用strip level ,使用.net2.0 subset
18.尽量减少smooth group
19.给美术定一个严格的经过科学验证的美术标准,并在U3D里面配以相应的检查工具

 

 

 

34、请简述GC(垃圾回收)产生的原因,并描述如何避免?

当我用new创建一个对象时,当可分配的内存不足GC就会去回收未使用的对象,但是GC的操作是非常复杂的,会占用很多CPU时间,对于移动设备来说频繁的垃圾回收会严重影响性能。下面的建议可以避免GC频繁操作。
 1)减少用new创建对象的次数,在创建对象时会产生内存碎片,这样会造成碎片内存不法使用
2)使用公用的对象(静态成员,常量),但是不能乱用,因为静态成员和常量的生命周期是整个应用程序。
3)在拼接大量字符串时StringBuilder。在使用注意,创建StringBuilder对象时要设置StringBuilder的初始大小如:
StringBuilder sbHtml = new StringBuilder (size);
4)使用object pool(对象池)

35、什么叫动态合批?跟静态合批有什么区别?

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。

区别:

动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。

静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。

http://blog.csdn.net/qinyuanpei/article/details/48262583

36List(链表)和数组的区别在哪里

从逻辑结构来看
1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。
2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素

 

从内存存储来看
1. (静态)数组从堆中分配空间, 对于程序员方便快速,但是自由度小
2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦

从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。

37、什么是LightMap?

LightMap:称为光照帖图,即把灯光和物体设置成静态,然后对灯光进行烘焙,此时在物体表面形成一张有灯光效果的帖图,此时把场景中的灯光删除掉,物体表面依然有灯光的效果,而这种效果是帖图造成的;光照帖图的优点是避免动态光照在场景中的实时渲染,减少drawcall;

 

38Unity3d中的碰撞器和触发器的区别?

碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;当Is Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器

 

39、unity脚本执行顺序详解 MonoBehaviour

 

unity脚本自带函数执行顺序如下:将下面脚本挂在任意物体运行即可得到

Awake ->OnEable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->OnDisable ->OnDestroy

 

Reset

Awake

OnEable

Start

FixedUpdate(yield WaitForFixedUpdate、Internal physics update、 OnTriggerXXX、 OnCollisionXXX、OnMouseXXX)

Update(yield null、yield WaitForScends、yield WWW、yield StartCoroutine、Internal animation update)

LateUpdate(OnWillRenderObject、OnPreCull、OnBecameVisible、OnBecameInvisible、OnPreRender、OnRenderObject、OnPostRender、OnRenderImage、OnDrawGizmos)

OnGUI(yield WaitForEndOfFrame、OnApplicationPause)

OnDisable

OnDestory

OnApplicationQuit

 

1. Awake:用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如GameObject.FindWithTag()这样的函数搜索它们。每个游戏物体上的Awake以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息Awake总是在Start之前被调用。它不能用来执行协同程序。

2. Start:仅在Update函数第一次被调用前调用。Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。在所有脚本实例中,Start函数总是在Awake函数之后调用。

3. FixedUpdate:固定帧更新,在Unity导航菜单栏中,点击“Edit”–>“Project Setting”–>“Time”菜单项后,右侧的Inspector视图将弹出时间管理器,其中“Fixed Timestep”选项用于设置FixedUpdate()的更新频率,更新频率默认为0.02s。

4. Update:正常帧更新,用于更新逻辑。每一帧都执行,处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)FixedUpdate,每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。

5.LateUpdate:在所有Update函数调用后被调用,和fixedupdate一样都是每一帧都被调用执行,这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。LateUpdate,在每帧Update执行完毕调用,他是在所有update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。

6.OnGUI:在渲染和处理GUI事件时调用。比如:你画一个button或label时常常用到它。这意味着OnGUI也是每帧执行一次。

7.Reset:在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个默认值。

8.OnDisable:当物体被销毁时 OnDisable将被调用,并且可用于任意清理代码。脚本被卸载时,OnDisable将被调用,OnEnable在脚本被载入后调用。注意: OnDisable不能用于协同程序。

9.OnDestroy:当MonoBehaviour将被销毁时,这个函数被调用。OnDestroy只会在预先已经被激活的游戏物体上被调用。注意:OnDestroy也不能用于协同程序。

40、设计模式
1、简单工厂模式

优点与缺点

  看完简单工厂模式的实现之后,你和你的小伙伴们肯定会有这样的疑惑(因为我学习的时候也有)——这样我们只是把变化移到了工厂类中罢了,好像没有变化的问题,因为如果客户想吃其他菜时,此时我们还是需要修改工厂类中的方法(也就是多加case语句,没应用简单工厂模式之前,修改的是客户类)。我首先要说:你和你的小伙伴很对,这个就是简单工厂模式的缺点所在(这个缺点后面介绍的工厂方法可以很好地解决),然而,简单工厂模式与之前的实现也有它的优点:

简单工厂模式解决了客户端直接依赖于具体对象的问题,客户端可以消除直接创建对象的责任,而仅仅是消费产品。简单工厂模式实现了对责任的分割。

简单工厂模式也起到了代码复用的作用,因为之前的实现(自己做饭的情况)中,换了一个人同样要去在自己的类中实现做菜的方法,然后有了简单工厂之后,去餐馆吃饭的所有人都不用那么麻烦了,只需要负责消费就可以了。此时简单工厂的烧菜方法就让所有客户共用了。(同时这点也是简单工厂方法的缺点——因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响,也没什么不好理解的,就如事物都有两面性一样道理)

虽然上面已经介绍了简单工厂模式的缺点,下面还是总结下简单工厂模式的缺点:

工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都会受到影响(通俗地意思就是:一旦餐馆没饭或者关门了,很多不愿意做饭的人就没饭吃了)

系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,这样就会造成工厂逻辑过于复杂。

了解了简单工厂模式之后的优缺点之后,我们之后就可以知道简单工厂的应用场景了:

当工厂类负责创建的对象比较少时可以考虑使用简单工厂模式()

客户如果只知道传入工厂类的参数,对于如何创建对象的逻辑不关心时可以考虑使用简单工厂模式

UML图

简单工厂模式又叫静态方法模式(因为工厂类都定义了一个静态方法),由一个工厂类根据传入的参数决定创建出哪一种产品类的实例(通俗点表达:通过客户下的订单来负责烧那种菜)。简单工厂模式的UML图如下:

 

 

2、工厂方法模式

优点与缺点

  工厂方法模式通过面向对象编程中的多态性来将对象的创建延迟到具体工厂中,从而解决了简单工厂模式中存在的问题,也很好地符合了开放封闭原则(即对扩展开发,对修改封闭)。

UML图

 

UML图可以看出,在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应的。针对UML图的解释如下:

Creator类:充当抽象工厂角色,任何具体工厂都必须继承该抽象类

TomatoScrambledEggsFactory和ShreddedPorkWithPotatoesFactory类:充当具体工厂角色,用来创建具体产品

Food类:充当抽象产品角色,具体产品的抽象类。任何具体产品都应该继承该类

TomatoScrambledEggs和ShreddedPorkWithPotatoes类:充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。

3、抽象工厂模式

抽象工厂模式:提供一个创建产品的接口来负责创建相关或依赖的对象,而不具体明确指定具体类

抽象工厂允许客户使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么。这样客户就可以从具体产品中被解耦。下面通过抽象工模式的类图来了解各个类中之间的关系:

 

优缺点

抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展,这真是抽象工厂模式的优点所在,然后抽象模式同时也存在不足的地方。下面就具体看下抽象工厂的缺点(缺点其实在前面的介绍中以已经涉及了):

抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这样也就违背了“开发——封闭”原则。

知道了抽象工厂的优缺点之后,也就能很好地把握什么情况下考虑使用抽象工厂模式了,下面就具体看看使用抽象工厂模式的系统应该符合那几个前提:

一个系统不要求依赖产品类实例如何被创建、组合和表达的表达,这点也是所有工厂模式应用的前提。

这个系统有多个系列产品,而系统中只消费其中某一系列产品

系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现。

 

4、建造者模式

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式使得建造代码与表示代码的分离,可以使客户端不必知道产品内部组成的细节,从而降低了客户端与具体产品之间的耦合度,下面通过类图来帮助大家更好地理清建造者模式中类之间的关系。

 

 

介绍完了建造者模式的具体实现之后,让我们总结下建造模式的实现要点:

1. 在建造者模式中,指挥者是直接与客户端打交道的,指挥者将客户端创建产品的请求划分为对各个部件的建造请求,再将这些请求委派到具体建造者角色,具体建造者角色是完成具体产品的构建工作的,却不为客户所知道。

2. 建造者模式主要用于“分步骤来构建一个复杂的对象”,其中“分步骤”是一个固定的组合过程,而复杂对象的各个部分是经常变化的(也就是说电脑的内部组件是经常变化的,这里指的的变化如硬盘的大小变了,CPU由单核变双核等)。

3. 产品不需要抽象类,由于建造模式的创建出来的最终产品可能差异很大,所以不大可能提炼出一个抽象产品类。

4. 在前面文章中介绍的抽象工厂模式解决了“系列产品”的需求变化,而建造者模式解决的是 “产品部分” 的需要变化。

5. 由于建造者隐藏了具体产品的组装过程,所以要改变一个产品的内部表示,只需要再实现一个具体的建造者就可以了,从而能很好地应对产品组成组件的需求变化。

 

5、原型模式(Prototype Pattern

上面代码实现的浅拷贝的方式,浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串,而深拷贝是对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。

介绍完原型模式的实现代码之后,下面看下原型模式的类图,通过类图来理清原型模式实现中类之间的关系。具体类图如下:

 

原型模式的优点有:

1. 原型模式向客户隐藏了创建新实例的复杂性

2. 原型模式允许动态增加或较少产品类。

3. 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。

4. 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构

原型模式的缺点有:

6. 每个类必须配备一个克隆方法

7.  配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

6、适配器模式

 

1、类的适配器模式:

 

2、对象的适配器模式

 

 

 

 

类的适配器模式:

优点:

· 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”

· 可以重新定义Adaptee(被适配的类)的部分行为,因为在类适配器模式中,Adapter是Adaptee的子类

· 仅仅引入一个对象,并不需要额外的字段来引用Adaptee实例(这个即是优点也是缺点)。

缺点:

· 用一个具体的Adapter类对Adaptee和Target进行匹配,当如果想要匹配一个类以及所有它的子类时,类的适配器模式就不能胜任了。因为类的适配器模式中没有引入Adaptee的实例,光调用this.SpecificRequest方法并不能去调用它对应子类的SpecificRequest方法。

· 采用了 “多继承”的实现方式,带来了不良的高耦合。

对象的适配器模式

优点:

· 可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则”(这点是两种实现方式都具有的)

· 采用 “对象组合”的方式,更符合松耦合。

缺点:

· 使得重定义Adaptee的行为较困难,这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

 

7、桥接模式

看完桥接模式的实现后,为了帮助大家理清对桥接模式中类之间关系,这里给出桥接模式的类图结构:

 

 

三、桥接模式的优缺点

介绍完桥接模式,让我们看看桥接模式具体哪些优缺点。

优点:

把抽象接口与其实现解耦。

抽象和实现可以独立扩展,不会影响到对方。

实现细节对客户透明,对用于隐藏了具体实现细节。

缺点: 增加了系统的复杂度

四、使用场景

1.我们再来看看桥接模式的使用场景,在以下情况下应当使用桥接模式:

2.如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。

3.设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。

4.需要跨越多个平台的图形和窗口系统上。

5.一个类存在两个独立变化的维度,且两个维度都需要进行扩展。

 

41、unity程序方面优化  

(1)务必删除脚本中为空或不需要的默认方法;   

(2)只在一个脚本中使用OnGUI方法;   

(3)避免在OnGUI中对变量、方法进行更新、赋值,输出变量建议在Update内;   

(4)同一脚本中频繁使用的变量建议声明其为全局变量,脚本之间频繁调用的变量或方法建议声明为全局静态变量或方法;   

(5)不要去频繁获取组件,将其声明为全局变量;   

(6)数组、集合类元素优先使用Array,其次是List;   

(7)在不使用时脚本禁用之,需要时再启用;

(8)可以使用Ray来代替OnMouseXXX类方法;  

(9)需要隐藏/显示或实例化来回切换的对象,尽量不要使用SetActiveRecursively或active,而使用将对象远远移出相机范围和移回原位的做法;   

(10)尽量少用模运算和除法运算,比如a/5f,一定要写成a*0.2f。  

(11)对于不经常调用或更改的变量或方法建议使用Coroutines & Yield;   

(12)尽量直接声明脚本变量,而不使用GetComponent来获取脚本; iPhone   

(13)尽量使用整数数字,因为iPhone的浮点数计算能力很差;   

(14)不要使用原生的GUI方法;   

(15)不要实例化(Instantiate)对象,事先建好对象池,并使用Translate“生成”对象

 

42:开发过程中与美术人员如何对接工作

美术人员输出了UI资源,原画,特效等传到ftp通知程序具体路径,程序从FTP拷贝资源到UnityUI资源文件夹,为了版本一致,程序同学可能需要对它进行重命名,才用上了一张新资源。

 

43、如何降低unity在手机端的占用?

资源分配要合理,了解unity的内存机制(资源从加载到释放的内存分配及释放方式),不用的资源及时释放掉,对贴图进行优化,比如android采用ETC格式,然后将贴图的透明通道进行分离,关掉Mipmap,这都是从资源的角度。再从代码的角度来考虑,尽可能的避免使用Unity自带GUI,避免GUI.Repaint过度的GC Allow。尽可能少的使用foreach,因为每次foreach都会产生一个Enumerator(约16B的内存分配),尽量改为for,lambda表达式使用不当会造成内存泄露(内存泄露就是指未释放不再使用的内存)。尽量少使用linQ语句。控制StartCoroutine的次数,开启一个协程,至少分配37B的内存,coroutine实例21B,Enumertor16B。使用stringBuilder代替string。对一些组件进行缓存比如transform,gameObject等。

 

44、简述c#怎么和unity交互并有哪些现有的解决方案?

 通过LuaInterface和Luanet两个dll,LuaState中包含DoFile和DoString方法。

 

45、什么是DC?DrowCall高了有什么影响?如何降低DrowCall?

CPU每准备数据通知GPU进行一次渲染就是一次DrawCall。DrawCall越高对显卡的负担就越大,同时DrawCall过高对GPU和CPU占有都会提高,GPU需要频繁的进行渲染工作,CPU需要不断的计算去给GPU提供绘制的图元数据,有可能会造成游戏卡顿等问题。降低DC的方法1)动态批处理,如果动态物体共用相同的材质,那么Unity会自动对这些物体进行批处理,你可以在buildSetting中对其进行设置2)静态批处理,只要物体不移动,并且拥有相同的材质,那么就可以进行静态批处理。因此静态批处理比动态批处理更加有效,我们可以把静止的物体(在游戏中永远不会移动、旋转和缩放)标记为静态的,使用静态批处理操作需要2倍的内存开销来存储合并后的几何数据。3)调整渲染顺序,说白了,就是同渲染数据相同的物体先渲染完,比如说A,C的材质相同,B的材质与A,C不同,如果先渲染A再渲染B再渲染C就是3个DC,但如果先渲染A,再渲染C,最后渲染B就是两个DC。4)再有就是同一界面尽量使用同一图集。其实说了这么些个方法,总结来说很简单,既然要优化DC,我们就是要从产生DC的原理上入手,我们所有的一切操作做的都是一件事儿,就是能一次渲染完成的事儿就没必要多渲染几遍。

 

46如何实现代码的热更新?简述方法及原理?

通过C#与lua的互相调用,lua是一种小巧的语言,可以在程序运行是编译和执行,就所我们通过lua来实现C#的热更。

 

47、List<T>与IList<T>的区别是什么?

首先IList 泛型接口是 ICollection 泛型接口的子代,并且是所有泛型列表的基接口。
  它仅仅是所有泛型类型的接口,并没有太多方法可以方便实用,如果仅仅是作为集合数据的承载体,确实,IList<T>可以胜任。

 不过,更多的时候,要对集合数据进行处理,从中筛选数据或者排序。这个时候IList<T>就爱莫能助了。

48、游戏制作中Atlas的作用?

把所有图片做成一张大图,节省内存. 但是要注意图集粒度, 什么样的图片合成一张图集, 对内存效率有很大影响.

49、游戏场景中可能同时存在哪些Camera?他们一般怎么设置?以及起到的作用?

主相机,主相机渲染主场景用透视摄像机

   UI相机,UI相机渲染UI用正交相机。

   深度相机,根据需求设置的, 可以通过设置深度保证某些对象一直在最前面显示

50、U3D的协程和c#的线程的区别?

线程和协程一样共享堆,而不共享栈,线程与协程最本质的区别是,线程在宏观上是同时进行的,而协程同一时刻只能执行一个协程,只不过协程可以通过某些条件暂时挂起不继续执行,等带达到条件要求之后在继续执行,从某种意义上来讲,协程可以看成是伪线程,本质上来讲,它们是包含关系,一个线程中可以包含多个协程

51、string和StringBuilder的区别是什么?是什么导致了这些区别?

String是字符串常量。

    String类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就要不断的生成新的对象,所以效率很低,建议在不断更改String对象的地方不要使用String类型。

    StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。这一点我们平时使用中也许都知道,连接操作频繁的时候,使用StringBuilder对象。

52、描述TCP建立连接的三次握手过程

 

 

53、编程求二叉树的高度

class Tree

    {

        public string Value;

        public Tree Left;

        public Tree Right;

}

public static int CalTreeHeight(Tree root)

        ‘ data-report-query=’spm=1001.2101.3001.9621’>立即使用

赞(0)
未经允许不得转载:上海聚慕医疗器械有限公司 » fsm包是什么牌子Unity全面面试题汇总

登录

找回密码

注册