【Unity面试】 2022年Unity面试题分享 | 全面总结 | 建议收藏

【重点面试题】代表面试的时候问到的题目
光背答案是没有用的,一定要动手操作一下,才能知道答案为什么是这个。

本文章力求从简单的形式到深入理解原理,再到扩展,丰富知识的层次感,知其所以然。

在文末为大家整理了系统的U3D的学习资料,以及总结打包的面试真题,需要文末公众号自取。

2021年Unity面试题分享


一、C#语言和面向对象OOP(已更新2021.3.8)

1、【重点面试题】面向对象的三大特性
封装 :隐藏对象的属性,并实现细节(方法),对外提供接口,
public全局,protected子类,internal同集,隐藏private
同类,public属性器,private字段,对赋值进行限定。
sealed修饰符的子类是不能被继承的。

		设计上:分而治之,封装变化、高内聚低耦合
		数据上:把一些基本数据复合成一个自定义类型的数据
		方法上:隐藏实现细节,向外提供接口

继承:重用现有代码
多态:静态多态重载,动态多态重写。
父类行为由子类具体实现,包含virtual虚方法,abstract抽象方法,interface接口
可以涉及题目虚方法、抽象方法、接口的区别

重载和重写区别
静态重载:返回值无关,与参数个数,类型相关。编译阶段。便于扩展和维护
动态重写override(运行时,改写了方法表的新地址)。
抽象方法的父类必须是抽象类,子类是抽象类可以不重写,抽象类不可以被实例化。

举例子非常重要
基类抽象人物工厂(接口),子类具体玩家工厂,子类怪物工厂,子类npc工厂,抽象产品(接口)具体魔法师,弓箭手产品~
资源管理工厂,UI资源管理工厂,音频资源管理工厂,资源管理,UI资源管理,音频资源


2、【重点面试题】值类型和引用类型区别
值类型:包含了所有简单类型(整数、浮点、bool、char)、struct、enum。
继承自System.ValueTyoe
引用类型包含了string,object,class,interface,delegate,array
继承自System.Object

内存区域上的区别
值类型:数据存储在栈上,超出作用域就自动清理
引用类型:数据存储在托管堆上,引用地址在线程栈上,地址指向数据存放的堆上
托管堆会由GC来自动释放 ,线程栈数据在作用域结束后会被清理。

拷贝策略:值类型是拷贝数据,引用类型是拷贝引用地址
如果值类型为传值参数,传值参数会在栈上新开辟一个副本,原先的值类型数据不会改变
如果引用类型是传值参数,传值参数会创建一个新的引用地址,两个引用地址会指向同一个对象实例的数据,实例数据会随着改变进行改变。(这种行为被称为副作用,一般实际项目不会这么操作,要么return返回参数,要么使用ref或者out修饰符)
【扩展Ref引用参数,Out输出参数可以利用这一副作用机制】

通常来讲 变量的值分配 与其声明该变量的位置有关。 局部变量的值 总是在 栈上的。 实例变量的值则和实例本身一起储存在实例储存的地方。 引用类型实例和静态总是储存在 堆上的。
数组的元素、引用类型中的值类型字段等,引用类型的确总是分配在托管堆上, 但是值类型并非总是分配在线程栈上有可能分配在堆上。

值类型和引用类型互相转换:拆箱和装箱
装箱:值类型====》引用类型object
1.分配内存堆
2.值类型数据拷贝到新的内存堆中
3.栈中分配一个新的引用地址指向内存堆
拆箱:引用类型object====》值类型
1.检查确保对象是给定值类型的一个装箱值
2.将该值数据复制到栈中的值类型

string是特殊的引用类型,如果传入参数是string,在方法里修改,原string数值不变。
原因是string的不变性,系统内部做了特殊处理。
链接: B站刘铁猛C#入门精要.


【重点面试题】3、装箱和拆箱的区别
值类型和引用类型的最终基类是Object
装箱:值类型转换成引用类型的过程,生成新的引用
拆箱;引用类型转换成值类型的过程

装箱操作:托管堆分配内存,值类型拷贝数据,object地址指向托管堆对象
拆箱操作:根据object引用地址找到托管堆上的数据,栈上数据拷贝
避免装箱操作,生成新的应用,解决办法就是第一是重载,第二是泛型
链接: 参考资料.

4、public、private、protected、internal、sealed的区别
public全局、private类内部、protected派生类、internal本程序集
sealed声明类就不能继承,声明方法就是不能被重写


【重点面试题】6、什么是接口,描述一下接口的成员具体实现(手撸代码和注意修饰符)
接口interface,不能定义字段,可以定义【非静态的】属性、索引器、事件、方法
默认public,但不能写任何访问修饰符
接口是引用类型,可以通过as运算符强转,获取某对象的接口的引用
接口可以继承N个接口,继承类要实现所有接口的方法

声明接口IA > 继承接口的类B > 类B实现接口所有方法
接口要小而精,定义一组方法,继承接口的派生类要实现接口的所有方法。
接口和抽象类是不能被实例化的对象(引用类型)。

public delegate void DelegateTest();
    public interface ITest //只能包含非静态成员函数,隐式public,但不允许访问修饰符
    {
        void Method(string a); //方法
        string Property //属性
        {
            get; set;
        }
        event DelegateTest EventTest; //事件,需要先定义一个委托
        int this[int index] //索引器
        {
            get; set;
        }
    }

举个例子:游戏门:抽象类,不能实例,很多行为,定义接口,破坏可以击碎
系统接口鼠标行为,停留进入离开
很多设计模式,是对接口的应用,面向接口编程,实现层面更加有层次。
【参考C#图解第十五章接口】


【重点面试题】7、foreach迭代器遍历和for循环遍历的区别
如果集合需要foreach遍历,是否可行,存在一定问题
foreach中的迭代变量item是的只读,不能对其进行修改,比如list.Remove(item)操作
foreach只读的时候记录下来,在对记录做操作,或者直接用for循环遍历
foreach对int[]数组循环已经不产生GC,避免对ArrayList进行遍历

for语句中初始化变量i的作用域,循环体内部可见。
通过索引进行遍历,可以根据索引对所遍历集合进行修改
unity中for循环使用lambda表达式注意闭包问题

Foreach遍历原理
任何集合类(Array)对象都有一个GetEnumerator()方法,该方法可以返回一个实现了 IEnumerator接口的对象。
这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象。
通过这个实现了 IEnumerator接口对象A,可以遍历访问集合类对象中的每一个元素对象
对象A访问MoveNext方法,方法为真,就可以访问Current方法,读取到集合的元素。

	    List<string> list = new List<string>() { "25", "哈3", "26", "花朵" };
 		IEnumerator listEnumerator = list.GetEnumerator();
        while (listEnumerator.MoveNext())
        {
            Console.WriteLine(listEnumerator.Current);
        }

枚举器的实现(枚举器可用于读取集合中的数据,但不能用于修改集合)

链接: 参考资料.


【重点面试题】8、string和stringbuilder和stringBuffer区别
String不变性,字符序列不可变,对原管理中实例对象赋值,会重新开一个新的实例对象赋值,新开的实例对象会等待被GC。
string拼接要重新开辟空间,因为string原值不会改变,导致GC频繁,性能消耗大

StringBuffer是字符串可变对象,可通过自带的StringBuffer.方法来改变并生成想要的字符串。对原实例对象做拼接的实例,不会生成新的实例对象。
拼接使用StringBuilder和StringBuffer,只开辟一个内存空间,这是性能优化的点。

StringBuilder是字符串可变对象,基本和StringBuilder相同。
唯一的区别是StringBuffer是线程安全,相关方法前带synchronized关键字,一般用于多线程
StringBuilder是非线程安全,所以性能略好,一般用于单线程
三者性能比较 StringBuilder>StringBuffer>String

