客户端开发问题总结
UNITY是怎么实现跨平台的
PART1
本质上,就和Java代码运行在JVM虚拟机中一样,Unity使用了叫做CIL的中间语言指令集(也叫通用中间件语言),这种指令集在虚拟环境CLI中运行,所以不需要对不同实际运行平台做适配,实现了跨平台。
中间代码可以实现平台无关性,即与特定CPU无关,只要把.NET框架某种语言编译成IL代码,就实现.NET框架中语言之间的交互操作(这就是为什么unity3D里面可以c#和js混编)
C#这玩意不开源,只运行在Window系列的平台上,那么由C#写的Unity的Script脚本就不能跨平台运行。所以开发了C#的虚拟机VM,就是Mono,这个东西和JVM的作用是一样的,因此,MonoVM使得C#能够在各平台上使用。
可以说mono其实是微软的.net的另一个实现,但是mono它跨平台,说白了Mono相当于一个跨平台的第三方.NET库。
实际使用中,C#代码被编译为运行在虚拟机之上的IL中间语言,然后在需要使用时加载VM,进行动态编译(Just In Time,JVM和前端里也有这个概念)
一个完整的MONO编译运行流程大致如下,最终它们会跑在指定的MONO VM之中
大概背书过程就是:c#脚本被mcs编译成CIL(其实是CIL assembly),以.exe或者.dll的形式存在,然后发布的应用中因为带有Mono运行时(虚拟机),其中的JIT或者Full-AOT引擎会将CIL转译成目标平台上的原生码,供不同的操作系统运行。
PART2
mono运行时中也有编译器(转译),将byte code转译成运行平台上的原生码(可能是0101…之类的?),分为三种形式,即时编译(JIT)、提前编译(AOT)和完全提前编译(Full-AOT)。这一步很重要,因为原生码直接作用于硬件,所以可以加快运行速度。这样Unity就集合了高级语言(c#/JS)的优点,开发效率高,也集合了低级语言的优点,运行速度快。
动态编译(JIT)效率慢,因为要一边编译,一边处理代码逻辑。IOS不允许动态编译,所以Unity不提供热更新。但是Android则允许动态编译。(怪不得IOS系统快,都是提前编译好的)
静态编译(AOT)其实是半静态编译,因为它可以保留代码,而只提前编译元数据信息,而且也是借助JIT引擎编译的。
完全静态编译(Full-AOT),ios上主要使用的就是这种转译方式,提前编译好,到时候只加载本地映像。
另外mono运行时还有一个重要的作用就是垃圾自动回收机制,有两种,一种是分代回收机制,一种是贝母回收机制。
早期使用的是Mono,现在使用的多是基于Mono衍生的IL2CPP
Mono的缺点 以及为什么要用IL2CPP 放在后面讲
ref:
https://www.zhihu.com/question/26595681
https://blog.csdn.net/wzjssssssssss/article/details/80196314
基本上这块可以搞明白了
另附Unity中的几种常见内存及其优化:https://www.jianshu.com/p/a79ceaccc1a3
DRAWCALL以及图集的使用
PART 1
显卡在渲染图片模型的过程中,图片从硬盘读到内存中,作为准备数据,然后CPU通知GPU,也就是调用图形库(Dx和OpenGL)接口,开始渲染操作。
那,一次通知(也可以说是OpenGL的一次描绘)就是一次DrawCall。
顺便一提DC也叫批次
具体的过程在这里:
https://zhuanlan.zhihu.com/p/26386905
CPU和GPU是一起走路的两条腿。每一次绘制CPU都要调用DrawCall,而在调动DrawCall前,CPU还要进行很多准备工作:检测渲染状态、提交渲染所需要的数据、提交渲染所需要的状态。
而GPU本身具有很强大的计算能力,可以很快就处理完渲染任务。
当DrawCall过多,CPU就会很多额外开销用于准备工作,CPU本身负载,而这时GPU可能闲置了。
比如拷贝1000个总大小1M的文件和单个大小为1M的文件,明显拷贝1000个文件要慢很多,DrawCall调用和这个很类似。
在这个过程中,要通知并且进行数据的传递,势必要消耗内存。那么多张图片就需要多次draw call,合成了一张大图则只需要一次draw call。所以如果DrawCall数量过多就会导致CPU进行大量计算,进而导致CPU的过载,影响游戏运行效率。
以OpenGL为例,一个简单的openGL的绘图次序是:设置颜色→绘图方式→顶点座标→绘制→结束。每帧都会重复以上的步骤。这就是一次draw call
PART2
那么如何减少DC呢?
(1)合批,就是把能合并的都合并起来,尽量减少Draw Call。(肯定有不能合并的,比如场景中的一个大树和主角,还有好多渲染状态啊透明啊之类的,篇幅有限不展开讲了,真感兴趣应该可以自己搜得到)
(2)instance,GPU硬件算法。使用与大量需要重复绘制的模型,比如草地。但是instance是有上限的,并且只能减少Draw Call,对于增加的面数来说是无解的。
渲染管线
PART1
渲染流水线都是可编程的,一个片段就是一个Shader。
作用:自定义的渲染管线能决定渲染的风格,以满足游戏的画面渲染要求。
渲染管线有三个阶段:应用 几何 光栅化
(1)应用阶段 GPU向CPU要数据也就是发生了DrawCall
CPU要把场景数据和粗粒度剔除(就是可以剔除不可见的物体,减少无效数据)从硬盘RAM那要过来发给GPU
(2) 几何阶段 涉及各种坐标变换 并裁剪
顶点着色器把顶点坐标从模型空间变换到齐次裁剪空间,然后裁剪以我们摄像机视角来说被遮住的物体,最后再把xyz坐标转换成xy坐标满足屏幕的空间坐标
(3)光栅化阶段 将3D连续的物体转化为离散屏幕像素点 然后着色和阴影处理
生命周期
Awake:当一个脚本被实例化时,Awake 被调用。我们大多在这个类中完成成员变量的初始化。
Start:仅在 Update 函数第一次被调用前调用。因为它是在 Awake 之后被调用的,我们可以把一些需要依赖 Awake 的变量放在Start里面初始化。 同时我们还大多在这个类中执行 StartCoroutine 进行一些协程的触发。要注意在用C#写脚本时,必须使用 StartCoroutine 开始一个协程,但是如果使用的是 JavaScript,则不需要这么做。
Update:当开始播放游戏帧时(此时,GameObject 已实例化完毕),其 Update 在 每一帧 被调用。
LateUpdate:LateUpdate 是在所有 Update 函数调用后被调用。
FixedUpdate:当 MonoBehaviour 启用时,其 FixedUpdate 在每一固定帧被调用。
OnEnable:当对象变为可用或激活状态时此函数被调用。
OnDisable:当对象变为不可用或非激活状态时此函数被调用。
OnDestroy:当 MonoBehaviour 将被销毁时,这个函数被调用。

浙公网安备 33010602011771号