Unity全面面试题汇总

Unity新手入门进阶之路(群内各种入门资源资料大神):721559786 (加群下载插件资源学习资料)

1、什么是协同程序Unity全面面试题汇总_第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 ls = new List(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

叉积的模为 x1 * y2 - x2 * y1 = |a||b| sin

点乘公式:

对于向量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与IList的区别是什么?

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

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

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)

        {

            if (root == null)

                return 0;

            return Math.Max(CalTreeHeight(root.Left), CalTreeHeight(root.Right)) + 1;

54、UI中如何有效的减少Drawcall

1. 使用Dynamic Batching。Static Batching

  2.通过把纹理打包成图集来尽量减少材质的使用。

  3.尽量少的使用反光,阴影,防止物体多次渲染。

  4.高级特性Shader降级为统一的低级特性的Shader

55、Animation中Curves如何使用

  切换至Curves后,可以添加Property,通过选择不同的属性,在视图中添加Key点修改函数曲线,从而实现物体的各种动画效果、颜色渐变等

56、GameObject上的多个Compone如何使用通用的方法获取开放的属性,并用UGUI形式展示出来

  对于系统组件, 由于都继承于Component, 可以使用 Component[] objs =  gameObject.GetComponents(), 这种形式获取到对象身上的所有组件.

之后可以遍历数组, 配合switch(类型甄别), 之后使用反射获取这个组件中的所有开放属性.

得到后, 使用Unity的text展示即可.

57、对于网络中角色操作的同步,场景中NPC的同步的看法。

  角色:可以考虑帧同步的方案,大部分游戏逻辑都在客户端实现,服务器只负责广播和验证操作

npc:可以考虑状态同步,只同步npc的状态行为,ai逻辑,技能逻辑,战斗计算都由服务器计算,然后将运算结果同步给客户端,客户端只需要接受服务器传过来的状态变化然后更新本地的动作状态,buff,位置等待;

58、UI血条优化方案

  血条优化(额外多写点)

1.如果是用UGUI开发的,当头顶文字数量较多时,确实很容易引起性能问题,可以考虑从以下几点入手进行优化:

2.尽可能避免使用UI/Effect,特别是Outline,会使得文本的Mesh增加4倍,导致UI重建开销明显增大;

3.拆分Canvas,将屏幕中所有的头顶文字进行分组,放在不同的Canvas下,一方面可以降低更新的频率(如果分组中没有文字移动,该组就不会重建),另一方面可以减小重建时涉及到的Mesh大小(重建是以Canvas为单位进行的);

4.降低移动中的文字的更新频率,可以考虑在文字移动的距离超过一个阈值时才真正进行位移,从而可以从概率上降低Canvas更新的频率。

59、UI动画实现方案:

1.帧动画方式实现: 将UI动画每一帧都制作成贴图, 然后在UI身上挂载脚本, 通过修改UI身上的图片来达到动画效果

2.使用特定的shader, 这个需要专门shader的支持

3.如果你指的UI的Transform动画, 那么可以使用插值计算(对位置, 旋转和缩放), 也可以使用市面常用的插件动画, 如dotween

60、NGUI优化:

1.根据各个UI控件的设计安放Panel,隔开DrawCall

  以前项目中发现NGUI的UIPanel.LateUpdate函数的CPU开销非常大,是合并了太多的DrawCall所致,尤其是将运行时会运动变化的UI控件和静止不变的UI控件的DrawCall合在了一起。当一个UI控件(UIWidget)的位置、大小或颜色等属性发生变化时,UIPanel就需要重建这个控件所用的DrawCall,某些情况下还要重建Panel上的所有DrawCall。它要重新计算这个DrawCall上所有控件的顶点信息,包括顶点位置、UV和颜色等,因此将UI控件分组,将一段时间内会发生变化的控件——比如怪物头顶的血条和伤害跳字放在同一个Panel上,其余不变化的控件就放在别的Panel上。两类控件就被隔开到不同的DrawCall不同的Panel中,DrawCall重建的时候,就不需要遍历那些没有变化的控件。

2.优化锚点内部逻辑,使其只在必要时更新

  在上一点优化了Panel的DrawCall重建效率之后,我们发现NGUI锚点自身的更新逻辑也会消耗不少CPU开销。即使是在控件静止不动的情况下,控件的锚点也会每帧更新(见UIWidget.OnUpdate函数),而且它的更新是递归式的,使CPU占用率更高。因此我们修改了NGUI的内部代码,使锚点只在必要时更新。一般只在控件初始化和屏幕大小发生变化时更新即可。不过这个优化的代价是控件的顶点位置发生变化的时候(比如控件在运动,或控件大小改变等),上层逻辑需要自己负责更新锚点。

3. 降低贴图素材分辨率

这一招是必然会显著降低美术品质的,美术立马会发现画面变得更模糊,因此一般不到程序撑不住的时候不会采用。

 

 

 

 

61、UI框架

 

 

 

 

 

 

62.MVC

1.Frame的组成结

(1)视图层(View)

(2) 控制层(Control)   

(3)数据层(Model)

整个Frame是由这三个部分组成,每一层管理属于自己的逻辑,核心思想是游戏逻辑和UI逻辑独立开。目前遇到的项目工程大多数View和Control逻辑都写在一起,这样后期修改和维护效率会很低,因为耦合性很高而View又是经常要修改的地方,所以造成Control方面的逻辑也不得不修改。

2.视图层(View)

说明:视图层为展现给玩家的逻辑层(包括UI的表现和场景内的表现),它位于整个框架逻辑的最顶层。

注意:View层中的任何逻辑都不可直接对Model层中的数据进行修改,可以看作它Model层拥有只读权限。

功能:

(1)负责管理自己所属面板(或场景)的逻辑。

(2)负责数据在视图层的刷新。

(3)实现用户界面按钮的操作逻辑。

3.控制层(Control)

说明:整个Frame中唯一一个对Model层拥有读写权限的逻辑层。

注意:Control层为整个框架的核心逻辑,请务必按照框架格式要求实现自己所需的功能。

功能:

(1)判断玩家操作是否符合条件,若符合执行指令(网游即向服务器发送请求)。

(2)根据逻辑(服务器返回数据)对Model层中的数据进行修改。

(3)回调View层中的方法通知操作完成或失败。

4.数据层(Model)

说明:整个Frame的数据中心,位于框架逻辑结构的最底层注意:Model层对每个逻辑层

开放的权限不同,View层对有只读权限,Control层对其有读写权限。

功能:   

(1)存放各个功能模块的相关数据。

(2)提供数据修改的方法。

62.pureMVC

PureMVC框架的目标很明确,即把程序分为低耦合的三层:Model、View和Controller。它们合称为PureMVC框架的核心,由Facade统一管理。

        一、Model保存对Proxy对象的引用,Proxy负责操作数据模型,与远程服务通信存取数据。

        二、View保存对Mediator对象的引用。由Mediator对象来操作具体的视图组件(View Component,例如Flex的DataGrid组件),包括:添加事件监听器,发送或接收Notification ,直接改变视图组件的状态。

        三、Controller保存所有Command的映射。Command可以获取Proxy对象并与之交互,通过发送Notification来执行其他的Command。

1.  Proxy是用来对数据模型进行查询、插入、更新、删除等操作的类。操作完成后,它就会发送Notification,也就是通知,告诉其它两个层我已经完成工作了。

2.Mediator是负责管理用户界面,与用户进行交互操作的。如:给Button添加事件,当用户点击按钮时,发送Notification,告诉Controler我们执行什么样的操作。比如这是一个登录的按钮,那么Mediator就会告诉发送通知给Controler,告诉它要执行登录操作。

3.Command可以获取Proxy对象并与之交互,通过发送Notification来执行其他的Command

 

 

 

63、行为树

包含四种节点。分别是action节点、组合节点、条件节点和修饰节点。

(1) Composites 组合节点,主要包含:Sequence顺序条件,Selector选择条件,Parallel平行条件以及他们之间相互组合的条件。

(2) Decorator 装饰节点,该节点只包含一个子节点,并未该子节点添加一些特殊的功能,如让子节点循环操作或者让子task一直运行直到其返回某个运行状态值,或者将task的返回值取反等等

(3) Actions 行为节点,行为节点是真正做事的节点,其为叶节点。Behavior Designer插件中自带了不少Action节点,如果不够用,也可以编写自己的Action。对于自己编写的行为状态,一般都是需要添加自己的

(4) Conditinals 条件节点 ,用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。

64、MVC框架开发UI界面

UI一般需要通过多次迭代开发,直到用户体验近似OK。另外至关重要的是, 我们想尽快加速迭代的过程。使用MVC模式来进行设计,已经被业界证明了是可以解耦屏幕上的显示,如何控制用户的输入对显示的改变,以及如何根据应用的状态进行改变。MVC模式提供了以下好处:

(1) 可以修改UI的外观,而不用修改一行代码

(2) 在不同的组件里面可以共享同一套逻辑代码,用来创建复杂的视图;

(3) 可以用很小的代价来改变UI的实现,比如正在使用NGUI , 但将来会切换成 UGUI

Model

  传统的MVC中的Model,我们都很熟悉:

        (1) 不保存任何View的数据或者View的状态

        (2) 只能被Controller或者其他Model访问

        (3) 会触发事件来通知外部系统进行处理和变更

这里的Model是使用Plain Old C# Objects(POCOs)来实现的,就是不依赖于任何外部库的C#代码。这是例子中对于PlayerModel的链接地址,它表示了Player的数据,包括HitPoints,XP和level,使用两个属性来进行访问。我们可以增加XP点,而Model则会raise一个XPGained事件。当获得升级的经验时,Level属性会更新,并raise一个LevelUp事件。

View

概念上的view一般就是在屏幕上渲染的东西。View的职责包括:

(1) 处理用户绘制元素的reference,包括纹理,特效等

(2) 播放动画

(3) 布局

(4) 接受用户输入

在代码这个特定例子中,View的实现使用了NGUI,所以它只是Unity工程中的一个Prefab。

但这个实现细节需要解耦。想了解更多学术上的知识的话,还可以看下Passive View。View不知道工程中其他部分的任何事情,无论是数据还是逻辑。这样其他的代码必选显式地告诉View显式什么,播放什么动画等等。

Controller

Controller是连接Model和View的桥梁。它会保存View的状态,并且根据外部事件来更新View的状态:

(1) 持有View所需要的应用状态

(2) 控制View的流程

(3) 根据状态show/hides/activates/deactivates/updates View或者View的某些部分。如controller可临时将攻击按钮Distable掉,因为此时攻击处于冷却状态,冷却状态一过,controller会re-enable这个按钮。

(4) load/Instantiate需要的assets,比如显示particles, 动态改变sprites等

(5) 处理用户在View中触发的事件,比如用户按下了一个按钮;处理Model触发的事件,比如player获得了XP并触发了升级,所以controller就更新了View中的Level Number

这就是MVC模式中定义的三个基本元素。但在这个例子中,我们加入了另外一个中间层来进一步解耦NGUI的View实现,称之为:

ViewPresenter

一个ViewPresenter位于View和Controller之间,作为一个接口存在,暴露了对于一个View来说是普适的操作集合,无论View本身是基于什么库来实现的(NGUI,UGUI等)

比如一个游戏中的按钮,一般来说都有以下功能集:

(1) 设置按钮label中的文字

(2) 修改按钮的背景图

(3) enable/disable用户输入

(4) 当用户点击按钮时,进行Notify

这些都是与UI具体实现无关的操作,在任何一个UI toolkit中都能找到这些操作。ViewPresenter实现为一个MonoBehaviour被Attach到NGUI View的Prefab上,所以可以在Controller中通过GameObject.GetComponent来得到它来进行功能调用。由于ViewPresenter是游戏代码和UI代码的桥梁,所以它不能完全独立于屏幕渲染的底层实现。在这个例子中,ViewPresenter需要持有NGUI Widgets(比如UIButton,UILabel等)的引用,用来于它们进行交互。其实ViewPresenter实际上是一个适配器模式(Adapter pattern)的实现:  我们额外创建了定制的接口来对应用进行访问。这些引用必须在Inspector或者其他代码中进行设置。

65、FSM有限状态机

FSM是一种数据结构,它由以下几个部分组成:

1,内在的所有状态(必须是有限个)

2,输入条件

3,状态之间起到连接性作用的转换函数

·为什么要用FSM?

  因为它编程快速简单,易于调试,性能高,与人类思维相似从而便于梳理,灵活且容易修改

FSM的描述性定义:

一个有限状态机是一个设备,或是一个模型,具有有限数量的状态。它可以在任何给定时间根据输入进行操作,使得系统从一个状态转换到另一个状态,或者是使一个输出或者一种行为的发生,一个有限状态机在任何瞬间只能处于一种状态。

State 状态基类定义了基本的Enter,Update,Exit三种状态行为,通常在这三种状态行为的方法里会写一些逻辑。每个State都会有StateID(状态id,可以是枚举等),FSMControl(控制该状态的状态控制器的引用),Check方法(用来进行状态判断,并返回StateID,通过FSMControl驱动)

FSMControl包含了一下FSMMachine,封装层。

FSMMachine驱动它的State列表,Update方法调用当前State的Check方法来获得StateID,当currentState的Check方法返回的StateID和当前StateID不同,则切换状态。

这是一个简单的FSM状态机系统,根据需要自己写个Control继承FSMControl来驱动状态。因为Check是State的职责,所以每一个不同对象的行为如Human的Idle和Dog的Idel区分肯定也不同。因此需要分别去写HumanIdleState和DogIdleState。如果还有Cat,Fish,可想而知代码量会有多么庞大。

因此我将FSMControl抽象为一个公共基类,把State的Check具体实现作为FSMControl的Virtual方法。这样在IdleState里的Check方法就不用写具体的状态切换判断逻辑,而是调用它FSMControl子类(自己写的继承自FSMControl的Control类)的重写方法

这样每次添加的新对象只要有Idle这个状态,就可以用一个公用的StateIdle,状态切换的逻辑差异放在Control层

 

 

 

 

 

 

 

66.Render 的作用?描述 MeshRender 和 SkinnedMeshRender 的关系与不同 

 

Mesh 就是指模型的网格(同名组件是用于调整网格属性的), MeshFilter 一般是用于获得模型网格的组件,而 MeshRender 是用于把网格渲染出来的组件

67、 NGUI Button 怎样接受用户点击并调用函数 , 具体方法名称是什么

OnClick()

主要是在 UICamera 脚本中用射线判断点击的物体并通过 SendMessage 调用OnClick() OnPress() 等函数,可以说 NGUI 的按钮是通过发消息这个方式调用的。

68、Socket封包、拆包、粘包

接触Socket通信的过程中,遇到了各种有关数据包的问题。这里做一下记录。

一、Socket粘包

1、什么是粘包? 
答:顾名思义,其实就是多个独立的数据包连到一块儿。

2、什么情况下需要考虑粘包? 
答:实际情况如下:

1、如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题。

2、如果发送的数据无结构,比如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包。

3、如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构: 
1)”good good study” 
2)”day day up” 
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是”good good studyday day up” 这样接收方就傻了,因为协议没有规定这么奇怪的字符串,所以要把它分包处理,至于怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。

