APP下载

Unity脚本执行周期算法与时间体系

2018-12-22江红伟吴兆明

电脑知识与技术 2018年32期
关键词:生命周期函数

江红伟 吴兆明

摘要:该文从Unity脚本开发中程序的执行方法和途径为出发点,着重讨论了脚本生命周期中程序从唤醒、开始到序运行再到渲染和GUI层面上的各个函数的执行原理和方法,让大家能够清楚地知道继承于MonoBehaviour类的脚本的行为,进而能够正确理解Unity脚本的执行机会和效率问题。本文还讨论了Unity系统中时间系统的问题,让大家认识到帧、秒和速率的关系。这两个方面的问题对于初步介入Unity程序开发领域的开发者来说是首要需解决的技术和思维方法的前提。

关键词:Unity;浅意识;生命周期;函数;C#

中图分类号:TP3 文献标识码:A 文章编号:1009-3044(2018)32-0230-03

在Unity开发项目中,程序算法和逻辑是核心,其他组件的活动都要受到相应脚本的控制,这就好比人的大脑控制着其他一切器官的活动。在Unity中,封装好的游戏脚本都将作为这个游戏物体的组件,并成为这个物体当中一部分。它好比神经网络贯穿到物体的全身,从而控制物体的各个组件的各个功能。在Unity中物体的常用组件其实就是被封装好的脚本语言。

控制组件的行为的脚本可大致被分为“浅意识脚本”和“深意识脚本”,继承于MonoBehaviour类的脚本可以称之为“浅意识脚本”,几乎所有的应用项目开发都是基于这种“浅意识脚本”而进行的二次开发;而可以直接在Unity编辑器中运行的脚本,我们可以称之为“深意识脚本”,它可以用作编辑器本身的功能扩展性的高级开发。

Unity官方提供了三种可执行的脚本语言,分别是“Javascript”“C#”和“Boo”,其中C#是属于强类型语言,而Javascript和Boo属于弱类型语言。由于在真实开发中要求较高效率的程序执行速度和较好的程序结构,所以大多数项目开发都会使用C#进行脚本编写。本次研究中,我也将基于C#进行讲解。

1 脚本生命周期

在Unity引擎中,程序的执行方式是单线程的,并且在大多数的项目开发中都是基于“浅意识脚本”的开发,也就是我们所编写的程序基本都是继承于MonoBehaviour类的,所以清楚的理解MonoBehaviour中从脚本唤醒到脚本销毁的生命周期尤为重要。

和安卓开发中的Activity生命周期有很多类似的地方,Unity脚本的基本生命周期也是由一些基本的区块构成的,分别是In Editor Mode(编辑器层面)、Startup(程序开始层面)、Updates(程序运行层面)、Rendering(渲染层面)及GUI(图形界面层面)。

下面我们首先来了解各个层面下特定函数的调用方法和各自的功能区别。

1.1 In Editor Mode

In Editor Mode(编辑器层面)是指Unity此时处在编辑状态,也就是在游戏开始之前的状态,这时物体脚本还未被唤醒,仅仅是组件被添加到了物体而已。所以這个状态的执行时间是先于其他一切层面的。

这个状态下可以运行的函数是Reset,它将在用户点击检视面板中该组件的Reset按钮或者首次添加该组件时被调用。如图1所示,变量xCopies和distance在被点击检视面板中该组件的Reset键后被初始化为10和1.5。

Reset函数主要用于初始化脚本中的数据,如:初始化一个值类型、初始化一个引用对象等等,这样做可以让这些数据不会因为后期的人为因素而发生不可预计的改变,在一个脚本中起到固定化某些初始参数的作用。

1.2 Startup

Startup程序开始层面下的函数有Awake、OnEnable、Start。当游戏运行之后,只要当前脚本所在的物体被启用了,即使脚本组件没有被启用,Awake和OnEnable都会在将被调用一次;而Start是在该脚本被启用之后才调用一次的。

这里我们着重讨论一下Awake和Start的关系。Awake开始于游戏运行时的第一时间,Start开始于Awake之后、Update之前,他们的主要作用都是给变量赋初值。一般情况下一个脚本里面无须同时具备Awake和Start函数,如图2中的代码,同时具有了Awake和Start函数结构,因为Start的执行时机是在Awake之后,所以xCopies最终值为15、distance的最终值为2.5,也就是说虽然Awake被执行了,但是在瞬间之后就跳转到Start的执行结果了。

假如一个脚本非得同时具有Awake和Start函数结构,那么也应该在Awake和Start分别执行不同变量的赋值计算,这样可以达到对不同的变量进行分类处理的目的。

1.3 Updates

Updates程序运行层面中包含的函数有FixedUpdate、yieldWaitForFixedUpdate、yieldWaitForSeconds、Update、LateUpdate等。

