This page looks best with JavaScript enabled

Interview unity

 ·  ☕ 14 min read

assetBundle.Unload true 和false 区别

当传入的参数为true,则不仅仅内存中的AssetBundle对象包含的资源会被销毁。根据这些资源实例化而来的游戏内的对象也会销毁。

当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源。

Unity GC 机制

战场优化

模型预创建预加载 动态图集 光效粒子 屏幕内外 自定义UIMesh GPU Instancing, 容器扩容优化,遍历容器,线程安全容器, 字符串拼接(图片字),

寻路 A* B* DBFS

物理碰撞,静态碰撞。动态碰撞RVO

动态图集的优化原理是什么

所谓动态图集就是没有办法静态生成的,需要在运行时动态生成的图集,那么我们为什么需要动态图集?

动态图集是为了解决游戏中动态图片太多的问题,也就是我们没有办法预先放在UI上的。下图案例中可以看到右下角的英雄技能图标、天赋技能图标,以及主动使用的物品图片,均为动态加载。左上角的英雄头像也是动态加载,而且由于技能之类的图片太多(毕竟有几十个英雄),所以没有办法打成一张静态图集。而如果作为独立图片动态加载,就会多十几个DrawCall。即便是打成多张静态图集,也会导致UI渲染的批次被打断。

解决方案:用动态打图集的方式。因为我们没有Unity源码,所以图集的分块算法参考了这个开源项目 http://davikingcode.com/blog/unity-generate-spritesheets-at-runtime/,这个算法效率比较不错,建议大家可以研究一下,它的分块算法的思路上本质上类似于BSP。

大图集是在游戏Loading时获得动态图片,然后把这些动态图片渲染到RenderTexture上,用GPU的方式来做可以保证加载的效率。在游戏中,英雄头像使用了一张256x256的RenderTexture,而英雄技能、天赋技能和物品图标使用了一张512x512的RenderTexture。这样一来,技能面板动态图标的消耗从12个DrawCall降低到1个DrawCall。而英雄头像部分,从最多9个DrawCall降低到2个DrawCall,这个结果是因为敌我双方英雄头像使用的材质不同。实际操作中,技能面板的动态图片放在同一个层级里,这样就只有1个DrawCall,上面的蒙板、边框零散图片打成静态图集,在不出现穿插的情况下,UGUI也会协助合批。因此通过这种方式大量减少了DrawCall。后面讲到的一些点其实也用到了动态图集。

渲染流水线的原理

渲染流程,可以分为三个阶段。应用阶段,几何阶段,光栅化阶段

ecs 优缺点,和mvc这些相比。 为啥选ecs

mvc 面向对象,(继承,多态,封装)高度耦合, 一个英雄charactor包含了 属性,状态,控制器
ecs 面向数据:(组合模式)推崇组合优于继承理念,函数式编程,system只对他关系的component负责。业务上更加专一。遍历内存上更加高效(保证内存的连续性),业务拆分的越细 代码复用率越高

帧同步

浮点类型,多线程,随机种子,静态变量,全局变量,容器顺序需要确定性

内存和虚拟内存的区别

指的是把硬盘中的一部分空间用来当做内存使用,虚拟内存的作用:是为了解决计算机在运行较大的程序时内存不足的情况,虚拟内存是在硬盘上的,它的速度要比内存慢的多,虚拟内存其实就是为了运行很大的程序的一种妥协的办法,妥协了软件的运行速度。

子类为什么可以赋值给基类对象

基类的指针可以指向派生类对象,但是反过来则不行,派生类的指针不可以指向基类的指针。这是为什么呢?这是因为派生类的对象所占的存储空间通常要比基类的对象大,原因就是派生类除了继承基类的成员之外,还拥有自己的成员,所以基类的指针操作派生类的对象时,由于基类指针会向操作基类对象那样操作派生类对象,而基类对象所占用的内存空间通常小于派生类对象,所以基类指针不会超出派生类对象去操作数据。
同样的道理,基类的引用可以作为派生类对象的别名,但是反过来则不行,派生类的引用不可以作为基类对象的别名。

