游戏面试题目

  • 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优化

  • 帧同步和状态同步

    1. 逻辑处理位置。状态同步的核心逻辑写在服务端,帧同步的逻辑写在了客户端。在状态同步中,客户端就相当于服务端的表现层,客户端把数据交给服务端处理完后,客户端按照服务端处理完的数据表现出来。而帧同步中,服务端不做什么逻辑处理,而是转发给所有客户端。

    2. 流量:状态同步下,流量消耗巨大,每发生属性的改变就需要服务端给客户端同步一次,这些消息往往需要分步发。而帧同步,服务端只需要转发就可以饿了

    3. 回放&观看。使用帧同步就会简单得多,只需要保存每个客户端的操作就可以了。而状态同步需要有个回放&观看的服务器,当一场战斗开始时,战斗服务器除了要给客户端发送消息,同时还要给回放&观看服务器发送消息, 存储对局的信息。当由客户端要看的收,回放&观看服务器就会把存储的消息发给客户端。

    4. 安全性:状态同步下能保证公平性,因为所有数据和逻辑处理都是在服务端进行。而帧同步是在客户端进行的,这样就导致相对容易修改客户端的数据

    5. 服务器压力: 状态同步服务器压力大,需要进行很多运算

    6. 开发效率:帧同步开发更简单,而状态同步的功能实现,需要服务器和客户端都要进行实现。

    7. 断线重连:状态同步更简单,重连后,服务端只要把数据发给客户端重新绘制一遍。而帧同步,服务器需要把断线这段时间的操作发给客户端,让他加快运行,直到追上当前时间。

    8. 如果要用到随机数,随机数要相同,所以得自己实现,而且需要服务端发送相同的随机种子。随机种子要用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_getintluaH_getshortstrluaH_getstrluaH_get

      • getint函数涉及到数组和哈希部分,如果key在1到sizearray范围内就在数组部分,否则在哈希部分

      • luaH_getstr他luaH_get最终可能调用到getgeneric这个函数,这个函数也只是查找哈希部分,是先找到主位置,如果发生冲突就去加上next取下一个节点

    • 设值,可能会涉及到重建table。对外接口有luaH_setluaH_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,如果开启列表或者关闭列表有的点就不用遍历,完成后,根据消耗公式排序整个开启列表,找到最小的放入关闭列表,父对象是起点。

不断循环,直到判断加入关闭列表的点就是终点,寻路结束。此时将关闭列表的点按照父对象的往回输出,就是最终路径。

  • 为什么不能直接把关闭列表全部输出呢?因为,如果中途有阻挡,很有可能出现刚开始的路径是错误的,然后最后循环得到正确路径,整个列表输出就会得到不相关的点。

  • 什么时候终点不可到达? 如果不可到达,起点列表无法添加新的节点,并且会不断添加进终点列表。最终起点列表为空

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值