以下问题与答案来自于真实求职,如有疑问请与本博客作者联系

此文章并非盈利性质,由作者单独整理而成,若存在侵权行为请联系博主进行更正,在极其必要情况下将删除该文章,在借鉴或者参考部分会进行特殊标明,本文章与Unity相关部分后期将与哔哩哔哩同步,如若二次使用或者更改,请与本学习网站所有者,即本人联系,七鳄学习格所有者拥有对本网站所有内容的最终决定权和解释权。—-2023年1月5日(七鳄学习格)

🎬 博客主页:https://blog.gmcj0816.top

🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📆 此文章为原创,并通过Typora编辑器撰写✨

No1.值类型和引用类型的区别(答案为原创)

值类型包括:数值类型,结构体,bool型,用户定义的结构体,枚举,可空类型。
值类型的变量直接存储数据,分配在托管栈中。变量会在创建它们的方法返回时自动释放,例如在一个方法中声明Char型的变量name=’C’,当实例化它的方法结束时,name变量在栈上占用的内存就会自动释放

引用类型包括:数组,用户定义的类、接口、委托,object,字符串,null类型,类。
引用类型的变量持有的是数据的引用,数据存储在数据堆,分配在托管堆中,变量并不会在创建它们的方法结束时释放内存,它们所占用的内存会被CLR中的垃圾回收机制释放

No2.Unity内置函数执行顺序

1.Awake(用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用【此函数不可用来执行协程】)
2.OnEable(只有在对象是激活(active)状态下才会被调用,这个函数只有在object被启用(enable)后才会调用)
3.Start(脚本实例被启用时调用)
4.FixedUpdate(固定时间调用,常常用于物理逻辑的处理中使用,编辑器默认为50fps)
5.Update(每帧调用,默认为0.02)
6.LateUpdate(LateUpdate会在Update结束之后每一帧被调用,常用于第三人称视角相机跟随)
7.OnBecameVisible(用于当物体在/进入摄像机会调用一次)
8.OnBecameInVisible(用于当物体不在/离开摄像机会调用一次)
7.OnGUI(每帧会被调用多次(一般最低两次),布局Layout和Repaint事件会首先处理,接下来处理的是是通过
Layout和键盘/鼠标事件对应的每个输入事件)
8.OnApplicationPause(这个函数将会被调用在暂停被检测有效的在正常的帧更新之间的一帧的结束时)
9.OnDisable(当行为变为非启用(disable)或非激活(inactive)时调用)
10.OnDestroy(物体删除时调用)
11.OnApplicationQuit(这个函数在应用退出之前的所有游戏物体上调用)

No3.链表和数组的区别,每个的优缺点(答案为原创)

1.区别:
(1)由于数组的存储内存时连续分配的,当数组长度在使用过程中增加长度,会导致数组变量被覆盖,所以数组的元素是固定的,而链表的存储方式是链式存储,组成链表的结点个数可以根据实际需要进行增减
(2)数组的存储单元在数组定义时就需要分配,而链表是由结点组成的,前一个结点指向后一个结点存储地址,再程序的执行过程中结点的存储单元可以动态分配
(3)数组中的元素顺序关系由元素在数组中的位置(即下标)确定,链表中的结点顺序关系由结点所包含的指针来体现
(4)在实际的使用过程中难免遇到便是对数据的增删操作,对于在数组中插入数据则需要对从插入位置开始后面的数据后移,而链表由结点组成,每一个结点包括本身信息的数据域和指向下一个结点存储地址信息的引用域,当向某n位置插入数据时,则仅需将n-1位置指向新插入的结点,将n结点的next指向原先的结点,当删除n位置结点时,则仅需将n-1指向n+1即可
2.数组和链表的优缺点
(1)优点
数组:随机访问性强,查找速度快
链表:插入删除速度快,内存利用率高,不会浪费内存,大小不固定,扩展灵活
(2)缺点
数组:插入和删除效率低;可能浪费内存;内存空间要求高,必须有足够的连续内存空间。;数组大小固定,不能动态拓展
链表:不能随机查找,必须从第一个开始遍历,查找效率低。

No4.Unity为什么能实现跨平台

Unity 的跨平台即是基于Mono,开发时的写的各种类型的脚本语言(C#、JavaScript…) 都会被 Mono 的编译器编译为 IL,然后在各个平台上的 Mono 虚拟机上运行,这样就实现了跨平台的机制

No5.面向对象编程的三个特点

c#面向对象编程的三大特性:继承,封装,多态
继承:是指在确定系统的某一部分内容时,应考虑到其它部分的信息及联系都在这一部分的内部进行;
封装:让某个类型的对象获得另一个类型的对象的属性和方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行;
多态:对于同一个行为,不同的子类对象具有不同的表现形式多态存在的3个条件:【1)继承 2)重写 3)父类引用指向子类对象】;