其中FixedUpdate、yieldWaitForFixedUpdate是基于一个物理时间执行一次程序内容,这个物理时间可在“Edit”→“Project Setting” →“Time” 中进行设置,默认是Fixed TimeStep是0.02秒,这个值就是FixedUpdate和yieldWaitForFixedUpdate的更新频率。当给刚体加一个物理作用力时,就得使用FixedUpdate来调用程序,如果渲染效率低下时总时间长度内调用的FixedUpdate频率是固定不变的,这样就控制好了物体运动速度的唯一性。

Update和LateUpdate是基于每帧更新的函数,由于Unity中帧的运行速度会根据渲染速度的快慢而得到不同的帧速度,所以不适用于物理作用力的更新,而适用于一个固定速度、一个位置变化的更新。

这时,发现物体可以沿着y轴的正轴方向移动,但是运动速度不均匀,这是由于这个y轴的数值变换值是每一帧都会进行一次更新的,而Unity中帧的速率是根据图形渲染效率而变快或者变慢的,所以导致了这种情况。为了避免这种情况的发生,我们应该将每帧的运动速度转为每秒的运动速度,这样就可以让物体产生匀速运动了。我们只需在速度值后面乘上Time.deltaTime就可以了,但是由于由秒计算的速度要比以帧计算的速度要慢很多,我们还应该乘以一个系数,写法如下:

Update是实现各种基本游戏行为最常用的函数,LateUpdate是在所有Update结束后才调用的。所以我们可以将正常行为的代码发在Update中,而需要滞后行为的代码放在LateUpdate中。如让一架相机跟随一个角色运动,我们就可以把角色控制代码放在Update中,让角色可以预运动起来;把相机的控制代码放到LateUpdate中,让相机的运动有一定的滞后性,这样相机的动作将更有弹性。

1.4 Rendering和GUI

在程序运行层面运行之后就涉及场景渲染和UI显示的问题了,渲染层面及图形界面层面是在Updates之后进行每一帧的进行更新的。首先我们需要一个相机来渲染场景,有时我们还需要另一个相机来渲染图形界面,这里就涉及如何管理多个相机,让它们协同工作的问题。如图3中所示,“Main Camera”是主相机,用来显示场景;“Canvas”UI容器之下的“UICamera”是专用于图形界面的相机。

在主相机的属性中,“ClearFlags”用来控制场景背景的显示方案,分别是天空盒、单色背景、背景透明和不清空。其他三个选项比较好理解,“不清空”起到的作用是可以让图形在透明背景中不清除上一帧的渲染图形,从而形成一个连续的动态轨迹效果。在这四个选项中最常用就应该是“天空盒”,可以让背景渲染出丰富的HDRI环境效果,让游戏场景可以模拟出一个真实环境。

在主相机中,“CullingMask”应设置为“Everything”,这样就可以让这个主相机显示所有的元素,包括场景和图形界面;还有一个比较重要的参数是“Depth”,用他来控制多个相机的叠加关系,此参数值越小代表此相机的层面越低,如本案例中将主相机设为-1,UICamera的对应值设为0,这样图形界面就可以渲染覆盖在场景元素之上。

在UICamera中,将“ClearFlags”设置为“Depth Only”这样就可以让图形之外的区域透明化,不至于把整个场景都盖住;“CullingMask”设置为“UI”,也就是控制这个相机只能用来渲染图形界面;“Depth”刚刚说过应该设置为0,确保图形界面是覆盖在场景之上的。

这样,两台相机就基本做到了各司其职、协同工作的目的。但是真实的应用并不是这么简单的,我们还需要到“Canvas”这个UI容器里去设置并了解一些属性。

首先设置“RenderMode”,它有三个选项,默认选项为“ScreenSpace-Overlay”,这個选项是让UI的显示区域自动以屏幕空间匹配,UI显示区域也会自动覆盖到场景之上,这样就无需使用UICamera了;我们可以设置为“ScreenSpace-Camera”,然后将“RenderCamera”设置为之前创建好的“UICamera”,这样就让UI的显示区域跟这个UICamera关联起来了,可以使用UICamera的“Depth”属性来控制UI是渲染在场景物体之上还是之下,如图4所示:

第三个选项是“WorldSpace”,作用是让UI图形区域在场景世界空间内自由的放置。假如有多个Canvas,要控制这些Canvas区域的前后叠盖关系,则可以在“SortingLayer”选项中建立不同的Sorting Layers,被放置在下面的层是叠在最上面被渲染出来的;假如这些Canvas都同属在同一个Sorting Layers里面,则可以通过“Order in Layer”值控制层叠关系,数值大的是叠在上面的。

到这里我们基本了解了Unity中脚本从唤醒、程序开始到程序运行再到渲染和GUI在其生命流程中的基本运行原则和方法,为了更好地理解程序行为的运行方法,我们需要系统来探讨一下Unity中的时间体系和关系。

2 Unity时间体系