相关方法
StringBuilder.Append 将信息追加到当前 StringBuilder 的结尾。
StringBuilder.AppendFormat 用带格式文本替换字符串中传递的格式说明符。
StringBuilder.Insert 将字符串或对象插入到当前 StringBuilder 对象的指定索引处。
StringBuilder.Remove 从当前 StringBuilder 对象中移除指定数量的字符。
StringBuilder.Replace 替换指定索引处的指定字符。


9、使用List的区别
list=new list()会导致每增加一个内容就增加新内存,导致原内存浪费,GC频繁
需要添加一个固定参数,只开辟一个内存,list = new list(50)
性能优化的点


10、字符串比较
先用string 变量存储 obj.name ,这用只有一个内存空间保存
如果不存储 obj.name每一次比较都会产生新的内存空间、
比较obj.tag==”Tag“不使用,而是使用避免GC的obj.CompareTag(“tag”)
射线检测SphereColliderNoAlloc可以避免GC,比直接使用SphereCollider性能要好


【重点面试题】11、请简述GC垃圾管理器,和GC产生的原因,并描述如何避免
GC垃圾回收机制,避免堆内存溢出,定期回收那些没有有效引用的对象内存
GC优化,就是优化堆内存,减少堆内存,即时回收堆内存
GC归属于CLR

如何避免
1.减少new的次数
2.字符串拼接使用stringbuilder,字符串比较先定义一个变量存储,防止产生无效内存
3.list,new时候,规定内存大小
4.如果要射线检测,应该使用避免GC的方法XXXXNoAlloc函数
5.foreach迭代器容易导致GC(目前Unity5.5已修复),使用For循环
6.使用静态变量,GC不会回收存在的对象,但静态变量的引用对象可能被回收
7.使用枚举替代字符串变量
8.调用gameobject.tag=="XXX"就会产生内存垃圾;那么采用GameObject.CompareTag()可以避免内存垃圾的产生:
9.不要在频繁调用的函数中反复进行堆内存分配,比如OnTriggerXXX,Update等函数
10.在Update函数中,运行有规律的但不需要每一帧执行的代码,可以使用计时器,比如1秒执行一次某些代码!!!
链接: 参考文章.


12、请描述interface和抽象类之间的不同
接口是一种行为,抽象类是一种不能实例化的对象。
接口interface可以定义方法、属性、索引器、事件
抽象类abstract可以定义字段、静态字段和方法、抽象方法、属性、构造函数
接口可以继承多个接口,抽象类只能继承一个类
接口直接实现所有成员,抽象类重写override抽象方法
接口和抽象都不能被实例化,派生类必须实现基类或接口的方法
抽象类可以派生自另一个抽象类,接口可以多重实现,抽象类只能单一继承
举个例子:抽象类门,多接口继承【可破坏、金属】的行为方法,派生类实例化这个门,接口的实现类实现具体行为,派生类创建这样具体的可破坏的铁门
链接: 参考资料.


【重点面试题】13、反射的实现原理?
定义:运行时,动态获取类型信息,动态创建对象,动态访问成员的过程。
另一种定义:审查元数据并收集元数据的信息。
元数据:编译后的最基本数据单元,就是一堆表,反射就是解析这些元数据。
反射是在运行期间获取到类、对象、方法、数据的一种手段
主要使用类库System.Reflection
反射要点:如何获取类型,根据类型来动态创建对象,反射获取方法以及动态调用方法,动态创建委托
一、动态获取类型信息
1.System.Reflection.Assembly.Load(“XXXX.dll”) 动态加载程序集
2.System.Type.GetType(“XXXX类名”); //动态获取某程序集中某类信息
3.obj.GetType(); //已知对象获取类信息 ——或者——typeof(类型) //已知类类型
二、动态创建对象实例(上一步操作后获得类对象)
System.Activator.CreateInstance(Type type);
三、动态访问成员调用方法(上一步操作后已获取实例对象)
System.Reflection.MethodInfo method = type.GetMethod(“方法名”);//获得方法
System.Reflection.MethodInfo.Invoke(object , new object[]{参数}) //调用的类实例和实例参数

核心类
System.Reflection.Assembly 描述程序集
System.Type 描述类
System.Reflection.FieldInfo 描述了类的字段
System.Reflection.ConstructorInfo 描述构造函数
System.Reflection.MethodInfo 描述类的方法
System.Reflection.PropertyInfo 描述类的属性

反射耗性能,lua是动态语言,一种小巧的脚本语言,会使用反射机制。

知识扩展
手机端不支持编译,需要热更方案,通过lua的反射机制将旧的DLL文件替换成新的DLL文件。
Xlua是lua框架,由TX鹅肠进行维护,方便了C#与lua相互调用,C#端实现lua虚拟机
链接:参考资料太多,主要搜索,C#反射机制,lua,xlua性能等等。


14、.Net 与 Mono 的关系?
.Net是一个语言平台
Mono为.Net提供集成开发环境,集成并实现了
.NET的编译器、CLR 和基础类库,
使得.Net既可以运行在windows也可以运行于 linux,Unix,Mac OS 等。

15、在类的构造函数前加上 static 会报什么错?为什么?
静态构造函数不允许添加访问修饰符,且必须无参数
原因:无论创建多少类型的对象,静态构造函数只执行一次
类实例化或者首静态成员调用之前,运行库会先调用静态构造函数
静态构造函数优先级高于任何其它构造函数
也无法使用this和base来调用静态构造函数
一个类只能有一个静态函数,如果有静态变量,系统也会自动生成静态函数


16、C# String 类型比 stringBuilder 类型的优势是什么?
string功能性更强,通用性更好,用途更广泛
string不可变性,线程栈同步
编译器已将把string,并通过操作优化成stringbuilder,在性能上不差,一般可以用string代替stringbuilder


17、C# 函数 Func(string a, string b)用 Lambda 表达式怎么写?
Lambda表达式(任意参数)=> { 表达式} ; => 读作goesto
(a,b)=> { } ;


【重点面试题】18、C#中有哪些常用的容器类,各有什么特点,性能区别?
Stack栈:先进后出,入栈和出栈,底层泛型数组实现,入栈动态扩容2倍
Queue队列:先进先出,入队和出队,底层泛型数组实现,表头表尾指针,判空还是满通过size比较
Queue和Stack主要是用来存储临时信息的

Array数组:需要声明长度,不安全
ArrayList数组列表:动态增加数组,不安全,实现了IList接口(表示可按照索引进行访问的非泛型集合对象),Object数组实现
List列表:底层实现是泛型数组,特性,动态扩容,泛型安全
将泛型数据(对值类型来说就是数据本身,对引用类型来说就是引用)存储在一个泛型数组中,添加元素时若超过当前泛型数组容量,则以2倍扩容,进而实现List大小动态可变。(注:大小指容量,不是Count)
LinkList链表
1、数组和List、ArrayList集合都有一个重大的缺陷,就是从数组的中间位置删除或插入一个元素需要付出很大的代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。
2、LinkedList(底层是由链表实现的)基于链表的数据结构,很好的解决了数组删除插入效率低的问题,且不用动态的扩充数组的长度。
3、LinkedList的优点:插入、删除元素效率比较高;缺点:访问效率比较低。

C#则List和LinkedList的区别
List是数组列表,LinkedList是双向链表,List读取速度快,时间复杂度是O(1),增删比较麻烦,时间复杂度是O(n).
LinkedList读取时间复杂度是O(n),增删时间复杂度是O(1)

HashTable哈希表(散列表)
概念:不定长的二进制数据通过哈希函数映射到一个较短的二进制数据集,即Key通过HashFunction函数获得HashCode
装填因子:α=n/m=0.72 ,存储的数据N和空间大小M
然后通过哈希桶算法,HashCode分段,每一段都是一个桶结构,一般是HashCode直接取余。
桶结构会加剧冲突,解决冲突使用拉链法,将产生冲突的元素建立一个单链表,并将头指针地址存储至Hash表对应桶的位置。这样定位到Hash表桶的位置后可通过遍历单链表的形式来查找元素。
1、Key—Value形式存取,无序,类型Object,需要类型转换。
2、Hashtable查询速度快,而添加速度相对慢
3、Hashtable中的数据实际存储在内部的一个数据桶里(bucket结构体数组),容量固定,根据数组索引获取值。