No6.三次握手和四次挥手

  • 1.三次握手

(1)第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_Send 状态。
(2)第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
(3)第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。

  • 2.四次挥手

(1)第一次挥手:主机1(可以是客户端也可以是服务端)向主机2发送一个终止信号(FIN),此时,主机1进入FIN_WAIT_1状态,他没有需要发送的数据,等待着主机2的回应;
(2)第二次挥手:主机2收到了主机l发送的终止信号(FIN),向主机1回应一个ACK,收到ACK的主机1进入FIN_WAIT_2状态;
(3)第三次挥手:在主机2把所有数据发送完毕后,主机2向主机]发送终止信号(FIN),请求关闭连接;
(4)第四次挥手:主机1收到主机2发送的终止信号(FIN),向主机2回应ACK 然后主机1进入TIME_WAIT状态(等待一段时间,以便处理主机2的重发数据)。主机2收到主机1的回应后,关闭连接.至此,TCP的四次挥手便完成了,主机l和主机2都关闭了连接。

No7.装箱和拆箱

装箱:值类型转换为引用类型
拆箱:引用类型转换为值类型
需要注意:任何称之为类的类型都是引用类型,使用class修饰,所有值类型都成为结构或者枚举,用struct/enum修饰

No8.碰撞器和触发器【基础】

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

No9.C#中==与equal的区别

(1)在C#中equal和==都可以判断两个变量是否相等,其实equal是Object中的一个虚方法,允许任何类重写将其实现,而==只是一个运算符;
(2)Equals一般在子类中重写后用于比较两个对象中内容是否相同,==在没有运算符重载的前提下时,引用类型用于比较地址;值类型用于比较值是否相同;
(3)运算效率不同,一般Equals没有==效率高,因为一般Equals比较的内容比==多

No10.执行效率问题

这两种方式哪一种效率更高一些呢

这里主要应用到的便是链表和数组的知识点,这里不难看出,实现的功能是相同的,但是很明显第二种效率更高
这是因为List的本质也属于数组,只不过是在Add执行下不断扩容自己的数组长度,但是这所造成的便是对性能的不断消耗,而使用数组直接创建一个10000的数组长度,然后把数据直接挨个放入的里面,这也就减少了一步内存长度扩容的操作,所以效率会相对高一些。

No11.浅拷贝与深拷贝的区别

浅拷贝:
只复制对象的引用地址
两个对象指向同一内存地址,修改其中一个另一个也会随之变化
深拷贝:
将对象和值赋值过来,两个对象修改其中任意值都不会影响对方

比如引用对象A和引用对象B
让A = B,就是浅拷贝,此时A、B的引用地址相同,改A中内容,B也变
如果想要深拷贝,简单处理就是new(包括对象中的成员)

No12.try,catch,final的执行顺序

其实try,catch,final的执行顺序大致可以分为三种情况
(1)try没有抛出异常,这种情况下则会直接执行final中的内容
(2)try抛出异常,并且catch捕捉到此错误,try中的内容将不执行,直接执行catch中的内容,然后执行final中的代码块内容
(3)try抛出异常,并且catch未捕捉到此错误,则try和catch的代码块都不会被执行,直接执行final

No13.DrawCall

1.什么是DrawCall?==>每次CPU准备渲染相关数据并通知GPU的过程称为一次DrawCall
2.DrawCall为什么会影响游戏运行效率?==>如果DrawCall次数较高,意味着CPU会花费更多的时间准备渲染数据,这会进行更多的计算,进而影响游戏的运行效率
3.如何减少DrawCall?==>2D和UI层面:打图集,并且注意面板中不同图集图片的层级不要穿插,3D模型层面:利用动态批处理和静态批处理,尽量不使用实时光照和实时阴影

No14.Unity路径问题

在Unity中自带了四个路径

  • dataPath是包含游戏数据文件夹的路径,一般不会去手动读写这个目录——其路径为Asset/
  • streamingAssetsPath返回的是流数据的缓存目录,适合用来存储一些外部数据文件用于读取StreamingAssets文件夹内的东西不会被加密,放进去什么就是什么,所以不要直接把数据文件赤裸裸地放到这个目录下。一般不会去手动将数据写入这个目录—此文件夹为只读文件夹>,因此在热更新中使用,会首先在此文件中查找,来锁定下一步需要更新的资源。
  • persistentDataPaht返回的是一个持久化数据存储目录。由于此文件夹是可读可写的,因此一般热更新下载的资源存放到此处
  • temporaryCachePath返回一个临时数据缓存目录。当应用程序发布到IOS和Android平台,这个路径也会指向一个公共的路径。应用更新、覆盖安装时,这里的数据都不会被清除,此文件为可读可写的