自定义的UI Mesh

构造出来的Mesh使用一个单独的正交摄像机来绘制,
在UI Mesh的构造函数中可以看到是创建了一个GameObject,附加MeshFilter和MeshRenderer,然后再做一些初始化的工作。
重点在于自行填充Mesh的三个Buffer:位置、UV和索引。另外为了避免在运行时重复申请内存,在初始化的时候要申请足够多的顶点。
在实际游戏中用到了多个UI Mesh,总体的顶点数大概在3000左右。
初始化Mesh之后,还要去维护顶点Buffer。一个小兵的血条包含背景底框和前景血条,2个矩形8个顶点,在游戏中去动态地改变这8个顶点的位置。如果某个Actor不在视野中,那么把它所有顶点坍缩到一个点就不显示了。另外,Actor死亡的时候,并不删除它的数据,而是先设置为不显示,然后缓存起来准备复用。也就是说无论整场战斗创建了多少个角色,实际上血条都是在这个Mesh的Buffer里不断复用。

(.net)装箱拆箱的概念

装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的过程. CLR 对值类型进行装箱时,会将值包装在 System.Object 实例中并将其存储在托管堆中。
拆箱(取消装箱)将从对象中提取值类型。 装箱是隐式的;取消装箱是显式的。
int i =1;
object o = i; //装箱
i = (int)o; //拆箱

(3D数学)4元数的做用是什么? 相比欧拉角的优点有哪些?

图形学用4元数表示旋转.

  1. 解决万向节死锁问题; 2) 四元数方便插值, 求逆运算

(图形学)深度缓冲区(Depth Buffer)是什么? 有什么作用? 模板缓冲(stencil buffer)是什么, 有什么作用?

深度缓冲区(或 z 缓冲区)存储深度信息,以控制渲染哪些多边形区域。用于决定不透明物体是否被绘制.
模具缓冲区用于遮罩图像中的像素,以产生特殊效果。 掩码控制是否绘制像素。 特殊效果包括合成、贴纸、溶解、淡化、滑动、轮廓描绘和剪影, 模板缓冲区逐个像素地启用或禁用渲染目标图面绘制。 究其本质,它使应用程序遮罩部分渲染图像,因此这些部分不会显示。 应用程序常常使用模板缓冲区实现特殊效果,例如溶解、贴纸和轮廓描绘。

(图形学)纹理是什么? Unity常用的纹理类型有哪些? 常用的纹理压缩格式有哪些?(列举3种)

纹理是为图形对象(mesh)提供纹理外观的像素颜色的位图. 位图资源(jpeg, png) 加载到引擎后变为纹理资源, 纹理资源是存储纹素的数据结构, 纹素是可以读取或者写入纹理的最小单位. 在着色器读取纹理时, 可以通过采样器对纹理进行筛选和读取. 纹理有1d纹理, 2d纹理和3d纹理. 纹理经常包括若干层级的mipmap.
类型有 Default, Sprite(2D andUI), NormalMap, EditorGUI, Ligthmap, Cookie

windows下有 DXT5

Android系统下常用 ETC1, ETC2,

iOS 常用 PVRTC

(图形学) UV坐标是什么?

//todo: 待完善.

uv坐标是归一化后(Normalized)的纹素坐标.

??

(图形学)MipMap是什么,作用?

MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。

动态加载资源的方式?

1.Resources.Load();
2.AssetBundle
AssetBundle相关
在通过AssetBundle.Unload(false)卸载AssetBundle对象后,如果重新创建该对象并加载之前加载过的资源到内存时,会出现冗余,即两份相同的资源。
被脚本的静态变量引用的资源,在调用Resources.UnloadUnusedAssets时,并不会被卸载,在Profiler中能够看到其引用情况。

unity3d从唤醒到销毁有一段生命周期,请列出系统自己调用的几个重要方法?

答:Awake —> Start —> Update –> FixedUpdate –> LateUpdate —>OnGUI –>Reset –> OnDisable –>OnDestory;

什么是协同程序?

在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,Unity的协程是在每帧结束之后去检测yield的条件是否满足。

LOD是什么,优缺点是什么?

LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。缺点是增加了内存。

什么叫动态合批?跟静态合批有什么区别?

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。
静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了
动态合批的限制条件呢?

Unity中的合批

静态合批 Static Batching

Unity中把物体标记为Static,然后开启静态合批
限制
    需要保持static,不能改变transform
    使用相同材质的物体才能合批
    一个批次上限为~15k个顶点 

动态合批 Dynamic Batching

Unity自带动态合批,需要在Unity中开启动态合批选项
会导致cpu消耗,如果不是gpu有瓶颈,最好关闭动态合批
前提
使用顶点位置、法线、UV0、UV1和切线为一个着色器提供180个顶点
使用顶点位置、法线和单一UV的着色器的300个顶点

材质球相同;

Mesh顶点数量不能超过300以及顶点属性不能超过900;

缩放不能为负值(x、y、z向量的乘积不能为负)等。

静态合批的利弊

静态合批采用了以空间换时间的策略来提升渲染效率。

其优势在于:网格通常在预处理阶段(打包)时合并,运行时顶点、索引信息也不会发生变化,所以无需CPU消耗算力维护;若采用相同的材质,则以一次渲染命令,便可以同时渲染出多个本来相对独立的物体,减少了DrawCall的次数。
在渲染前,可以先进行视锥体剔除,减少了顶点着色器对不可见顶点的处理次数,提高了GPU的效率。

其弊端在于:合批后的网格会常驻内存,在有些场景下可能并不适用。比如森林中的每一棵树的网格都相同,如果对它采用静态合批策略,合批后的网格基本等同于:单颗树网格 x 树的数量,这对内存的消耗可能就十分巨大了。

总而言之,静态合批在解决场景中材质基本相同、网格不同、且自始至终都保持静止的物体上时,很适用。

动态合批与静态合批的区别

1、动态合批不会创建常驻内存的“合并后网格”,也就是说它不会在运行时造成内存的显著增长,也不会影响打包时的包体大小;

2、动态合批在绘制前会先将顶点转换到世界坐标系下,然后再填充进顶点、索引缓冲区;静态合批后子网格不接受任何变换操作,仅手动合批后的Root节点可被操作,因此静态合批的顶点、索引缓冲区中的信息不会被修改(Root的变换信息则会通过Constant Buffer传入);

3、因为2的原因,动态合批的主要开销在于遍历顶点进行空间变换时的对CPU性能的开销;静态合批没有这个操作,所以也没有这个开销;

4、动态合批使用根据渲染器类型分配的公共缓冲区,而静态合批使用自己专用的缓冲区。

什么是动静分离

操作:讲可以可以活动的元素放在一个CanvasA下,不可活动的元素放在CanvasB下。

原因:Canvas下某一个元素变化时,Canvas下的所有元素都会被网格重建(Rebatch),大量的静态元素是不需要网格重建的。

得失:

优:减少了大量静态元素反复网格重建的消耗

劣:打断了合批

什么是网格重建

工作:

ReBatch: 重算Canvas下所有元素变换

Rebuild: ReBatch中标记的元素更新自己

6大OO设计原则?

1.开闭原则
2.单一职责原则
3.依赖倒置原则
4.接口隔离原则
5.迪米特法则
6.里氏替换原则

什么是里氏代换原则?

里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。通俗点:就是子类对象可以赋值给基类对象,基类对象不能赋值给子类对象

你能说出几种创建型模式
抽象工厂
建造者
工厂
原型模式/克隆模式
单例模式

简述MVC、MVP、MVVM三种模式

你有了解过多少种软件的分层结构

三层架构、六边形、洋葱架构、整洁架构

在编辑场景时将GameObject设置为Static有何作用?

设置游戏对象为Static时,这些部分被静态物体挡住而不可见时,将会剔除(或禁用)网格对象。因此,在你的场景中的所有不会动的物体都应该标记为Static。

如果你在游戏中编写一个类,不想让其他同事继承这个类,你会怎么办?
在类声明时与函数声明时的作用sealed修饰的类为密封类,类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。

如何理解委托?