Directionary<TKey,TVaule>字典,有序,泛型存储不需要进行类型装换(不需要装箱拆箱),碰撞阈值扩容~
HashSet:一组不包含重复的元素集合【LeetCode算法217存在重复元素】

性能排序:
插入性能: LinkedList > Dictionary > HashTable > List
遍历性能:List > LinkedList > Dictionary > HashTable
删除性能: Dictionary > LinkedList > HashTable > List
小结:
在修改较频繁,且查找和删除也较多时,首选LinkedList,
在主要以删除为主,插入为辅,且查找较少时,首选Dictionary,
在查找频繁,而又无需修改的情况下,则首选List。

//哈希表结构体
private struct bucket {
   public Object key;//键
    public Object val;//值
    public int hash_col;//哈希码
}
//字典结构体
private struct Entry {
    public int hashCode;    // 除符号位以外的31位hashCode值, 如果该Entry没有被使用,那么为-1
    public int next;        // 下一个元素的下标索引,如果没有下一个就为-1
    public TKey key;        // 存放元素的键
    public TValue value;    // 存放元素的值
}

private int[] buckets;      // Hash桶
private Entry[] entries;    // Entry数组,存放元素
private int count;          // 当前entries的index位置
private int version;        // 当前版本,防止迭代过程中集合被更改
private int freeList;       // 被删除Entry在entries中的下标index,这个位置是空闲的
private int freeCount;      // 有多少个被删除的Entry,有多少个空闲的位置
private IEqualityComparer<TKey> comparer;   // 比较器
private KeyCollection keys;     // 存放Key的集合
private ValueCollection values;     // 存放Value的集合

链接: Stack参考链接.
链接: Queue参考链接.
链接: ArrayList参考链接.
链接: List参考链接.
链接: HashTable参考链接.
链接: Dictionary参考链接.


19、C#中常规容器和泛型容器有什么区别,哪种效率高?
常规容器有拆箱和装箱操作,速度慢,消耗性能
泛型容器效率更高


20、有哪些常见的数值类?
简单数值类型:整数型、字符型、布尔型、实数型
复合类型:结构类型、枚举类型


21、C#中委托和接口有什么区别?各用在什么场合?
委托delegate:unity事件与委托密切相关,回调机制,减少对象之间数据交互
接口interface:多人协作,完全抽象,类单继承
委托是约束方法的集合
接口是约束类具备的功能集合,解决类单继承问题


22、C#中unsafe关键字是用来做什么的?什么场合下使用?
unsafe 非托管代码,配合fixed一起使用 ,用在需要指针操作的场合
项目背包系统的任务装备栏使用到


【重点面试题】23、C#中ref和out关键字有什么区别?知道Ref的深层原理是什么?
ref修饰引用参数。参数必须赋值,带回返回值,又进又出
out修饰输出参数。参数可以不赋值,带回返回值之前必须明确赋值,
引用参数和输出参数不会创建新的存储位置

如果ref参数是值类型,原先的值类型数据,会随着方法里的数据改变而改变,
如果ref参数值引用类型,方法里重新赋值后,原对象堆中数据会改变,如果对引用类型再次创建新对象并赋值给ref参数,引用地址会重新指向新对象堆数据。方法结束后形参和新对象都会消失。实参还是指向原始对象,值不够数据改变了
【参考C#图解教程:引用类型作为值参数和引用参数】


24、For,foreach,Enumerator.MoveNext的使用,与内存消耗情况
for通过索引或下标一次进行遍历
foreach和Enumerator.MoveNext通过迭代进行遍历
内存消耗本质没有多少区别
迭代器有一个状态机
before
running:yield return 或 yield break 或迭代结束
after


25、函数中多次使用string的+=处理,会产生大量内存垃圾(垃圾碎片),有什么好的方法可以解决。
使用stringbuilder的append


26、当需要频繁创建使用某个对象时,有什么好的程序设计方案来节省内存?
Unity对象池
设计单例模式全局实例化一次


27、Foreach循环迭代时,若把其中的某个元素删除,程序报错,怎么找到那个元素?以及具体怎么处理这种情况?(注:Try…Catch捕捉异常,发送信息不可行)
foreach迭代器不能进行操作
在循环中记录索引值或者key值,在迭代结束后,查找到这个元素,在进行删除操作


28、GameObject a=new GameObject() GameObject b=a 实例化出来了A,将A赋给B,现在将B删除,问A还存在吗?
存在
a引用地址在线程栈中,数据内容在托管堆中
b引用地址在线程栈中,数据内容指向A的托管堆中的内容
B删除,只是删除b的引用地址


【重点面试题】29、C#引用和C++指针的区别
C#不支持指针,但可以使用Unsafe,不安全模式,CLR不检测
C#可以定义指针的类型、整数型、实数型、struct结构体
C#指针操作符、C#指针定义
使用fixed,可以操作类中的值类型
相同点:都是地址
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
不同点:
指针是个实体,引用是个别名。
sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
引用是类型安全的,而指针在不安全模式下


【重点面试题】30、using的作用
资源:实现了IDisposable接口的类或结构。
using语句确保这些资源能够被适当的释放(Resource.Dispose)
using原理
using(分配资源){ 使用资源 } ——> 释放资源 (隐式)
使用资源(可能会导致异常)会被放进Try块里,释放资源(有无异常)都会放进在finally块

using(分配资源)
{
	try{ 使用资源 }
	finally{ Resource.Dispose}
}

using指令,using+命名空间(或命名空间一个类型) 在源文件的顶端声明
也可以不使用using,直接命名空间.类.成员方法


【重点面试题】31、字典Dictionary的内部实现原理
泛型集合命名空间using System.Collections.Generic;
任何键都必须是唯一

该类最大的优点就是它查找元素的时间复杂度接近O(1),实际项目中常被用来做一些数据的本地缓存,提升整体效率。
实现原理
1.哈希算法:将不定长度的二进制数据集给映射到一个较短的二进制长度数据集一个Key通过HashFunc得到HashCode
2.Hash桶算法:对HashCode进行分段显示,常用方法是对HashCode直接取余
3.解决碰撞冲突算法(拉链法):分段会导致key对应的桶会相同,拉链法的思想就像对冲突的元素,建立一个单链表,头指针存储到对应的哈希桶位置。反之就是通过确定hash桶位置后,遍历单链表,获取对应的value

Key值 HashFunc Buckets桶 Entries入口(最小数据结构)
在这里插入图片描述
Dictionary字典中最小的数据结构体Entry,调用Add(Key,Value)方法添加的元素都会被封装在这样的一个结构体中。

private struct Entry {
    public int hashCode;    // 除符号位以外的31位hashCode值, 如果该Entry没有被使用,那么为-1
    public int next;        // 下一个元素的下标索引,如果没有下一个就为-1
    public TKey key;        // 存放元素的键
    public TValue value;    // 存放元素的值
}

Collection版本控制,字典重要变量version,这个变量,在每一次新增、修改和删除操作时,都会使version++
之后每一次迭代过程都会检查版本号是否一致,如果不一致将抛出异常。
这样就避免了在迭代过程中修改了集合,造成很多诡异的问题。

链接: Dictionary实现原理资料.


【重点面试题】32、泛型是什么
多个代码对 【不同数据类型】 执行 【相同指令】的情况
泛型:多个类型共享一组代码
泛型允许类型参数化,泛型类型是类型的模板
5种泛型:类、结构、接口、委托、方法
类型占位符 T 来表示泛型