No15.Unity中协程的原理

Unity中的协同程序分为两部分:1.协程函数本体(迭代器函数)2.协程调度器(协程管理器)
协程利用迭代器函数的分步执行的特点,协程调度器对迭代器函数们进行统一管理,根据迭代器函数的返回值来决定下一次执行函数逻辑的时间点,从而实现逻辑分时分步执行的目的

No16.Unity底层如何处理C#代码

主要有两种方案,分别是Mono和IL2CPP

Unity之所以能够跨平台,是因为Mono基于.Net技术构建,因为使Unity能够在PC,Xbox等平台支持

而IL2CPP是为了解决Mono .Net的问题而开发出的新技术,这里主要原因便是iOS不支持.Net,并且Mono无法应用到Web平台,这就造成了Unity有局限性,因此使用IL2CPP,其基本原理就是按照传统的基于.Net的c#开发,然后编译成CLR or IL,再通过l2cpp工具,将CLR/IL字节码转成静态的C++代码,最终编译成目标的native机器码。

No17.Unity多线程需要注意

1.只能从主线程访问Unity相关组件、对象以及UnityEngine命名空间中的绝大部分内容
2.如果多线程中要和Unity主线程同时修改一些数据可以通过lock关键词加锁

No18.泛型约束

1.值类型约束 T:struct
2.引用类型约束 T:class
3.公共无参构造约束 T:new()
4.类约束 T:类名
5.接口约束 T:接口名
6.另一个泛型约束 T:U

No19.闭包

闭包是指有权访问另一个函数作用域中的变量的函数所以闭包一般都是指的一个函数,创建这种特殊闭包函数的方式往往是在一个函数中创建另一个函数

No20.内存泄漏
内存泄漏指的就是对象超过生命周期后而不能被GC回收,一般指不会再使用的引用对象由于某些操作而不能被GC垃圾回收,而一直占用着内存

常见的内存泄漏有:
1.静态引用
2.不使用的引用对象没有置null,一直被引用
3.文件操作时,没有使用using或者没有进行Dispose()
4.委托或事件注册后没有解除注册(有加就有减)

No21.transform.forward和Vector3.forword的区别

Vector3.forword始终时(0,0,1)
可以认为是世界坐标系的Z轴朝向

transform.forword是当前物体的局部坐标系的Z轴在世界坐标系下的朝向
可以认为是物体自己的Z轴朝向

No22.如何优化UI(基于UGUI)

  • 性能上
    1.打图集,将同一画面的图片放入一个图集中,目的是减少DrawCall
    2.面板中的图片和文字尽量不要交叉,因为这样会产生多余的DrawCall
    3.取消勾选不必要的射线检测,UI组件上的
    4.减少透明图片的重叠使用

  • 内存上
    1.大图尽量使用9宫格缩放,让美术设计UI面板底图时不要过于复杂尽量是有规律的纹理和颜色变化
    2.图片的RGBA通道分离

No23.Unity使用IL2CPP打包时,我们应该注意什么?如何避免
使用IL2CPP打包时,最可能出现的问题就是代码裁剪,IL2CPP会自动将它认为不会使用的代码裁剪掉,比如我们在使用Lua开发时,其实会用到很多UnityEngine或者我们自己写的C#代码,但是这些代码并不会在引擎中直接使用,都是在Lua中使用的,此时最容易出现的问题就是代码裁剪,导致打包后出现异常和报错。

要避免IL2CPP的裁剪有3种方式,我们可以组合使用
1.设置打包时的裁剪等级
2.通过xml文件配置明确规定哪些内容不裁剪
3.在静态方法中显示调用不想被裁剪的内容

No24.Unity中的Destroy和DestroyImmediate的区别
Destroy方法:可以指定删除的延迟时间,如果第二个参数不填写,最快也会在下一帧前完成删除。也就是如果Destroy对象后马上判空,该对象不会为空。实际的对象销毁操作始终延迟到当前更新循环结束,但始终在渲染前完成

DestroyImmediate方法:会立即销毁删除对象

No25. Lua三大特性
封装:利用table进行封装
继承:利用元表和 index 模拟继承关系设置子类的元表为父类,父类的 index为父类自己,当子类身上找不到对应属性和方法时,会查找元表的__index中的内容,也就是会查找父类中的内容,通过这种方式来模拟继承
多态:子类自己去实现带:的同名方法即可