所以说:Tcp连续发送消息的时候,会出现消息一起发送过来的问题,这时候需要考虑粘包的问题。

3、粘包出现的原因 (在流传输中,UDP不会出现粘包,因为它有消息边界。)

1、发送端需要等缓冲区满才发送出去,造成粘包 (发送端出现粘包)

2、接收端没有及时接收缓冲区包数据,造成一次性接收多个包,出现粘包 (接收端出现粘包)

4、解决粘包

1、缓冲区过大造成了粘包,所以在发送/接收消息时先将消息的长度作为消息的一部分发出去,这样接收方就可以根据接收到的消息长度来动态定义缓冲区的大小。(这种方法就是所谓的自定义协议,这种方法是最常用的)

2、对发送的数据进行处理,每条消息的首尾加上特殊字符,然后再把要发送的所有消息放入一个字符串中,最后将这个字符串发送出去,接收方接收到这个字符串之后,再通过特殊标记操作字符串,把每条消息截出来。(这种方法只适合数据量较小的情况)

注:要记住这一点:TCP对上层来说是一个流协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片的,其间是没有分界线的,也就是没有包的概念。所以我们必须自己定义包长或者分隔符来区分每一条消息。

二、Socket的封包、拆包

1、为什么基于TCP的通信程序需要封包、拆包? 
答:TCP是流协议,所谓流,就是没有界限的一串数据。但是程序中却有多种不同的数据包,那就很可能会出现如上所说的粘包问题,所以就需要在发送端封包,在接收端拆包。

2、那么如何封包、拆包? 
答:封包就是给一段数据加上包头或者包尾。比如说我们上面为解决粘包所使用的两种方法,其实就是封包与拆包的具体实现。

69、TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保   证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
  UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

 

70、Socket 客户端 队列 的问题

项目中采用了socket通信,通过TCP发送数据给服务器端,因为项目需要,要同时开启大量的线程去发送不同的数据给服务器端,然后服务器端返回不同的数据。由于操作频繁,经常会阻塞,或没有接收到服务器端返回的数据;

因此考虑到使用一个队列:将同一ip下的数据存入一个队列中,通过队列协调发送;当第一条数据发送出去没有收到服务器端返回的数据时,让第二条数据插入队列中排队,当第三条数据也发送出来后,继续排队,以此类推;
如果当第四条数据发出来的时候,存入队列中,第一条数据收服务器端返回数据后,队列中的第二条第三条数据就扔掉,直接发送第四条数据

你可能感兴趣的:(Unity面试题,Unity,面试题)