泛型类不是实际的类,而是类的模板
从泛型类型创建实例
声明泛型类型》通过提供【真实类型】创建构造函数类型》从构造类型创建实例
类<T1,T2> 泛型类型参数

性能:泛型不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高
安全:通过知道使用泛型定义的变量的类型限制,编译器可以在一定程度上验证类型假设,所以泛型提高了程序的类型安全。


【重点面试题】33、结构体和类有什么区别


二、Unity脚本基础(已更新2021.3.8)

【重点面试题】1、Unity3D中的协程(coroutine),C#线程和进程之间的区别是什么?

简记:协程和线程区别
协程(协同程序Coroutine): 同一时间只能执行某个协程。开辟多个协程开销不大。协程适合对某任务进行分时处理。 Unity提供了StartCoroutine来开启协程,当你在 StartCoroutine 的函数体里处理一段代码时,利用 yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。

举个例子:LoadLevelAsync(异步加载关卡)后台加载场景的方法。允许你在后台加载新资源和场景,再利用协同,你就可以在前台用 loading 条或动画提示玩家游戏处于加载中,同时后台协同处理加载的事宜。

进程progress:进程是线程的容器。
线程thread: 同一时间可以同时执行多个线程。开辟多条线程开销很大。线程适合多任务同时处理,并发并行。
协程:具体多返回点的方法,时间分片(帧),Unity只能是单线程,只能在主线程调用Api,对象
在这里插入图片描述


2、Unity3D 是否支持写成多线程程序?如果支持的话需要注意什么?
支持,如果掌握的Unity多线程的方法,就可以从容的使用多个硬件处理器或处理很难划分管理数据块。
【注意】
1、只能从主线程中访问 Unity3D 的组件,对象和 Unity3D 系统调用
2、如果同时你要处理很多事情或者与 Unity 的对象互动,可以用 thread,可以通过排序来解决一个线程改变一个数据的问题,否则使用协程coroutine。
3、C#中有 lock 这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象

【重点面试题】3、OnEnable、Awake、Start 运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?
Awake > OnEnable>Start 推荐看脚本生命周期顺序
OnEnable可以在同一周期反复的发生
SetActive(true)就会反复触发OnEnable事件
SetActive(false)就会反复触发OnDisable事件

其中Awake函数一般用于实现单例模式;当脚本被实例化时,调用awake,完成成员变量的初始化,在单例模式中会有一个虚方法OnStart在awake中调用,在实际脚本中重写OnStart方法
OnEnable函数是在游戏对象可以调用时调用;
OnDisable是在游戏对象不可用时调用;
Start函数则是在场景中显示该游戏对象前调用一次,用于开始设置物体属性和渲染;
FixedUpdate函数具有固定更新频率,一般进行游戏对象的物理引擎的更新;
Update函数则是渲染帧更新,每秒更新一定频率;
LateUpdate函数是延迟更新,只有在每一帧的所有Update函数都执行完了过后才会执行;
而OnGUI函数则是在每一帧更新时调用。
在这里插入图片描述
4、Unity3D 如何获知场景中需要加载的数据?如何动态资源加载?
instantiate:最简单的一种方式,以实例化的方式动态生成一个物体。

Assetsbundle:即将资源打成 asset bundle 放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object,unity官方推荐也是绝大多数商业化项目使用的一种方式。

Resource.Load:可以直接load并返回某个类型的Object,前提是要把这个资源放在Resource命名的文件夹下,Unity不管有没有场景引用,都会将其全部打入到安装包中

AssetDatabase.loadasset :这种方式只在editor范围内有效,游戏运行时没有这个函数,它通常是在开发中调试用的。


5、Unity中碰撞器(Collider)和触发器(Trigger)的区别?
碰撞器(Collider)有碰撞效果,IsTrigger=false,可以调OnCollisionEnter/Stay/Exit函数

触发器(Trigger)没有碰撞效果,isTrigger=true,可以调用OnTriggerEnter/Stay/Exit函数


6、U3D中,几种施加力的方式,描述出来
首先需要对象要有rigidbody组件
Rigidbody.AddForce /AddForceAtPosition
2D Constant Force
Force/Relative Force/Torque


7、物体自旋转使用的函数叫什么?物体绕某点旋转使用函数叫什么?
transform.Rotate
transform.RotateAround


8、u3d 提供了一个用于保存读取数据的类,(playerPrefs),请列出保存读取整形数据的函数
PlayerPrefs类是一个本地持久化保存与读取数据的类
PlayerPrefs类支持3中数据类型的保存和读取,浮点型,整形,和字符串型。
分别对应的函数为:
SetInt();保存整型数据;GetInt();读取整形数据;
SetFloat();保存浮点型数据; GetFlost();读取浮点型数据;
SetString();保存字符串型数据; GetString();读取字符串型数据;


9、unity3d 从唤醒到销毁有一段生命周期,请列出系统自己调用的几个重要方法。
(init)Awake>OnEnable>Start > (Physic)FixedUpdate> (GameLogic)Update>Yield >LateUpdate>OnGui>OnDisable>OnDistroy


10、物理更新一般在哪个系统函数里?
FixedUpdate,每固定帧绘制时执行一次,和 update 不同的是 FixedUpdate 是渲染帧执行,如果你的渲染效率低下的时候 FixedUpdate 调用次数就会跟着下降。

FixedUpdate 比较适用于物理引擎的计算,因为是跟固定帧渲染有关。
Update 就比较适合做游戏逻辑更新。
LateUpdate比较适合相机更新,update后更新,防止视角移动,游戏对象发生空帧未出现。


11、反向旋转动画的方法是什么?
1.将动画速度调成-1
2.改代码animation.speed=-1


【重点面试题】12、用代码实现第三人称角色控制器?第一人称角色控制器
大致思路:
摄像机与角色的距离范围
摄像机旋转、平移
鼠标控制摄像机
//代码稍后会在博客中贴出,转链接,目前未实现


13、获取、增加、删除组件的命令分别是什么?
获取:GetComponent
增加:AddComponent
删除:Destroy


14、Animation.CrossFade 是什么?
动画淡入淡出

15、Application.LoadLevel 命令作用是什么?
加载关卡,已弃用
现在使用SceneManager.LoadScene

16、调式记录到控制台的命令是什么?
Debug.Log();

17、编辑器类存放路径是什么?
Asset/Editor

18、使用原生 GUI 创建一个可以拖动的窗口命令是什么?
GUI.DragWindow

19、localPosition 与 Position 的使用区别?
localPosition :自身坐标系,相对于父级的位置
Position :世界坐标系中的位置

20、Mathf.Round和Mathf.Clamp和Mathf.Lerp含义?
Mathf.Round:四舍五入
Mathf.Clamp:左右限值
Mathf.Lerp:插值

【重点面试题】21、写一个计时器工具,从整点开始,格式为00:00:00
分小时、分、秒

22、写出 Animation 的五个方法
AddClip 将 clip 添加到名称为 newName 的动画中。
Blend 在后续 time 秒中将名称为 animation 的动画向 targetWeight 混合。
CrossFade 在后续 time 秒的时间段内,使名称为 animation 的动画淡入,使其他动画淡出。
CrossFadeQueued 使动画在上一个动画播放完成后交叉淡入淡出。
IsPlaying 名称为 name 的动画是否正在播放?
PlayQueued 在先前的动画播放完毕后再播放动画。
RemoveClip 从动画列表中移除剪辑。
Sample 对当前状态的动画进行采样。
Stop 停止所有使用该动画启动的正在播放的动画。