No26.string和StringBuilder如何选择
string在每次拼接时都会产生垃圾
而StringBuilder在拼接时,是在原空间中进行修改,不会产生垃圾,会自动帮助我们扩容
所以当字符串需要频繁修改拼接时,我们使用StringBuilder

No27.请说明字符串中string str = null,string str = “”,string str = string.Empty,三者的区别

str = null 在堆中没有分配内存地址
str = “” 和 string.Empty 一样都是在堆内存中分配了空间,里面存储的是空字符串
而string.Empty是一个静态只读变量

No28.委托与事件的区别

1.事件只能在方法的外部进行声明,而委托在方法的外部和内部都可以进行声明;

2.事件只能在类的内部进行触发,不能在类的外部进行触发。而委托在类的内部和外部都可触发;

No29.ref和out的区别

ref和out主要应用:当方法需要多个返回值的时候

ref传入的变量必须初始化,而out传入的参数必须在内部赋值

No30.粘包与半包

粘包:

客户端依次发送 “ Lpy”和 “ _ IS—handsome”,期望其他客户端也展示出 “ Lpy”和 “ —is_handsome”两条信息 , 但由于 Receive可能把两条信息当作一条信息处理,有可能只展示 “ Lpy_is_handsome”一条信息 (如图4-5所示)。Receive方法返回多少个数据,取决千操作系统接收缓冲区中存放的内容。

  • 粘包解决方法:

1.长度信息法是指在每个数据包前面加上长度信息。 每次接收到数据后 , 先读取表示长 度的字节 , 如果缓冲区的数据长度大千要取的字节数, 则取出相应的字节 , 否则等待下一 次数据接收。

2.固定长度法:每次都以相同的长度发送数据,

3.结束符号法:规定一个结束符号,作为消息间的分隔符 。

半包:

发送端发送的数据还有可能被拆分,如发送 “ HelloWorld”(如图4-6所示),但在接收 端调用Receive时,操作系统只接收到了部分数据,如 “ Hel ” ,在等待一小段时间后再次 凋丿廿Receive才接收到另一部分数据 “ loWorld”。

No31.点乘与叉乘

点乘求角度,叉乘求方向

点乘结果为 float 是投影长度点乘的主要作用就是,点乘求角度。

  • 点乘的方法是:

Vector3.Dot(向量A,向量B); 得到的结果是数 即 B在A上的投影长度

Unity中的叉乘 使用左手定则 确认结果向量方向

  • 叉乘方法

Vector3.Cross(tmpA, tmpB) 叉乘结果是一个向量

在同一平面内, 结果 > 0 表示 B在A的逆时针方向,, 结果 <0 表示B在A的顺式针方向, 结果 = 0表示B与A同向

点乘和叉乘的综合应用:点乘求出相差角度,叉乘求出转向,让AI朝玩家方向自动移动,转向

No32.C#中如何让自定义容器类能够使用for循环遍历?
通过在类中实现索引器实现

No33.C#中如何让自定义容器类能够使用foreach循环遍历?
通过为该类实现迭代器可以让其使用foreach遍历
传统方式:
继承IEnumerator、IEnumerable两个接口
实现其中的
1.GetEnumerator方法
2.Current属性
3.MoveNext方法
语法方式:
利用yield return语法,实现GetEnumerator方法即可完成迭代器的实现

No34.协程使用

常用的方法如下:

1.yield return 数字; 下一帧执行
2.yield return null; 下一帧执行
3.yield return new WaitForSeconds(数字); 等待指定秒后执行
4.yield return new WaitForFixedUpdate(); 等待下一个固定物理帧更新时执行
5.yield return new WaitForEndOfFrame(); 等待摄像机和GUI渲染完成后执行
6.yield break; 跳出协程

No35.C#中如何让一个类不能再被其他类所继承?
使用密封关键字sealed修饰该类

No36.C#中使用泛型的好处
1.可以为不同类型对象的相同行为进行通用处理,提升代码复用率
2.避免装箱拆箱,提升性能

No37.请说明Thread、ThreadPool、Task分别是什么?并简单说明彼此的区别
Thread是线程,可以使用他开启线程处理复杂逻辑,避免主线程卡顿
ThreadPool是线程池,他是C#为线程实现的缓存池,主要用于减少线程的创建,减少GC触发
Task是任务,他是基于线程池的优化,让我们可以更方便的控制线程

No38.Unity中动态加载资源的方式
1.Resource类中的相关方法加载Resources文件夹下的资源
2.AssetBundle类中或Addressables类中的相关方法加载AB包中的资源
3.WWW类中或UnityWebRequest类中的相关方法加载本地或远端资源

