-
CPU和GPU的工作是什么?
-
渲染管线中几何阶段CPU主导还是GPU主导,该阶段主要做什么? GPU主导,对顶点、法线等模型数据的相关处理,对他们进行坐标空间转换,裁剪画面外图元等,还可以进行顶点着色,为每个顶点计算颜色、纹理坐标、发现等属性
-
是不是所有矩阵都有逆矩阵,如何计算一个矩阵的逆矩阵?
-
齐次坐标是什么?有什么用?把n维的向量或矩阵n + 1维,可以明确区分向量和点,能表示出平移变换
-
shader开发中的坐标空间变换,主要在那几个空间中进行变换?变换顺序是什么?主要将模型相关数据在模型空间 -》 世界空间 -》观察空间 -》裁剪空间 -》 屏幕空间之间变换
-
合批(前提是材质相同)
-
为了减少cpu向gpu提交渲染命令(drawcall)的次数,减少gpu渲染状态切换次数,前提是gpu相对空闲,cpu把更多时间花在提交drawcall
-
drawcall消耗性能的原因,命令从runtime到驱动,cpu要发生用户模式到内核模式的切换
-
合批方式
-
离线合批,在运行前用工具进行资源处理
-
实时合批
-
静态合批,勾选static,空间换时间,不会降低drawcall在build时候,自动生成合并的网格,然后存储到文件中。当运行时,一次性提交合并模型的顶点数据,设置一直渲染状态,多次调用drawcall绘制模型。因为我们预先把模型的顶点数据转换到世界空间,并且这些模型共享材质,调用drawcall时没有发生渲染状态的切换,渲染API会缓存绘制命令,起到了优化效果,并且运行时,顶点位置处理不需要再计算。
-
动态合批,把共享材质的模型顶点变换到世界空间,通过一次drawcall绘制多个模型,顶点变换由cpu完成,会带来cpu性能消耗
-
-
-
-
渲染管线
- 应用阶段、几何节点、光栅化阶段
-
UI和模型的渲染过程
-
DrawCall Batches setPass Call是什么 setpasscall是shader中渲染通道的数量
-
降低延迟感受
-
UI批处理
-
原理
-
批处理规则
-
以Canvas为单位不包含子Canvas,合批操作在子线程
-
第一步,剔除不必渲染的Canvas
-
第二部,计算Canvas各个UI控件的深度
-
Depth深度计算规则,(这个深度与image的深度不一样)
-
按Hierarchy中从上到下的顺序遍历UI
-
对于当前UI
-
如果不渲染, Depth = -1
-
如果渲染,但与下面没有其他UI元素相交,Depth = 0
-
如果要渲染,但与下面元素由一个相交,如果可以合批(材质贴图完全相同),那么Depth相同,不过不相同,Depth = Lower + 1;
-
如果要渲染,而且与多个相交,求与每个UI计算出的Depth取最大值
-
这里的下面,指的是Hierarchy面板中在CurrentUI上面的UI,也就是后渲染的UI
-
相交:指的是网格相交,不是两个UI的Rect相交
-
根据Depth materialID TextureID RenderOrder(UI队列顺序)排序,Depth 相同则按材质id排,材质相同则按纹理ID排, 纹理ID相同则按UI顺序排,都是从小到大排序,得到VisibleList
-
得到VisibleList后,判断相邻元素是否能合批,此时不考虑Depth。合批是将同一Canvas下多个UI的网格合并在一起,如果其中任何一个元素的材质、网格顶点、位置(Transform)甚至颜色或者在该Canvas下动态创建或删除UI元素都将导致该Canvas重新计算合批(需要注意的是仅仅会影响这一个Canvas,子Canvas或父Canvas以及其他Canvas不会重新计算),重新生成新的网格,这个重新计算生成网格的过程被称为rebuild。所以,这也是为什么做UI提倡动静分离(动态部分和静态部分分别用不同的Canvas),层级尽量减少(层级多了,重新计算更耗时)的原因。
-
-
-
-
-
优化
-
使用图集
-
动静分离(动态部分和静态部分分别使用不同的Canvas)
-
Text如果可以用图片代替就用图片代替
-
避免频繁删除/增加UI对象,UI层次结构变化会引起Canvas的更新(rebuild)
-
避免UI元素数目过多和层次结构过于复杂影响Batch更新速度
-
尽量不要使用Mask(其内部使用了模板缓冲,至少会造成增加2个Draw Call)
-
-
overdraw
-
内存和gpu优化
-
帧同步和状态同步
-
逻辑处理位置。状态同步的核心逻辑写在服务端,帧同步的逻辑写在了客户端。在状态同步中,客户端就相当于服务端的表现层,客户端把数据交给服务端处理完后,客户端按照服务端处理完的数据表现出来。而帧同步中,服务端不做什么逻辑处理,而是转发给所有客户端。
-
流量:状态同步下,流量消耗巨大,每发生属性的改变就需要服务端给客户端同步一次,这些消息往往需要分步发。而帧同步,服务端只需要转发就可以饿了
-
回放&观看。使用帧同步就会简单得多,只需要保存每个客户端的操作就可以了。而状态同步需要有个回放&观看的服务器,当一场战斗开始时,战斗服务器除了要给客户端发送消息,同时还要给回放&观看服务器发送消息, 存储对局的信息。当由客户端要看的收,回放&观看服务器就会把存储的消息发给客户端。
-
安全性:状态同步下能保证公平性,因为所有数据和逻辑处理都是在服务端进行。而帧同步是在客户端进行的,这样就导致相对容易修改客户端的数据
-
服务器压力: 状态同步服务器压力大,需要进行很多运算
-
开发效率:帧同步开发更简单,而状态同步的功能实现,需要服务器和客户端都要进行实现。
-
断线重连:状态同步更简单,重连后,服务端只要把数据发给客户端重新绘制一遍。而帧同步,服务器需要把断线这段时间的操作发给客户端,让他加快运行,直到追上当前时间。
-
如果要用到随机数,随机数要相同,所以得自己实现,而且需要服务端发送相同的随机种子。随机种子要用int类型的。
-
-
astart算法
-
卡顿问题
-
常见的装箱操作,装箱引起的问题
-
堆和栈的概念,堆和栈的内存空间,分布布局
-
栈是向地位地址扩展的数据结构,是一块连续的内存的区域,即开始是固定的内存大小,如果没有空间就会报StackOverflow,const局部变量也在栈里。由系统自动分配和销毁
-
静态变量和全局变量存放在可读写区
-
堆的内存空间不连续,空闲的内存空间地址由链表记录,由低位到高位扩展,堆大小受限于计算机的虚拟内存。由用户销毁
-
c#分配对象,new之前先在栈中分配地址空间,new出来后,才在堆中分配空间。定义的局部变量会放在栈中,执行完函数后,会自动释放,堆中分配的内存通常由垃圾回收机制处理
-
引用变量,指向堆的空间地址,存放在栈中。new出来的堆空间,只有没有东西引用他了,才会被看成垃圾,不能再使用,后续由垃圾回收器回收
-
区别:
-
管理方式:栈由系统自动分配和销毁,堆的释放需要人手动
-
空间大小:栈往往固定1M 或 2M, 堆由虚拟内存限制
-
是否产生碎片:栈不会残生内存碎片,堆的内存不连续就会造成由大量碎片,是程序效率低
-
生长方向:栈往下生长,堆往上生长
-
分配方式:堆是动态分配的, 栈可以动态也可以静态
-
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持。分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
-
-
-
反射性能消耗
-
生命周期函数:
-
Awake :在创建出实例时调用,只会调用一次,如果对象一开始没被激活,则会激活后调用
-
OnEnable:对象被激活时调用
-
对于添加到场景中的对象,在为任何对象调用 Start 和 Update 等函数之前,会为_所有_脚本调用 Awake 和 OnEnable 函数
-
Reset:调用 Reset 可以在脚本首次附加到对象时以及使用 Reset 命令时初始化脚本的属性。
-
Start:启用脚本实例后,第一次帧更新前,调用,对于添加到场景的对象,将在所有脚本调用start
-
OnApplicationPause:在帧的结尾处调用此函数(在正常帧更新之间有效检测到暂停)
-
更新
-
FixedUpdate:调用fixedupdate频率常常超过update,帧率较低时,能多次调用,帧率高时,可能在帧之间不调用。因为它是基于可靠的计数器实现,独立于帧数。fixedupdate之后马上调用物理计算和更新,fixedupdate的计算不用乘以time.dealtime
-
Update:之后一般会调用游戏逻辑
-
lateUpdate:每帧调用,在update执行完之后执行,常用于摄像机跟随的,之后进行的时渲染相关的逻辑
-
-
-
Unity跨平台编译:
-
Mono:通过msc把c#代码转成中间语言IL,因为Mono支持CLI中间语言架构,所以Mono 虚拟机能够运行中间语言IL,运行过程中,把中间语言IL转成原生码Native Code
-
IL2CPP:得到中间语言后,利用IL2CPP会转成c++代码,让每个系统平台的C++编译器转成原生码,因为即使转成c++,但是内存管理这块还是遵循c#,所以需要一个IL2CPP VM:负责GC 线程管理。由于禁止jit即时编译,所以全都是AOT 提前编译,而C++又是可以跨平台的执行的
-
-
协程和线程的优缺点
-
线程无法访问unity进程的成员
-
协程可以yield return 相关地方
-
-
List 和 Dic内部结构
-
全局变量太多会有什么坏处
-
命名冲突
-
代码脆弱
-
难以测试
-
-
软件设计规则SOLID
-
-
单一职责原则(Single Responsibility Principle
-
开闭原则(Open Closed Principle);
-
里氏替换原则(Liskov Substitution Principle);
-
迪米特法则(Law of Demeter),又叫“最少知道法则”;
-
接口隔离原则(Interface Segregation Principle);
-
依赖倒置原则(Dependence Inversion Principle)。
-
-
-
锚点和pirvot的区别
-
[锚点](Anchor Point):锚点是用于控制 UI 元素在屏幕上的位置和大小的点。在 Unity 中,锚点通常用于控制 UI 元素的布局和缩放。锚点的位置相对于父物体或者画布来确定,可以通过修改锚点的属性来改变 UI 元素的位置和大小。
-
轴心点(Pivot Point):轴心点是用于控制物体旋转和缩放的点。,轴心点通常用于控制 3D 模型的旋转和缩放。轴心点的位置相对于物体的局部坐标系来确定,可以通过修改轴心点的属性来改变物体的旋转和缩放。
-
-
UI动画
-
update 和 fixedupdate
-
XLua怎么去热更新
-
反编译
-
资源的优化和格式的优化
-
UI画布动静分离,是为了减少什么
-
drawCall包含什么信息
-
碰撞算法有哪些
-
AABB:根据物体三个轴的x y z的最大最小,生成包围盒。碰撞检测,根据两个物体的包围盒是否发生重叠,不可旋转
-
OBB:最小有向包围盒,可以旋转
-
-
unity状态机 动画的切换
-
对新手引导的优化
-
unity导航系统了解多少
-
a星算法实现过吗
-
如果要改一个刚体的旋转移动,在哪里执行呢
-
用的最多的设计模式?单例模式的懒汉和饿汉的区别?对于线程方面哪个是线程安全的
-
懒汉模式线程不安全,除非加锁,加了锁执行效率会变低
-
饿汉模式线程安全,但是占内存
-
懒汉双重校验锁
-
-
勇者斗恶龙,小工具的开发
-
平时有什么demo吗
-
打印机安装如何判断安装对了?碰撞检测
-
FPS如何判断 子弹是否打到人身上?子弹很慢,子弹还没到人就跑了,或者子弹很快,还没有帧之间的碰撞检测没有检测到,如何判断呢
-
设置刚体的碰撞检测为连续检测,条件是:
-
刚体和刚体,同样都是连续检测的,碰撞器必须是box 、sphere、Capsule
-
刚体和静态碰撞器:刚体的碰撞器必须是box sphere capsule,静态碰撞器是mesh
-
-
-
UI Button组件改写
-
美术资产使用
-
unity骨骼动画
-
商业战斗系统
-
动态避障算法
-
场景流加载
-
动画混合原理
-
正向和逆向运动学,IK算法
-
委托内不小心添加两个相同的方法,怎么解决? 可以在+方法前 ,减去该方法,如果委托没有这个方法,减去不会报错的,只有为空时才会报错
-
Unity生命周期函数原理
-
Unity性能优化
-
资源包依赖问题
-
C#闭包概念,什么时候会产生闭包
-
是否了解C#中unsafe用法
-
编译方式是否是il2cpp
-
值类型与引用类型的区别?哪些数据类型是值类型,哪些是引用类型
-
说下lua的基础数据类型,lua如何写面向对象
-
UI之间的粒子特效渲染如何控制层级
-
ab包打包策略
-
如果一个ab包频繁被其他ab包引用,依赖它的ab包卸载时,是否卸载这个底层ab包
-
animator分层动画,animator组件的拥有物体太多时有什么样的性能问题
-
overrideAnimator在什么情况下用到
-
没重写update的MonoBehaviou实例的update函数会不会被调用?如何实现的?
-
ugui的layout组件实现原理以及优化点
-
无限滑动列表如何实现
-
gc的性能耗在哪里?
-
遍历托管堆的所有对象,标记和清除不需要的对象,压缩和整理剩余对象,会占用cpu时间
-
触发对象终结器,用于释放非托管资源,终结器执行顺序和时间不确定,可能会延长GC时间
-
GC会分代回收,虽然提高了GC的效率,但是增加了GC的复杂度和开销
-
如果频繁发生创建和销毁大量或大规模的对象,会产生更多内存碎片
-
-
怎么减少GC
-
避免不必要的对象分配和销毁,尤其是循环或高频调用的方法中。可以使用缓存池
-
避免使用终结器,使用IDispose显式调用释放非托管资源
-
避免使用大型对象堆,使用数组池和内存流
-
-
为什么有GC还要有IDispose呢。因为GC回收的是托管资源,对于不是托管资源的无法识别,IDispose提供了一种通用的机制来进行清理。非托管资源有:文件句柄,在操作系统中打开的文件。数据库链接,与数据库服务器建立的连接。网络连接,比如socket连接对象。unsafe关键字使用的指针。
-
gc后引用数据的地址会不会改变?如何保持不变
-
unity重要的生命周期说一下
-
unity如何调用它的生命周期
-
说说什么情况下会用到状态机
-
状态机、行为树、以及复合型的AI原理
-
除Astar之外还了解哪些寻路算法
-
如何优化Astar算法
-
A*寻路原理,和dijkstra有什么区别
-
前者有启发函数,有可能可以更快地找到目的地的最短路径
-
后者可以找一个节点到所有节点的最短路径,而a星只关注从一个到一个或多个
-
-
a星算法一定能找到最短路径吗
-
前提是启发函数的选择,启发函数是用来估算当前节点到目标节点的代价函数,需要满足
-
一致性:两个相邻的点,有h (n)<=c (n, m)+h (m),其中c (n, m)是从n到m的实际代价。(两边之和大于第三边?)
-
无偏性:对于任意一个点,有h (n)<=h* (n),其中h* (n)是从n到目标节点的实际最短代价。
-
-
-
tcp和udp,各个类型的游戏用了哪种协议,为什么?
-
lua table的底层了解过吗
-
是数组和哈希表的混合体,哈希表是一个node数组采用链地址法
-
Node包括TKey 和TValue, Tkey多了一个next字段用于把冲突节点连接起来
-
新建Table luaH_new函数创建一个空表, 数组和Node的部分初始为空
-
取值
luaH_getint
,luaH_getshortstr
,luaH_getstr
,luaH_get
,-
getint函数涉及到数组和哈希部分,如果key在1到sizearray范围内就在数组部分,否则在哈希部分
-
luaH_getstr他luaH_get最终可能调用到
getgeneric
这个函数,这个函数也只是查找哈希部分,是先找到主位置,如果发生冲突就去加上next取下一个节点
-
-
设值,可能会涉及到重建table。对外接口有
luaH_set
和luaH_setint
。setint是要处理数组的情况,里面有个getint,如果找到了对应key的值,那么直接可以设,否则需要调用luaH_newkey新建key -
luaH_newkey,主要功能是将key插入到hash表中,返回关联的value
-
首先算出主位置,如果主位置是空,直接将key设置,然后返回
-
如果主位置不为空,根据lastfree空闲指针,设置进去,更新next,和主节点连接
-
如果不是主位置节点,并且被占用了,那么就把节点移到空闲位置,然后设置key'到这个节点
-
-
getfreepos函数用于找空闲结点,Table结构中有一个lastfree变量,它刚开始指向结点数组的最后,getfreepos使lastfree不断向前移,当移动到最前面,即没有空闲节点,需要重建table
-
重建table
-
rehash函数要确定有多少整型key,并决定这些整型key有多少值放到数组部分去,然后剩下的值放到哈希部分,最后有可能会缩减空间,也可能会扩大空间。
-
数组尺寸和哈希尺寸是2次幂,且放入数组部分的key数量要超过数组长度的一半
-
得到数组新长度和哈希新长度时,开始重建luaH_resize
-
如果数组长度变小了,需要把多余的移到哈希表中
-
把老的哈希表内容设置到新的
-
-
-
遍历,根据key先遍历数组,再遍历哈希表,数组中key一直+1遍历,哈希部分需要通过key找到node数组位置往后遍历,要防止table中有多个nil值
int luaH_next (lua_State *L, Table *t, StkId key);
-
-
lua 如何实现一个类,如果父类子类都有同名的变量,怎么给父类的变量赋值
-
设计模式接触的多吗
-
单例模式的优缺点
-
数组在内存是怎么存的,在栈和堆是怎么存放的
-
对string,在栈和堆是怎么分配的,为什么用string会比较差,为什么他会重新new一个
-
lua 元表是什么
-
MVC是什么?
-
model,负责模型持有所有的数据、状态和程序逻辑。模型接受视图数据的请求,并返回最终的处理结果。它处理业务逻辑
-
view,负责界面的显示,以及与用户的交互功能,例如表单、网页等
-
controller,分发器,决定视图发来的请求由哪一个模型来处理
-
-
MVP是什么
-
presenter,作为中间人处理业务逻辑
-
model,负责数组的获取操作
-
view,负责页面的显示
-
-
OPP 面向过程 是一种以事物为中心的编程思想。主要关注“怎么做”,即完成任务的具体细节。它考虑的是实际地实现。比如要完成一个呼吸的方法,实现具体的呼吸内容就是OPP
-
OOP 面向对象 以对象为基础,关注的是谁来做
-
ab包怎么管理,打包要干啥,下载怎么用,怎么解决包依赖,lua脚本怎么更新,怎么对比包是否需要更新
-
单例模式作用,使用单例和只创建一个static对象有什么区别
-
作用:可以避免对资源的多重占用;可以控制实例的数量;可以在全局范围内共享数据
-
区别:单例可以延时加载,静态在初始化时就会被初始化;单例模式可以继承类和实现接口;
-
单例的缺点:不适用于要变化的对象;没有抽象层,对单例的扩展需要修改源代码;单例类的职责更重,不仅需要负责自己的业务,还要负责自己的实例化过程;单例的滥用,线程安全
-
改进:使用懒汉加载,使用对象池
-
-
Unity的内存泄漏
-
Mono内存泄漏,是基于C#的GC机制,忘记清楚未使用的引用,导致无法回收这些对象所占的内存。unity使用的Mono Gc是贝姆垃圾收集器,可能会产生内存碎片,Mono堆内存的占用只会增加,每次扩建都需要申请更多的内存。
-
Native内存泄漏,是指加载和使用资源时,没有卸载不需要的资源,导致资源占用的内存无法释放。Unity的资源是通过c++分配在native堆上,需要主动释放,Resources.UnloadUnuseAssets 和 Resources.UnloadAssets释放资源
-
-
如何实现计时任务
-
c#静态字段的初始化会比静态构造方法的执行要早
-
C#调用tostring方法时,传入字符串参数,有哪些格式转换规则?
-
Fn,n表示保留几位小数
-
E,用科学计数法表示
-
X,16进制表示
-
P,百分比表示
-
Dn,n表示结果字符串长度至少为n位,不足则添0
-
C,货币格式
-
N,“数字格式表示”带逗号的哪种
-
-
C#中ulong可以表示最大多少,BigInteger有什么意义?
-
ulong表示2的64次方-1
-
BigInteger可以使用任意大小的数字,当所有数字类型都不能满足我们的需求时
-
-
C#数组int转化为string 数组
-
遍历
-
Array.convertall
-
LINQ
-
-
为什么使用单例模式比静态类要多
-
单例可以延迟实例化
-
单例可以继承,扩展
-
-
项目
-
scrollview 有做什么优化吗
-
用什么寻路算法,优化,还有别的寻路吗
-
单例模式不能减少内存
-
登录的密码有做加密吗
-
服务器有什么表
-
打图集能提升多少性能,有没有用工具测试数据
-
项目支持多语言吗,采用什么编码格式,怎么区分查表是日语还是中文
-
对话加入语音效果了吗
-
邮件里面有没有带url
-
有没有做文字滚动
-
多线程如果避免开销
-
动态库和静态库的区别
-
静态库编译时会拷贝一份,直接被加载到程序项目中,不会被改变,但是会让程序体积变大
-
动态库编译时不会直接加载到项目中,目标程序只会存储动态库的引用,程序运行时才会加载进去。可以被多个程序引用,运行才加载的特性,可以随时替换库,而不需要重新编译。但会带来一部分性能消耗,程序会依赖外部环境,环境不对会导致程序无法运行
-
MVP框架描述一下
-
用到了什么设计模式
-
新手引导怎么设计的,如何判断进入到指定状态的,是有一个的工具吗,你做了什么
-
动态和静态图片都打了一个图集吗?动静分离,比如背包系统不太适合用
-
是否用了lua
-
怎么管理单例
-
-
-
反射的原理是什么? 当程序被编译器编译成exe或者dll后,即程序集,程序集里面由IL和metadata 组成,IL是我们写的代码,方法体,而metadata(元数据)是编译中生成的,记录了里面的方法名、类和成员变量以及特性等,反射就是读取元数据的数据。
-
游戏开发中和开发完成打包后,如果进行资源的管理? 感觉这道题问的是打包后可读可写的那个文件夹persistentpath
-
对于场景中UI的优化,内存优化?对象池、取消掉一些不交互的图片的raycast ,避免频繁调用setActive 用修改layer或移动位置来代替
-
排行榜如何实现?
-
A
计算最短路劲,扫周围的点,排除边界和阻挡点,找出最优点
概述:不停地找周围的点,选出一个点继续往下循环
原理
-
消耗公式: f = g(离起点的距离) + h(离终点的距离)斜边上的距离算作根号2
-
开启列表
-
关闭列表
-
格子对象的父对象
-
开始先把起点放入关闭列表,起点的父对象为null。遍历一圈,忽略点阻挡以及边界,把所有点记录到开启列表中,根据消耗公式排序,把最小的点放入关闭列表,他的父对象是起点。
最小的点作为新的起点,再遍历一圈的点,起点距离 = 该点离起点的距离 + 起点的g,如果开启列表或者关闭列表有的点就不用遍历,完成后,根据消耗公式排序整个开启列表,找到最小的放入关闭列表,父对象是起点。
不断循环,直到判断加入关闭列表的点就是终点,寻路结束。此时将关闭列表的点按照父对象的往回输出,就是最终路径。
-
为什么不能直接把关闭列表全部输出呢?因为,如果中途有阻挡,很有可能出现刚开始的路径是错误的,然后最后循环得到正确路径,整个列表输出就会得到不相关的点。
-
什么时候终点不可到达? 如果不可到达,起点列表无法添加新的节点,并且会不断添加进终点列表。最终起点列表为空