23、用鼠标实现在场景中拖动物体,用鼠标滚轮实现缩放(用一个 Cube 即可)
在场景中添加一个Plan,Camera,Directional Light,Cube。添加两个脚本一个挂在Camera上,另一个挂在Cube上。
1.鼠标滚轮实现缩放:将摄像机的镜头拉近或者拉远,调整摄像机的视角就可以实现2.鼠标实现在场景中拖动物体:解决思路就是将世界坐标转换成屏幕坐标,然后计算物体与鼠标之间移动量,循环鼠标被按下操作,得到鼠标的当前位置,加上计算好的移动量,将新的坐标赋值给物理就行了。 具体代码实现:http://www.cnblogs.com/hewencong/p/4299722.html

24、<愤怒的小鸟>给予初速度以后,怎么让小鸟受到重力和空气阻力的影响而绘制抛物线轨迹,说出具体的计算方法
Vector3 v 代表初速度 v’代表现在的速度, 假设小鸟是沿的 z 轴也就是transform.forward 方向,运动的质量为 m,那么 v‘=v-new Vector3(0,mgt,ft),transform.Translate(v’)做的就是抛物线运动(g 为重力加速度不要用现实中的需要自己调试,f 为阻力也要自己调试
设置,t 为时间)

25、当游戏中需要频繁创建一个物体时,我们需要怎样做能够节省内存?
1.使用预制体对象
2.使用对象池技术

26、碰撞检测需要物体具备什么属性?
能检测碰撞发生的方式有两种,一种是利用碰撞器,另一种则是利用触发器

27、如何使子控件居中,如果使用UGUI怎么实现
锚点设置为中心

28、去掉敏感字的程序(手写程序)
字符串replace

29、写出WWW的几个方法
WWW.LoadFromCacheOrDownload:可被用于将Assets Bundles自动缓存到本地磁盘
WWW.Dispose :释放现有的 WWW 对象。
WWW.isDone:是否完成下载?(只读)
WWW.progress:下载进度(只读)。

【重点面试题】30、启用MipMaps对内存的影响是?MipMap的作用?如何操作?
增加约33%的内存,1/4 +1/16
Lod相关知识

31、采用Input.mousePosition来获取鼠标在屏幕上的位置
左下角为原点(0,0),右上角为(Screen.Height,Screen.Width)

32、Unity中销毁GameObject的方式,简述Destroy和DestroyImmediate的区别
Destroy销毁消息对象,内存中还是存在,只有内存不够才被清除释放内存
DestroyImmediate立即销毁对象,并释放内存

33、如何检测物体是否被其他对象遮挡
射线检测
EventSystem.IsPointerOverGameObject
是否具有给定 ID 的指针是否位于 EventSystem 对象上


34、UnityAction和UnityFunc的区别
unity中需要带上修饰event,事件与委托密切相关,两行代码变一行代码
public event Action myEvent;

UnityAction本质上就是委托,带泛型参数最多4个,且没有返回值的方法
Action<T1,T2,T3>
UnityFunc本质上也是委托,带泛型参数最多4个,可以有返回值的方法
Func<T1,T2,T3,Return>

Action和Func的重要区别:
Action只用于没有返回值的方法,Func只用于有返回值的方法
它们泛型里的区别:
Action的泛型里要和方法参数的类型相同,且只有一种泛型
Func的泛型里前者和方法参数类型相同,最后一个与返回值类型相同

一般用于回调方法,注册事件,类直接数据交互松耦合
链接: 参考资料.


【重点面试题】35、unity常用资源路径有哪些

//获取的目录路径最后不包含  /
//获得的文件路径开头包含 /
Application.dataPath; //Asset文件夹的绝对路径
//只读
Application.streamingAssetsPath;  //StreamingAssets文件夹的绝对路径(要先判断是否存在这个文件夹路径)
Application.persistentData ; //可读写

//资源数据库 (AssetDatabase) 是允许您访问工程中的资源的 API
AssetDatabase.GetAllAssetPaths; //获取所有的资源文件(不包含meta文件)
AssetDatabase.GetAssetPath(object) //获取object对象的相对路径
AssetDatabase.Refresh(); //刷新
AssetDatabase.GetDependencies(string); //获取依赖项文件


Directory.Delete(p, true); //删除P路径目录
Directory.Exists(p);  //是否存在P路径目录
Directory.CreateDirectory(p); //创建P路径目录

AssetDatabase //类库,对Asset文件夹下的文件进行操作,获取相对路径,获取所有文件,获取相对依赖项
Directory //类库,相关文件夹路径目录进行操作,是否存在,创建目录,删除等操作

【重点面试题】36、向量的点乘、叉乘以及归一化的意义?
叉乘cross:可以获得两个向量A和B所构成平面,垂直的向量C,和左手坐标系类似,可以用来判断角色移动方向,判断顺时针还是逆时针旋转
在这里插入图片描述
点乘 dot :用来求向量之间的夹角,判断向量是否在同一方向、以及B向量在A向量上的投影
在这里插入图片描述
a·b>0 方向基本相同,夹角在0°到90°之间
a·b=0 正交
a·b<0 方向基本相反,夹角在90°到180°之间

归一化normalized:用在只关系方向,不关心大小的情况下


三、Unity性能优化(必问)(已更新2021.3.8)

lod 是什么,优缺点是什么
MipMap 是什么?作用?
当游戏中需要频繁创建一个物体对象时,我们需要怎么做来节省内存。
如何优化内存?
动态加载资源的方式?和区别
请简述一下对象池原理,什么情况下使用?
19.使用mipmap有什么好处?什么情况下使用?
Unity内存优化?GC垃圾回收
你认为unity在开发过程中哪些地方比较容易造成内存泄漏和内存泄漏问题?如何避免?
如何解决过多创建和删除对象带来的卡顿问题
Unity资源加载的有几种方式,简述asset bundie
背包系统中只有20个格子,现在有总共有100个物体,除了显示在视野中的20个外,对其他的处理方法?(注:将其他隐藏起来不可行,对象池得有具体的说明)

四、设计模式

1、用过哪些设计模式? 谈谈自己比较熟悉的设计模式
链接: 学习资料网站.
创建型:工厂方法、抽象工厂、单例、原型、生成器
结构型:代理、外观、组合、适配器、装饰、桥接、享元
行为:迭代器、中介者、观察者、策略、状态、命令、访问者、模板方法、备忘录、责任链

1)工厂方法模式 面试题
在父类中提供一个创建对象的方法,在其子类中决定实例化对象的类型。
优点:单一职责,开闭,里氏替换LSP,依赖抽象DIP,迪米特法则DP
缺点:每一次增加一个具体产品,都需要增加一个具体类和对应的具体工厂,代码复杂度就成倍增加,增加了系统具体类的依赖。
学习链接: 工厂方法模式.

2)抽象工厂模式:创建一系列相关的对象,而无需指定具体类。简记超级工厂创建其它工厂。
优点:单一职责、开闭、LSP、DIP、DP
缺点:引入众多接口和类,代码复杂度增加
抽象工厂通常基于一组工厂方法

3)单例模式 【面试题】
保证一个类只有一个实例,并提供一个访问实例的全局节点。
单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。
单例的构造函数必须对客户端 (Client) 代码隐藏。 调用获取实例方法必须是获取单例对象的唯一方式。
优点:解决了2个问题
缺点:违反单一职责原则,开闭原则。
Unity中的单例模式和不继承MonoBehaviord的普通单例模式。
应用例子:游戏中各种各样的管理器Manager
隐藏游戏体:obj.hideFlags = HideFlags.HideAndDontSave
链接: 我的两种单例模式代码.

4)状态模式 面试题
是一种行为设计模式,在对象的内部发生状态改变时改变其行为
解决大量if else 或者switch 多状态的情形
代码结构
客户端:新建具体状态,并且调用具体行为
状态控制器:状态属性,转换状态方法,调用状态的具体行为
状态父类或接口:控制器属性,设置控制器方法(保存控制器),抽象行为
具体状态:继承状态,重写为具体行为
链接: 参考资料代码.
优点:单一、开闭、控制器和父类状态封装,只需要注重具体状态类行为修改。易于维护和扩展、减少因新增状态对原因脚本进行大量修改,每个状态只需要维护自己,多项目开发、易于维护
缺点:状态较少的情况下就小题大做
举个例子:场景状态,主场景状态,加载场景状态,战斗场景状态,3个场景的切换