在Unity中Time类所提供的都是静态函数,其中又被分为只读函数和可修改函数。

2.1 可修改静态函数

下面我们来探讨可修改静态函数。fixedDeltaTime、maximumDeltaTime、timeScale,这三个静态变量除了可以在脚本中控制数值,也可以在系统设置“ProjectSettings/Time”中直接进行修改,如图5中对应的三个参数设置:

fixedDeltaTime-以秒计算的每帧时间间隔,代表Unity系统中物理时间上的固定更新速率,更新速率越快表现出的画面将越流畅、更新速率越慢表现出的画面将越卡顿。

maximumDeltaTime-以秒计算的一帧中的最大时间间隔,用于控制当某一瞬间系统配置消耗过高,导致帧速率降低而出现画面速度变慢时的时间间隔,以保证画面速度的流畅性。

timeScale-传递时间的缩放,这可以用于减慢或加速运动效果,如在游戏画面进行截屏时需要快进或慢动作,这个数值将非常有用处。

captureFramerate-也是可修改的静态变量,只能在脚本中控制其数值,数值为整数类型。这个函数的作用和timeScale非常类似,也可以用于形成减速或加速的运动效果,它是指以帧来计算游戏中的物理时间,如数值为0时代表使用游戏本身的时间体系,也就是关闭captureFramerate功能;数值为1时代表每跳一帧游戏中的时间过去了一秒,这将使场景的运动速度非常快;数值为50时代表每过50帧,游戏时间过去一秒,这将和系统的默认速率相差无几。所以可以通过这个数值大小来很好的控制游戏速率的快慢。

2.2 不可修改静态函数

现在我们来探讨Time类中的不可修改的静态函数。

time、fixedTime和frameCount都是指游戏运行到现在所用的总时间,time、fixedTime它是以秒进行计算的,而frameCount是以帧数计算的。time和fixedTime也是区别的,time精确到以每帧间隔的秒数,而fixedTime是精确到系统的物理间隔时间,也就是说它们的精度是不一样的。所以要正确地使用这两个静态变量就需要清楚time应该被应用在Update函数结构中,而fixedTime应该被应用在FixedUpdate函数结构中。

realtimeSinceStartup是指游戏开始到现在总共消耗的秒数,包括游戏暂停的时间,而上面谈到的三个静态变量对于游戏暂停的时间是忽略不计的。

unscaledTime是在忽略TimeScale之后游戏运行到现在所用的总时间

timeSinceLevelLoad-是目前已加载完成的关卡所使用的时间,它也是以应该被秒进行计算的。

deltaTime和smoothDeltaTime类似,都是在计算一帧所需花费的时间。deltaTime是以秒计算完成上一帧所需要的时间,这是一个最常用的时间变量,如当表达匀速速度时让速度变量乘以此变量,就可以让原来不稳定的速度转变成以秒计算的均匀的速度,如1.3章节中代码部分所示的关系;smoothDeltaTime是对最近的几帧所消耗的时间进行平均化计算而得到的一帧所需的秒数。

unscaleDeltaTime是在忽略TimeScale之后完成上一幀所需要的时间。

3 总结

目前游戏开发及虚拟现实行业正在迅速崛起,逐渐茁壮成长为具有广阔前景的新兴市场。市场也需要Unity3D作为主流的技术支撑,所以我们应该顺应市场发展的需求积极主动地掌握Unity3D技术、技巧,以谋求作为编程技术人员的更好的发展空间。

Unity3D技术发展市场潜力巨大,其版本更替推陈出新,目前的2018版已构建了可编辑渲染管线,加强了下一代渲染技术;优化了轻量级渲染管线LWRP的性能,增强了高清晰渲染管线HDRP效果,从而帮助了开发者实现高端视觉效果;还增强了对Android、iOS、MacOS、Windows、PS4和UWP的IL2CPP托管代码调试的支持,为移动端提供了轻量级渲染管线LWRP的优化。

作为有一定程序开发经验和图形基础的程序开发者,想要从其他的程序开发领域进入到Unity3D程序开发领域,首先要解决就是Unity3D脚本执行原理和时间管理体系。只有透彻的理解这两个部分的知识才有可能取得对Unity3D编程的更深层次的发展。作者著此文,希望能够对Unity3D学习者起到抛砖引玉的效果,开启Unity3D程序设计之门。

参考文献:

[1] 王树斌.浅析Unity3d开发游戏流程及常用技术[J].电脑知识与技术,2012(22).

[2] 陈嘉栋.Unity 3D脚本编程-使用C#语言开发跨平台游戏[M].北京:电子工业出版社,2016.

【通联编辑:张薇】

猜你喜欢

生命周期函数
二次函数
二次函数
函数备考精讲
从生命周期视角看并购保险
民用飞机全生命周期KPI的研究与应用
企业生命周期及其管理
覆盖整个生命周期的威胁防护解决方案
全方位全生命周期项目管理浅谈