委托类似于 C++ 函数指针,但它是类型安全的。委托允许将方法作为参数进行传递。委托可用于定义回调方法。委托可以链接在一起;例如,可以对一个事件调用多个方法。方法不需要与委托签名精确匹配。

GC是什么? 为什么要有GC?
GC是垃圾收集器。程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一: System.gc() Runtime.getRuntime().gc()

死锁的必要条件?怎么克服?

系统的资源不足,进程的推进的顺序不合适,资源分配不当,一个资源每次只能被一个进程使用,一个资源请求资源时,而此时这个资源已阻塞,对已获得资源不放,进程获得资源时,未使用完前,不能强行剥夺。

C#是否可以对内存直接进行操作?

C#是可以对内存进行直接操作的,虽然很少用到指针,但是C#是可以使用指针的,在用的时候需要在前边加unsafe,,在.net中使用了垃圾回收机制(GC)功能,它替代了程序员,不过在C#中不可以直接使用finalize方法,而是在析构函数中调用基类的finalize()方法。

TCP、UDP协议在OSI七层模型和SI七层模型和TCP/IP四层模型中分别属于哪一层的网络协议?

都是属于传输层的网络协议。传输层提供了应用程序之间的通信。
如何实现可靠UDP?

UGUI性能优化

1.动静分离,将程序会动态设置的组件跟静态组件分离
2.文本与图片穿插编排会打断DrawCall
3.静态合并图集,降低DrawCall
4.对于复杂的场景,可以考虑动态合并图集,降低DrawCall
5.使用多个CanvasRender,例如一个界面一个CanvasRender
6.降低Mesh重建次数
7.隐藏物件可以使用Scale=0, 移动到非渲染层级,移动位置到相机外,关闭CanvasRender

游戏热更新

代码热更新的具体方案: lua, ILRT
资源热更新的具体方案: AB
资源分发方案
外部玩家存在不同的版本,如何同步升级这些版本: 打包差异升级包, 版本号

游戏SDK

渠道如何打包
渠道SDK如何接入

数组和链表的区别

从逻辑结构上来看,数组必须实现定于固定的长度,不能适应数据动态增减的情况,即数组的大小一旦定义就不能改变。当数据增加是,可能超过原先定义的元素的个数;当数据减少时,造成内存浪费;
链表动态进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。
从内存存储的角度看;数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;链表从堆中分配空间,自由度大但是申请管理比较麻烦。
从访问方式类看,数组在内存中是连续的存储,因此可以利用下标索引进行访问;链表是链式存储结构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。

核心素质:

抗压能力、组织协调能力、学习能力、解决问题能力、主动反馈、执行力

专业能力:

完整的上线项目经历、架构/业务设计能力、工程管理(规范、文档、代码审核)

Mask和RectMask2D原理

Mask的原理就是利用了StencilBuffer(模板缓冲),它里面记录了一个ID,被裁切元素也有StencilBuffer(模板缓冲)的ID,并且和Mask里的比较,相同才会被渲染。因为模板缓冲可以提供模板的区域,也就是前面设置的圆形图片,所以最终会将元素裁切到这个圆心图片中。 如图所示,在Mask外面放一个普通的图片,默认情况下Stencil Ref的值是0,所以它不会被裁切,永远会显示出来。

RectMask2D

Mask2D会在OnEnable()方法中,将当前组件注册ClipperRegistry.Register(this);这样在上面ClipperRegistry.instance.Cull();方法时就可以遍历所有Mask2D组件并且调用它们的PerformClipping()方法了。PerformClipping()方法,需要找到所有需要裁切的UI元素,因为Image和Text都继承了IClippable接口,最终将调用Cull()进行裁切。
RectMask2D会将RectTransform的区域作为_ClipRect传入Shader中,并且激活UNITY_UI_CLIP_RECT的Keywords。Stencil Ref 的值是0 表示它并没有使用模板缓冲比较,如果只是矩形裁切,RectMask2D并且它不需要一个无效的渲染用于模板比较,所以RectMask2D在特定情况下的效率会比Mask要高。
Share on

James
WRITTEN BY
James
Unity Developer