5)观察者模式 【面试题】
是一种行为设计模式,允许你定义一种订阅通知机制
代码结构
发布者:当新事件发生后,向其他对象发送自己所订阅的事件,发送通知方法。
订阅者接口:声明了通知方法Update更新
具体订阅者:可以执行一些操作来响应发布者的消息。
客户端:分别创建发布者和订阅者对象,然后为订阅者注册发布者更新
优点:开闭,迪米特法则,建立对象之间联系,数据松耦合
缺点:通知顺序是随机的
中介者和观察者相似

6)中介者模式
是一种行为设计模式,减少对象之间混乱无序的依赖关系,对象之间通过中介者对象进行合作。现实世界:飞机塔台调度
代码结构
N个组件:各种包含业务逻辑的类
中介者接口:接口申明了与组件的交流方式,通知
具体中介者:封装了多种组件间的关系。
组件并不知道其它组件的情况,组件如果发生变化,通知中介者,然后判断并发送给相应的其它组件。接受者和发送者不知道谁来处理请求和谁发出的请求
优点:单一、开闭、减少个组件的依赖关系,复用各个组件
缺点:

2、请说出 4 种面向对象的设计原则,并分别简述它们的含义
0、单一职责原则
一个类实现一个功能

1、开闭原则OCP(Open Close Principle)
对扩展开放,对修改关闭。

