江紅偉 吳兆明
摘要:該文從Unity腳本開發(fā)中程序的執(zhí)行方法和途徑為出發(fā)點,著重討論了腳本生命周期中程序從喚醒、開始到序運行再到渲染和GUI層面上的各個函數(shù)的執(zhí)行原理和方法,讓大家能夠清楚地知道繼承于MonoBehaviour類的腳本的行為,進(jìn)而能夠正確理解Unity腳本的執(zhí)行機(jī)會和效率問題。本文還討論了Unity系統(tǒng)中時間系統(tǒng)的問題,讓大家認(rèn)識到幀、秒和速率的關(guān)系。這兩個方面的問題對于初步介入Unity程序開發(fā)領(lǐng)域的開發(fā)者來說是首要需解決的技術(shù)和思維方法的前提。
關(guān)鍵詞:Unity;淺意識;生命周期;函數(shù);C#
中圖分類號:TP3 文獻(xiàn)標(biāo)識碼:A 文章編號:1009-3044(2018)32-0230-03
在Unity開發(fā)項目中,程序算法和邏輯是核心,其他組件的活動都要受到相應(yīng)腳本的控制,這就好比人的大腦控制著其他一切器官的活動。在Unity中,封裝好的游戲腳本都將作為這個游戲物體的組件,并成為這個物體當(dāng)中一部分。它好比神經(jīng)網(wǎng)絡(luò)貫穿到物體的全身,從而控制物體的各個組件的各個功能。在Unity中物體的常用組件其實就是被封裝好的腳本語言。
控制組件的行為的腳本可大致被分為“淺意識腳本”和“深意識腳本”,繼承于MonoBehaviour類的腳本可以稱之為“淺意識腳本”,幾乎所有的應(yīng)用項目開發(fā)都是基于這種“淺意識腳本”而進(jìn)行的二次開發(fā);而可以直接在Unity編輯器中運行的腳本,我們可以稱之為“深意識腳本”,它可以用作編輯器本身的功能擴(kuò)展性的高級開發(fā)。
Unity官方提供了三種可執(zhí)行的腳本語言,分別是“Javascript”“C#”和“Boo”,其中C#是屬于強(qiáng)類型語言,而Javascript和Boo屬于弱類型語言。由于在真實開發(fā)中要求較高效率的程序執(zhí)行速度和較好的程序結(jié)構(gòu),所以大多數(shù)項目開發(fā)都會使用C#進(jìn)行腳本編寫。本次研究中,我也將基于C#進(jìn)行講解。
1 腳本生命周期
在Unity引擎中,程序的執(zhí)行方式是單線程的,并且在大多數(shù)的項目開發(fā)中都是基于“淺意識腳本”的開發(fā),也就是我們所編寫的程序基本都是繼承于MonoBehaviour類的,所以清楚的理解MonoBehaviour中從腳本喚醒到腳本銷毀的生命周期尤為重要。
和安卓開發(fā)中的Activity生命周期有很多類似的地方,Unity腳本的基本生命周期也是由一些基本的區(qū)塊構(gòu)成的,分別是In Editor Mode(編輯器層面)、Startup(程序開始層面)、Updates(程序運行層面)、Rendering(渲染層面)及GUI(圖形界面層面)。
下面我們首先來了解各個層面下特定函數(shù)的調(diào)用方法和各自的功能區(qū)別。
1.1 In Editor Mode
In Editor Mode(編輯器層面)是指Unity此時處在編輯狀態(tài),也就是在游戲開始之前的狀態(tài),這時物體腳本還未被喚醒,僅僅是組件被添加到了物體而已。所以這個狀態(tài)的執(zhí)行時間是先于其他一切層面的。
這個狀態(tài)下可以運行的函數(shù)是Reset,它將在用戶點擊檢視面板中該組件的Reset按鈕或者首次添加該組件時被調(diào)用。如圖1所示,變量xCopies和distance在被點擊檢視面板中該組件的Reset鍵后被初始化為10和1.5。
Reset函數(shù)主要用于初始化腳本中的數(shù)據(jù),如:初始化一個值類型、初始化一個引用對象等等,這樣做可以讓這些數(shù)據(jù)不會因為后期的人為因素而發(fā)生不可預(yù)計的改變,在一個腳本中起到固定化某些初始參數(shù)的作用。
1.2 Startup
Startup程序開始層面下的函數(shù)有Awake、OnEnable、Start。當(dāng)游戲運行之后,只要當(dāng)前腳本所在的物體被啟用了,即使腳本組件沒有被啟用,Awake和OnEnable都會在將被調(diào)用一次;而Start是在該腳本被啟用之后才調(diào)用一次的。
這里我們著重討論一下Awake和Start的關(guān)系。Awake開始于游戲運行時的第一時間,Start開始于Awake之后、Update之前,他們的主要作用都是給變量賦初值。一般情況下一個腳本里面無須同時具備Awake和Start函數(shù),如圖2中的代碼,同時具有了Awake和Start函數(shù)結(jié)構(gòu),因為Start的執(zhí)行時機(jī)是在Awake之后,所以xCopies最終值為15、distance的最終值為2.5,也就是說雖然Awake被執(zhí)行了,但是在瞬間之后就跳轉(zhuǎn)到Start的執(zhí)行結(jié)果了。
假如一個腳本非得同時具有Awake和Start函數(shù)結(jié)構(gòu),那么也應(yīng)該在Awake和Start分別執(zhí)行不同變量的賦值計算,這樣可以達(dá)到對不同的變量進(jìn)行分類處理的目的。
1.3 Updates
Updates程序運行層面中包含的函數(shù)有FixedUpdate、yieldWaitForFixedUpdate、yieldWaitForSeconds、Update、LateUpdate等。
其中FixedUpdate、yieldWaitForFixedUpdate是基于一個物理時間執(zhí)行一次程序內(nèi)容,這個物理時間可在“Edit”→“Project Setting” →“Time” 中進(jìn)行設(shè)置,默認(rèn)是Fixed TimeStep是0.02秒,這個值就是FixedUpdate和yieldWaitForFixedUpdate的更新頻率。當(dāng)給剛體加一個物理作用力時,就得使用FixedUpdate來調(diào)用程序,如果渲染效率低下時總時間長度內(nèi)調(diào)用的FixedUpdate頻率是固定不變的,這樣就控制好了物體運動速度的唯一性。
Update和LateUpdate是基于每幀更新的函數(shù),由于Unity中幀的運行速度會根據(jù)渲染速度的快慢而得到不同的幀速度,所以不適用于物理作用力的更新,而適用于一個固定速度、一個位置變化的更新。
這時,發(fā)現(xiàn)物體可以沿著y軸的正軸方向移動,但是運動速度不均勻,這是由于這個y軸的數(shù)值變換值是每一幀都會進(jìn)行一次更新的,而Unity中幀的速率是根據(jù)圖形渲染效率而變快或者變慢的,所以導(dǎo)致了這種情況。為了避免這種情況的發(fā)生,我們應(yīng)該將每幀的運動速度轉(zhuǎn)為每秒的運動速度,這樣就可以讓物體產(chǎn)生勻速運動了。我們只需在速度值后面乘上Time.deltaTime就可以了,但是由于由秒計算的速度要比以幀計算的速度要慢很多,我們還應(yīng)該乘以一個系數(shù),寫法如下:
Update是實現(xiàn)各種基本游戲行為最常用的函數(shù),LateUpdate是在所有Update結(jié)束后才調(diào)用的。所以我們可以將正常行為的代碼發(fā)在Update中,而需要滯后行為的代碼放在LateUpdate中。如讓一架相機(jī)跟隨一個角色運動,我們就可以把角色控制代碼放在Update中,讓角色可以預(yù)運動起來;把相機(jī)的控制代碼放到LateUpdate中,讓相機(jī)的運動有一定的滯后性,這樣相機(jī)的動作將更有彈性。
1.4 Rendering和GUI
在程序運行層面運行之后就涉及場景渲染和UI顯示的問題了,渲染層面及圖形界面層面是在Updates之后進(jìn)行每一幀的進(jìn)行更新的。首先我們需要一個相機(jī)來渲染場景,有時我們還需要另一個相機(jī)來渲染圖形界面,這里就涉及如何管理多個相機(jī),讓它們協(xié)同工作的問題。如圖3中所示,“Main Camera”是主相機(jī),用來顯示場景;“Canvas”UI容器之下的“UICamera”是專用于圖形界面的相機(jī)。
在主相機(jī)的屬性中,“ClearFlags”用來控制場景背景的顯示方案,分別是天空盒、單色背景、背景透明和不清空。其他三個選項比較好理解,“不清空”起到的作用是可以讓圖形在透明背景中不清除上一幀的渲染圖形,從而形成一個連續(xù)的動態(tài)軌跡效果。在這四個選項中最常用就應(yīng)該是“天空盒”,可以讓背景渲染出豐富的HDRI環(huán)境效果,讓游戲場景可以模擬出一個真實環(huán)境。
在主相機(jī)中,“CullingMask”應(yīng)設(shè)置為“Everything”,這樣就可以讓這個主相機(jī)顯示所有的元素,包括場景和圖形界面;還有一個比較重要的參數(shù)是“Depth”,用他來控制多個相機(jī)的疊加關(guān)系,此參數(shù)值越小代表此相機(jī)的層面越低,如本案例中將主相機(jī)設(shè)為-1,UICamera的對應(yīng)值設(shè)為0,這樣圖形界面就可以渲染覆蓋在場景元素之上。
在UICamera中,將“ClearFlags”設(shè)置為“Depth Only”這樣就可以讓圖形之外的區(qū)域透明化,不至于把整個場景都蓋住;“CullingMask”設(shè)置為“UI”,也就是控制這個相機(jī)只能用來渲染圖形界面;“Depth”剛剛說過應(yīng)該設(shè)置為0,確保圖形界面是覆蓋在場景之上的。
這樣,兩臺相機(jī)就基本做到了各司其職、協(xié)同工作的目的。但是真實的應(yīng)用并不是這么簡單的,我們還需要到“Canvas”這個UI容器里去設(shè)置并了解一些屬性。
首先設(shè)置“RenderMode”,它有三個選項,默認(rèn)選項為“ScreenSpace-Overlay”,這個選項是讓UI的顯示區(qū)域自動以屏幕空間匹配,UI顯示區(qū)域也會自動覆蓋到場景之上,這樣就無需使用UICamera了;我們可以設(shè)置為“ScreenSpace-Camera”,然后將“RenderCamera”設(shè)置為之前創(chuàng)建好的“UICamera”,這樣就讓UI的顯示區(qū)域跟這個UICamera關(guān)聯(lián)起來了,可以使用UICamera的“Depth”屬性來控制UI是渲染在場景物體之上還是之下,如圖4所示:
第三個選項是“WorldSpace”,作用是讓UI圖形區(qū)域在場景世界空間內(nèi)自由的放置。假如有多個Canvas,要控制這些Canvas區(qū)域的前后疊蓋關(guān)系,則可以在“SortingLayer”選項中建立不同的Sorting Layers,被放置在下面的層是疊在最上面被渲染出來的;假如這些Canvas都同屬在同一個Sorting Layers里面,則可以通過“Order in Layer”值控制層疊關(guān)系,數(shù)值大的是疊在上面的。
到這里我們基本了解了Unity中腳本從喚醒、程序開始到程序運行再到渲染和GUI在其生命流程中的基本運行原則和方法,為了更好地理解程序行為的運行方法,我們需要系統(tǒng)來探討一下Unity中的時間體系和關(guān)系。
2 Unity時間體系
在Unity中Time類所提供的都是靜態(tài)函數(shù),其中又被分為只讀函數(shù)和可修改函數(shù)。
2.1 可修改靜態(tài)函數(shù)
下面我們來探討可修改靜態(tài)函數(shù)。fixedDeltaTime、maximumDeltaTime、timeScale,這三個靜態(tài)變量除了可以在腳本中控制數(shù)值,也可以在系統(tǒng)設(shè)置“ProjectSettings/Time”中直接進(jìn)行修改,如圖5中對應(yīng)的三個參數(shù)設(shè)置:
fixedDeltaTime-以秒計算的每幀時間間隔,代表Unity系統(tǒng)中物理時間上的固定更新速率,更新速率越快表現(xiàn)出的畫面將越流暢、更新速率越慢表現(xiàn)出的畫面將越卡頓。
maximumDeltaTime-以秒計算的一幀中的最大時間間隔,用于控制當(dāng)某一瞬間系統(tǒng)配置消耗過高,導(dǎo)致幀速率降低而出現(xiàn)畫面速度變慢時的時間間隔,以保證畫面速度的流暢性。
timeScale-傳遞時間的縮放,這可以用于減慢或加速運動效果,如在游戲畫面進(jìn)行截屏?xí)r需要快進(jìn)或慢動作,這個數(shù)值將非常有用處。
captureFramerate-也是可修改的靜態(tài)變量,只能在腳本中控制其數(shù)值,數(shù)值為整數(shù)類型。這個函數(shù)的作用和timeScale非常類似,也可以用于形成減速或加速的運動效果,它是指以幀來計算游戲中的物理時間,如數(shù)值為0時代表使用游戲本身的時間體系,也就是關(guān)閉captureFramerate功能;數(shù)值為1時代表每跳一幀游戲中的時間過去了一秒,這將使場景的運動速度非常快;數(shù)值為50時代表每過50幀,游戲時間過去一秒,這將和系統(tǒng)的默認(rèn)速率相差無幾。所以可以通過這個數(shù)值大小來很好的控制游戲速率的快慢。
2.2 不可修改靜態(tài)函數(shù)
現(xiàn)在我們來探討Time類中的不可修改的靜態(tài)函數(shù)。
time、fixedTime和frameCount都是指游戲運行到現(xiàn)在所用的總時間,time、fixedTime它是以秒進(jìn)行計算的,而frameCount是以幀數(shù)計算的。time和fixedTime也是區(qū)別的,time精確到以每幀間隔的秒數(shù),而fixedTime是精確到系統(tǒng)的物理間隔時間,也就是說它們的精度是不一樣的。所以要正確地使用這兩個靜態(tài)變量就需要清楚time應(yīng)該被應(yīng)用在Update函數(shù)結(jié)構(gòu)中,而fixedTime應(yīng)該被應(yīng)用在FixedUpdate函數(shù)結(jié)構(gòu)中。
realtimeSinceStartup是指游戲開始到現(xiàn)在總共消耗的秒數(shù),包括游戲暫停的時間,而上面談到的三個靜態(tài)變量對于游戲暫停的時間是忽略不計的。
unscaledTime是在忽略TimeScale之后游戲運行到現(xiàn)在所用的總時間
timeSinceLevelLoad-是目前已加載完成的關(guān)卡所使用的時間,它也是以應(yīng)該被秒進(jìn)行計算的。
deltaTime和smoothDeltaTime類似,都是在計算一幀所需花費的時間。deltaTime是以秒計算完成上一幀所需要的時間,這是一個最常用的時間變量,如當(dāng)表達(dá)勻速速度時讓速度變量乘以此變量,就可以讓原來不穩(wěn)定的速度轉(zhuǎn)變成以秒計算的均勻的速度,如1.3章節(jié)中代碼部分所示的關(guān)系;smoothDeltaTime是對最近的幾幀所消耗的時間進(jìn)行平均化計算而得到的一幀所需的秒數(shù)。
unscaleDeltaTime是在忽略TimeScale之后完成上一幀所需要的時間。
3 總結(jié)
目前游戲開發(fā)及虛擬現(xiàn)實行業(yè)正在迅速崛起,逐漸茁壯成長為具有廣闊前景的新興市場。市場也需要Unity3D作為主流的技術(shù)支撐,所以我們應(yīng)該順應(yīng)市場發(fā)展的需求積極主動地掌握Unity3D技術(shù)、技巧,以謀求作為編程技術(shù)人員的更好的發(fā)展空間。
Unity3D技術(shù)發(fā)展市場潛力巨大,其版本更替推陳出新,目前的2018版已構(gòu)建了可編輯渲染管線,加強(qiáng)了下一代渲染技術(shù);優(yōu)化了輕量級渲染管線LWRP的性能,增強(qiáng)了高清晰渲染管線HDRP效果,從而幫助了開發(fā)者實現(xiàn)高端視覺效果;還增強(qiáng)了對Android、iOS、MacOS、Windows、PS4和UWP的IL2CPP托管代碼調(diào)試的支持,為移動端提供了輕量級渲染管線LWRP的優(yōu)化。
作為有一定程序開發(fā)經(jīng)驗和圖形基礎(chǔ)的程序開發(fā)者,想要從其他的程序開發(fā)領(lǐng)域進(jìn)入到Unity3D程序開發(fā)領(lǐng)域,首先要解決就是Unity3D腳本執(zhí)行原理和時間管理體系。只有透徹的理解這兩個部分的知識才有可能取得對Unity3D編程的更深層次的發(fā)展。作者著此文,希望能夠?qū)nity3D學(xué)習(xí)者起到拋磚引玉的效果,開啟Unity3D程序設(shè)計之門。
參考文獻(xiàn):
[1] 王樹斌.淺析Unity3d開發(fā)游戲流程及常用技術(shù)[J].電腦知識與技術(shù),2012(22).
[2] 陳嘉棟.Unity 3D腳本編程-使用C#語言開發(fā)跨平臺游戲[M].北京:電子工業(yè)出版社,2016.
【通聯(lián)編輯:張薇】