No39.Unity中的光照贴图的作用
在移动平台上(或配置较低的设备上)使用实时光源是非常消耗性能的
我们可以使用光照贴图,预先将环境光烘焙到贴图上,可以减少性能消耗

No40.LOD(多细节层次)和 MipMap(纹理图)的作用

优化游戏性能
从不同距离渲染对象时,使用的是质量不同的模型(LOD)和贴图(Mipmap)。(一般情况是越远面数越低,图片越小)

No41.在Unity中如何控制渲染优先级
1.不同摄像机渲染时,摄像机深度(Camera depth)控制优先级
2.相同摄像机时,排序层级(Sorting Layer)控制优先级
3.相同排序层级时,层中的顺序(Order in Layer)控制优先级
4.相同摄像机,无排序层级属性时,Shader中的RenderQueue(渲染队列)控制优先级

No42.内存中,堆和栈的区别

堆和栈是操作系统堆进程占用的内存空间的两种管理方式

栈:由操作系统自动分配释放,存放函数的参数值,局部变量值,栈中数据的生命周期随着函数的执行完成而结束

堆:一般由程序员分配释放,如果开发人员不释放,程序结束时由操作系统回收
(在C#中 托管堆内存 会由 C#帮助我们管理,存在GC垃圾回收机制)

No43.UDP和TCP的区别
连接方面:TCP面向连接,UDP无连接
是否可靠:TCP可靠(无差错、不丢失、不重复、按顺序),UDP不可靠
传输效率:TCP相对UDP较低
连接对象:TCP一对一,UDP n对n

No44.C#中new关键字的用法

  • 创建新的对象 例如:Character character=new Character();
  • 子类使用与父类相同的方法,但是不继承父类的方法,可以在函数声明前加上new关键字,来隐藏父类的方法
  • 泛型约束使用new关键字,表示需要无参构造函数,具体如下
1
2
3
4
5
6
class ItemFactory<T> where T : new() {
public T GetNewItem()
{
return new T();
}
}

No45.如何用int来记录32种状态

需要一个知识点便是int所占的字节数位4,通过按位记录状态,每一位代表一种状态,1代表存在,0代表不存在

46.优化Unity游戏的性能

(1)尽可能使用低多边形模型和贴图,并尽量减少场景复杂度。

(2)精简场景中的物体数量和顶点数量,并尽可能使用批处理技术以减少绘制调用。

(3)避免不必要的物理计算和运算量大的脚本代码,并尽可能使用对象池技术来减少内存分配。

(4)尽可能使用Unity的内置工具来识别性能瓶颈,如Profiler、Frame Debugger和Hierarchy Window。

(5)最后,可以考虑使用各种第三方工具和优化技术,如动态LOD、屏幕空间反射(Screen Space Reflections)和渐进式网格细化(Progressive Mesh)。

47.GUI与UGUI的优点和缺点

UGUI所见即所得,UGUI使用Canvas和事件系统,UGUI还能自适应屏幕
GUI在脚本周期中使用OnGui函数,通过脚本代码控制。OnGui性能消耗大,每一次渲染都是一个DrawCall
在手游端都在寻求原生GUI的替代方案

48.MeshFilter、MeshRender 和 SkinnedMeshRender 的关系与不同

MeshFilter网格过滤器,通过mesh属性获取模型网格
MeshRender网格渲染器,渲染Material,lighting,probe探针
SkinnedMeshRender蒙皮网格渲染器,渲染人物模型,渲染基本属性,材质,光照,探针,其他设置属性
Unity换装主要是切换Mesh、root bone和材质贴图

49.Prefab 的作用?Editor下动态创建Prefab的方式

prefab是素材,模型,贴图,shader等默认配置的集合体,便于修改
prefab已经被序列化存储在二进制文件当中,方便传输,方便打包导出的操作
prefab是一个模板,方便进行实例化
团队协作的便捷性

使用脚本publci字段,直接将prefab拖拽到这个字段下
Asset文件夹下,创建Resource文件夹,prefab放入,在代码里使用Resource.load(“prefab名称”)
50.用 u3d 实现 2d 游戏,有几种方式
摄像机改为正交模式
使用引擎改为2D系统
使用UGUI

51.OverDraw

首先OverDraw这个是非常影响渲染性能的,而OverDraw的定义正如字面意思所说,使过度绘制,也就是说同一个屏幕像素被绘制了太多次,而为了避免OverDraw,主要体现在以下三个点上

  • UGUI中的Text组件尽量少的使用Outline这个组件
  • UGUI中的Image组件尽量少使用Mask
  • 减少粒子特效的数量
  • 尽量减少半透明的物体的使用