2、里氏代换原则LSP(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现,即子类一定可以替换其基类。

3、依赖倒转原则DIP(Dependence Inversion Principle)
针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则ISP(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好。
它还有另外一个意思是:降低类之间的耦合度。

5、迪米特法则,又称最少知道原则DP(Demeter Principle)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则CRP(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

3、设计一个状态机类型,状态值为int类型,要求:
拥有接口,获取当前状态,切换状态
外部可以监听状态切换事件,参数为切换前状态和切换后状态(使用delete和event)

4、如何处理unity中界面资源,界面逻辑以及功能模块三者之间的耦合关系

5、什么是MVC模式


五、Unity编辑器基础


1、请描述游戏动画有几种,以及其原理。
关键帧动画:每一帧动画序列当中包含了顶点的空间位置信息以及改变量,然后通过插值运算,得出动画效果。选中某一游戏对象,创建animation,添加属性Transform,MeshRender、collider。还可以添加关键帧,在关键帧上Add Animation Event事件。
骨骼动画:模型当中有一个骨骼结构层次的对象,存储了各个骨骼在空间内的位置信息。皮肤蒙皮附着在骨骼上,决定了角色的外观,每一个顶点数据都会随着多个骨骼影响而改变,从而实现动画效果。创建animator将各个动画拖入到动画状态机当中,设置参数,连接各个动画状态,在通过脚本控制来实现动画控制
关节动画:了解不多,是骨骼动画的前身,模型分成N个部分网格,分成部分动画,组成一个整体动画


2、Avator的作用
用户提供的模型骨架和Unity的骨架结构进行适配,是一种骨架映射关系。
方便动画的重定向
AnimationType有三种类型
Humanoid人型:可以动画重定向,游戏对象挂载animator,子类原始模型+重定向模型,设置原始模型和使用模型的AnimationType为Humanoid类型
Generic非人型
Legacy旧版
Avator Mask身体遮罩,身体某一部分是否受到动画影响
反向动力学 IK,通过手或脚来控制身体其他部分


3、物体发生碰撞的必要条件
物体A有(Collider和Rigidbody)或者CharacterController,物体B有collider
rigidbody完全受物理引擎影响
CharacterController物理引擎影响受限制


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


5、一个场景放置多个carmera并同时处于活动状态,会发生什么
多个Camera渲染画面,受到Camera组件的属性ClearFlag、CullingMask和Depth的影响,影响最终的合成画面。


6、使用过哪些插件
shader graph制作shader光影效果
cinemachine+timeline+postprocessingstack制作过场动画
nodecanvas制作怪物ai
easytouch手游触摸控制


7、U3D 中用于记录节点空间几何信息的组件名称,及其父类名称
Transform继承于Component继承于Object
常用脚本继承类关系
链接: 参考资料.


8、请简述如何在不同分辨率下保持 UI 的一致性
Canvas画布设置组件CanvasScaler的分辨率模式为Scale with screen size,保持画布一定比例下随屏幕改变,画布进行缩放。
UI位置的一致性通过设置锚点,Anchor Presets锚点预设


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


10、简述 SkinnedMesh 的实现原理
SkinnedMesh蒙皮网格动画
分为骨骼和蒙皮两部分
骨骼是一个层次结构,存储了骨骼的Transform数据
蒙皮是mesh顶点附着在骨骼之上,顶点可以被多个骨骼影响,决定了其权重等,
还有将顶点从Mesh空间变换到骨骼空间~


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

使用脚本publci字段,直接将prefab拖拽到这个字段下
Asset文件夹下,创建Resource文件夹,prefab放入,在代码里使用Resource.load(“prefab名称”)


12、如何销毁一个 UnityEngine.Object 及其子类
Destroy


13、为什么 Unity3D 中会发生在组件上出现数据丢失的情况?
组件被删除了


14、如何安全的在不同工程间安全地迁移 asset 数据?三种方法
导出包export package
将assets文件夹和libarary文件加一起迁移
unity自带的assets server功能 (不懂这个功能,上面两个方法用的多)


15、MeshCollider 和其他 Collider 的一个主要不同点?
MeshCollider是基于顶点数据,片面组成,消耗性能
BoxCollider是基于算法的,性能好


16、当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?
fixedupdate中代码控制,代码中两个位置进行射线检测,射线碰撞信息
增加碰撞体体积,射线穿过,射线长度增加
rigidbody刚体设置两个参数Interpolate急速插值和CollisionDetection碰撞检测


17、MeshRender 中 material 和 sharedmaterial 的区别?
两者是MeshRender的属性
sharedMaterial 是共用的 Material,称为共享材质。修改共享材质会改变所用使用该材质的物体,并且编辑器中的材质设置也会改变。
material 是独立的 Material,返回分配给渲染器的第一个材质。修改材质仅会改变该物体的材质。如果该材质被其他的渲染器使用,将克隆该材质并用于当前的渲染器。


18、用 u3d 实现 2d 游戏,有几种方式?
摄像机改为正交模式
使用引擎改为2D系统
使用UGUI


19、u3d 中碰撞器和触发器的区别?
碰撞器有碰撞效果,触发器会穿过,但能检测到
collider设置属性isTrigger为True
OnCollisionEnter/Stay/Exit
OnTriggerEnter/Stay/Exit
触发器可以用来检测物体是否经过某片空间


20、CharacterController 和 Rigidbody 的区别
CharacterController一般用在人型角色,继承于Collider,可以认为是受限的Rigidbody
Rigidboy完全受物理引擎影响,质量,阻力等属性,Is Kinematic不受物理引擎影响,通过代码改变Transform。


21、什么叫做链条关节
Hinge Joint,模拟两个物体之间有一个链条连接
在某个距离内只会发生移动不产生作用力,类似门的效果
超过某个距离会产生拉力,弹簧的效果


22、unity3d 提供了几种光源,分别是什么
Direction平行光
Point点光源
Spot聚光灯
Area区域光(烘焙用)


23、动画层(Animation Layers)的作用是什么?
动画分层
身体部位动画分层,比如我只想动动头,身体其他部分不发生动画
可以方便处理动画区分


24、Material 和 Physic Material 区别?
物理材质:处理物理效果,比如滑动摩擦力,静态摩擦力,反弹等属性
Material就是普通的贴图


25、什么是导航网格( NavMesh)?
用于自动寻路的网格
比如A*寻路
链接: 更加详细的参考资料.


26、Unity 摄像机有几种工作方式,分别是什么?
正交模式和透视模式


27、UGUI相关概念
UGUI锚点,相对于父对象,屏幕自适应
UGUI分为世界坐标和屏幕坐标
UGUI的Image可以使用material
UGUI不需要绑定collider,UI事件会拦截
UGUI通过Mask来裁剪
UGUI顺序根据Hierarchy顺序,越下面渲染在最上层


28、Unity3d中static batching和dynamic batching 各有什么用?
动态批处理和静态批处理,都是在一定条件下,对多次的DrawCall请求进行合并处理,减少CPU的DrawCall数量,达到提高性能的目的

静态批处理将静态的游戏对象组合成大网格(不移动)
对游戏对象Static属性设置成batching static ,
前提要共享材质且不移动,不缩放,不旋转

动态批处理是将很小的网格,将类似的顶点组合到一起,一次性绘制
不需要任何操作,可以旋转,缩放,移动
前提是共享材质,且不包含900顶点不超300顶点网格


29、Unity3d中Awake和Start 谁先执行,update和fixedUpdate 有什么区别?
awake先执行,一般用来初始化成员变量
start设置物体属性和渲染
fixedUpdate固定帧渲染,用于更新渲染物理引擎
update帧渲染,用于更新操作


30、向量的运算有哪些?Unity有哪些API可以计算
加法减法:物理上计算两个力的合力或者几个速度分量的叠加Vertor3(a1+b1,a2+b2,a3+b3)
数乘:向量与一个标量相乘,变量的正负,表示方向的正反方向变化,对向量的长度进行缩放
点乘:a点乘b得到一个标量,集合意义是a和b长度相乘再乘以两者夹角的余弦
叉乘:a叉乘b得到一个新向量,满足unity的左手坐标系

Vector3类
单位化normalized
向量长度magnitude
叉乘cross
点乘 dot
两向量夹角 angle
距离 distance
投影 project


31、UI面板层级管理
如果分为三层结构,可以使用Unity自带的Api在Hierarchy
Transofrm类
SetAsFirstSiBling 列表开头
SetAsLastSiBling 列表最后
SetSiBlingIndex 同级索引

六、数据结构和算法(已更新2021.2.27)


1、时间复杂度解释一下
算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 输入大小n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。

  1. 当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n)
    不等于一个常数项时,直接将常数项省略。
  2. 因为高次项对于函数的增长速度的影响是最大的,所以我们直接忽略低次项。
  3. 因为函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数。

比如 T(n) = 29 ,此时时间复杂度为 O(1)。
比如 T(n) = n + 29,此时时间复杂度为 O(n)。
比如T(n) = n^3 + n^2 + 29,此时时间复杂度为 O(n^3)。
比如T(n) = 3n^3,此时时间复杂度为 O(n^3)。

for (int i = 2; i < n; i++) {
        i *= 2;
        printf("%i\n", i);
    }

假设循环次数为t,必有2^t < n
t = log(2)(n),即 T(n) = log(2)(n),可见时间复杂度为 O(log(2)(n)),即 O(log n)。

加入 T(n) = T(n – 1) + T(n – 2) 是一个斐波那契数列,通过归纳证明法可以证明,当 n >= 1 时 T(n) < (5/3)^n,同时当 n > 4 时 T(n) >= (3/2)^n。
所以该方法的时间复杂度可以表示为 O((5/3)^n),简化后为 O(2^n)。

在这里插入图片描述


整数反转、以及溢出问题
反转数字是个很简单的问题,只需不断取模累乘即可
int t = x%10;
res = res*10 + t;
判断溢出
res < INT_MAX/10,这种情况无论t为多大,都不会溢出;
res > INT_MAX/10,这种情况无论t为多小,一定会溢出;
res == INT_MAX/10,其中INT_MAX=2147483647,所以当t>7时,会发生溢出;

链接: 参考.


Unity游戏常用洗牌算法


【面试题】冒泡排序
冒泡排序(BubbleSort)
在排序过程中相邻元素不断比较交换,一些元素慢慢被换到最后
时间复杂度
最好时间复杂度是O(N) :相邻不需要交换
最坏时间复杂度是O(N^2):反序文件,一直交换

参考我的另一个博文
链接: 冒泡示意图和代码


二分查找
二分查找:在有序的集合中搜索特定值的过程
有序集合:Collection
目标:Target
索引:Index
左右指针:Left和Right
中间指针:Middle——根据条件来确定向左查找还是向右查找

进行二分查找的训练

链接: leetcode二分查找算法.


【面试题】二叉树
二叉查找树(英语:Binary Search Tree),也称为 二叉搜索树、有序二叉树(Ordered Binary Tree)或排序二叉树(Sorted Binary Tree)。

具有下列性质的二叉树(可以是空树):

  1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值
  2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值
  3. 任意节点的左、右子树也分别为二叉查找树; 没有键值相等的节点

相比其他数据结构优势在于:查找插入的时间复杂度较低。

为 O(logn)
最坏是O(N)

改进版的二叉查找树可以使树高为 O(logn),从而将最坏效率降至 O(logn),如 AVL 树、红黑树等。

进行二叉树的训练

链接: leetcode二叉搜索树算法.


== 【面试题】用栈实现队列 、 用队列实现栈 ==
算法:2个栈实现队列
链接: 用栈实现队列.

算法:2个队列实现栈
链接: 用队列实现栈.

六、Lua语言和Xlua热更(已更新2021.3.8)


1、Lua如何调用C#
三种方式
第一种:官方不推荐
第二种:如果Resource文件下的Lua文件,使用Lua的Require函数即可
第三种:如果Lua文件是下载的,使用自定义Loader可满足

链接: C#调用Xlua文件.


2、C#如何调用Lua


3、资源如何打包?依赖项列表如何生成?
1.查找指定文件夹ABResource里的资源文件
——Directory.GetFile(资源路径)
——新建AssetBundleBuild对象
——获取资源名称,并赋值对应AB名称
——获取各个资源的依赖项:通过UnityEditor.AssetDataBase类获取各个资源的依赖项
2.使用Unity自带的BuildPipeline进行构建AB包
——BuildPipeLine.BuildAssetBundles(输出AB包路径)
——File.WriteAllLines(将依赖项写入文件里)


4.如何解析版本文件?如何加载AB包资源?具体流程是怎么样的?
1.解析版本文件列表
——File.ReadAllLines(读取文件列表资源路径URL)
——获取资源名称,获取AB包名称,获取依赖项,字典容器存储
——获取Lua文件
2.加载资源
——异步加载资源AB包,AssetBundleRequest请求,AssetBundle.LoadFromFileAsync
——先检查依赖项,再异步加载AB包依赖项
——加载成功后都有对应的回调方法,将资源作为参数传入
在这里插入图片描述


5、热更新方案有哪些?以及具体热更流程
1、整包:存放在上SteamingAssets里
——策略:完整更新资源放在包里
——优点:首次更新少
——缺点:安装包下载时间长,首次安装久
2、分包
——策略:少部分资源放在包里,大部分更新资源存放在更新资源器中
——优点:安装包小,安装时间短,下载快
——缺点:首次更新下载解压缩包时间旧
3、适用性
——海外游戏大部分是使用分包策略,平台规定
——国内游戏大部分是使用整包策略
4、文件可读写路径
——Application.streamingAssestsPath 只读目录
——Application.persistentDatapath 可读写目录
——资源服务器地址URL
5、【从资源服务器】下载单个文件或多个文件
——NetWorking.UnityWebRequest获取URL , HTTP GET , 连接资源服务器
——获取到downloadHander的文件数据Data,完成后会回调方法,将文件Data作为参数传出
6、检查是否初次安装
在这里插入图片描述
在这里插入图片描述


6、网络客户端C# 和 Lua


7、Lua的GC原理是什么?如何避免GC内存泄露?


8、简述Lua实现面向对象的原理???
总结:对象标识、状态、类体系、继承、私有性
1.表table就是一个对象,对象具有了标识self,状态等相关操作
2. 使用参数self表示方法的该接受者是对象本身,是面向对象的核心点,冒号操作符可以隐藏该self参数
3. 类(Class):每个对象都有一个原型,原型(lua类体系)可以组织多个对象间共享行为
4. setmetatable(A,{__index=B}) 把B设为A的原型
5. 继承(Inheritance):Lua中类也是对象,可以从其他类(对象)中获取方法和没有的字段
6. 继承特性:可以重新定义(修改实现)在基类继承的任意方法
7. 多重继承:一个函数function用作__Index元方法,实现多重继承,还需要对父类列表进行查找方法,但多继承复杂性,性能不如单继承,优化,将继承的方法赋值到子类当中
8. 私有性(很少用)基本思想:两个表表示一个对象,第一个表保存对象的状态在方法的闭包中,第二个表用来保存对象的操作(或接口),用来访问对象本身。使第一个表完成内容私有性。

代码举例~~

--类
A={}
B={}
setmetatable(A,{__index=B})
--单继承
function Account:new(o) -- 传入self为Account
    o=o or {}
    self.__index = self --直接把表Account当做元表
    setmetatable(o,self)
    return o
end
--多重继承
local function serach(k,plist) --在父类列表中查找k方法
    for i = 1, #plsit do
        -- body
        local v = plist[i][k] --第i个方法K
    if v then return v end  --V存在
    end
end
setmetatable(c,{__index=function(t,k)
    local v = search(k,parents)  --serach方法(k是第K个,parents是父类列表)
    t[k]=v --将方法保存进t数组里
    return v
end})
--私有性
function newAccount(initBalance)
    local self = {balance = initBalance} --self表示局部表,第一个表用来保存內部状态,私有性
    local withdraw = function(v)
        self.balance = self.balance - v
    end
    local deposit = function(v)
        self.balance = self.balance + v
    end
    local getbalance = function()
        return self.balance
    end
    return 
    {
        withdraw = withdraw, 
        deposit = deposit, 
        getbalance = getbalance
    }   --返回外部對象,方法名進行映射
end

acc1=newAccount(100)
acc1.deposit(10)
print(acc1.getbalance()) --答案是110

9、简述Lua有哪8个类型?简述用途?
nil 空——可以表示无效值,全局变量(默认赋值为nil),赋值nil ,使其被删除
number 整数
table 表 ——
string 字符
userdata 自定义
function 函数
bool 布尔
thread线程

七、实际面试中遇到的问题 (已更新2021.3.8)

以下是面试中,通过各种方式收集到的今年的面试问题,因为各个公司侧重点都不尽相同,只列出题目,不列出答案。
部分答案,上面已包含。

1 、同步的细节处理
2 、BUFF影响,数值回滚
3、 复杂动画转换过渡,融合底层逻辑
4 、曲线运动碰撞检测不到
5、 帧同步,如何侦测不同步,为啥就不同步了
6、 发射子弹的状态同步
7 、状态同步的缺点优点
8、组件系统,组件设计游戏的方式,以游戏驱动的设计模式,ECS架构
9 、技能系统架构

1.值类型和引用类型区别,数组是什么类型?字符串?接口?结构体里有引用类型,引用类型内存在什么地方开辟?结构体呢?有一个Class类,里面有IntIE在哪类型字段,分别在哪?值类型的基类?装箱操作实际发生了什么?
2.在函数里参数传递,值类型和引用类型的区别
3.值类型在函数的传参可以改变,该怎么做
4.装箱和拆箱的区别
5.有无event关键字修饰的delegate有什么区别
6.委托赋值的是实例方法和静态方法的区别,得到的委托有什么区别
7.字符串拼接为什么要用stringbuilder
8.abc将c替换d,abcd会产生哪些字符串
9.List容器的作用和特点
10.dictionary作用和内部实现原理
11.100个元素集合分别用list(key每一个元素的字段)和 dictionary(key),查找元素,两者的时间复杂度
12.泛型是什么
13.ArrayList和List作为泛型,有存储差别吗
14.异常机制
15.using的作用
16.接口和类的继承区别
17.lambda表达式的作用
18lambda什么情况下产生闭包
19.哈希表的实现原理
20.单链表翻转,adcde,转换成 edcba , 怎么做
21、有了解寻路算法?A*算法实现思路?

1.面向对象OOP的特性有哪些?结合具体案例说一下。
2.协程,进程,线程有什么区别,协程能够举个例子吗?
3.冒泡排序怎么做?时间复杂度?
4.二叉树是怎么样的?如果将每一个叶子节点输出?具体算法如何实现?
5.MMO项目,背包系统是如何实现的?
6.MMO项目,道具系统的道具是如何实现的?
7.MMO项目,资源管理是如何实现的?
8.XLua项目里,lua怎么调用C#的?

1.string str = “ ” 和 string str = null 有什么区别?
2.unicode 和 utf-8 有什么区别?
3.有一头母猪,它每年年初生一头小母猪。每头小母猪从第4个年头开始,每年年初也生一头小母 猪。请编程实现在第n年的时候,共有多少头母猪?
4.分析以下代码,完成填空。
string strTmp = “abcdefg某某某”;
int i= System.Text.Encoding.Default.GetBytes(strTmp).Length;
int j= strTmp.Length;
以上代码执行完后,i= 13__ j= 10
5.什么是DrawCall,如何减少DrawCall?
6.如何使Camera只观察指定对象?
7.Leetcode:堆栈实现队列。
8.LeetCode:单链表,输出倒数第2个,奇数个节点输出数据,节点倒序?
9.Lua的基本数据结构
10.lua如何实现面向对象
11.lua的垃圾回收机制原理是怎么样的
12.lua可以做哪些优化?

1.如何实现lua面向对象编程
2.lua里表和元表是什么
3.状态同步是如何实现的
4.状态同步网络卡顿如何解决
5.项目的使用什么架构框架体系?
6.使用Protobuf的优点
– 平台无关,语言无关,可扩展;
  - 提供了友好的动态库,使用简单;
  - 解析速度快,比对应的XML快约20-100倍;
  - 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

1.简述Lua实现面向对象的原理
2.lua中如何避免内存泄露
3.实现一个不规则形状的UI图标(元贴图是方形不透明,请简述可行的方法和原理)
4.在游戏中实现轮廓描边效果的方案及原理
5.简述一种手机UI分辨率适配的方法
6.有数组TArray A ,请写for循坏删除其中值大于0的元素
7.背包系统性能优化方案~

1.n个六面筛子,扔出来,综合大于S的概率
2.战士暴击率6%,打40次,求暴击几率
3.mipmap的作用
4.判断2个平面是否相交
5.单链表,求倒数第二个元素
6.背包道具装备到身上如何实现

🎁🌻🌼🌸 粉丝福利来喽 🎁🌻🌼🌸

  1. 免费领取海量资源 🎁
    简历自测评分表、Unity职级技能表、面试题库、入行学习路径等
  2. 《Unity游戏开发五天集训营 》50个名额 🎁
    我给大家争取到了 50个《游戏开发五天集训营 》名额,原价198,前50个免费
    扫码加入,暗号小听歌
    即可参加ARPG狼人战斗系统、饥荒生存类游戏开发、回合制RPG口袋妖怪游戏等游戏开发训练营
  3. 额外抽奖机会🎁
    参加游戏训练营、还有机会获得大厂老师在线面试指导、或者有机会获得价值1998元的《Unity极速入门与实战》课程

🔻🔻🔻🔻
扫下方二维码,获取游戏开发福利,暗号小听歌 🔻🔻🔻🔻

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注